Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions apps/runloop-engine/internal/db/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,33 @@ package db
import (
"context"
"fmt"
"math"
"time"

"github.com/jackc/pgx/v5/pgxpool"
"github.com/runloop/runloop-engine/internal/config"
"github.com/rs/zerolog/log"
"github.com/runloop/runloop-engine/internal/config"
)

// Postgres represents a PostgreSQL database connection
type Postgres struct {
Pool *pgxpool.Pool
}

// clampInt32 narrows an int from config (env-var-derived) to the int32 range
// pgxpool requires. Values ≤ 0 produce 0 so callers can fall back to library
// defaults; large values are pinned to math.MaxInt32 instead of silently
// wrapping into a negative number.
func clampInt32(v int) int32 {
if v <= 0 {
return 0
}
if v >= math.MaxInt32 {
return math.MaxInt32
}
return int32(v)
}

// NewPostgres creates a new PostgreSQL connection pool
func NewPostgres(cfg *config.Config) (*Postgres, error) {
ctx := context.Background()
Expand All @@ -25,9 +40,12 @@ func NewPostgres(cfg *config.Config) (*Postgres, error) {
return nil, fmt.Errorf("unable to parse database URL: %w", err)
}

// Set pool configuration
poolConfig.MaxConns = int32(cfg.DatabaseMaxConns)
poolConfig.MinConns = int32(cfg.DatabaseMaxIdle)
// Set pool configuration. pgxpool stores pool sizes as int32, so clamp
// the int values from config to the int32 range. In practice these come
// from env vars and are small (≤ a few hundred); the clamp is a safety
// belt that also satisfies CodeQL's incorrect-integer-conversion rule.
poolConfig.MaxConns = clampInt32(cfg.DatabaseMaxConns)
poolConfig.MinConns = clampInt32(cfg.DatabaseMaxIdle)

// Create connection pool
pool, err := pgxpool.NewWithConfig(ctx, poolConfig)
Expand Down
29 changes: 29 additions & 0 deletions apps/runloop-engine/internal/db/postgres_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package db

import (
"math"
"testing"
)

// clampInt32 keeps pgxpool's int32 fields safe from out-of-range int values
// coming out of the env-var config.

func TestClampInt32(t *testing.T) {
cases := []struct {
in int
want int32
}{
{0, 0},
{-1, 0},
{math.MinInt32, 0},
{1, 1},
{200, 200},
{math.MaxInt32, math.MaxInt32},
{math.MaxInt32 + 1, math.MaxInt32},
}
for _, c := range cases {
if got := clampInt32(c.in); got != c.want {
t.Errorf("clampInt32(%d) = %d, want %d", c.in, got, c.want)
}
}
}
20 changes: 16 additions & 4 deletions apps/runloop-engine/internal/queue/backend_kafka.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"math"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -43,8 +44,8 @@ type KafkaBackend struct {
workerID string

mu sync.Mutex
readers map[string]*kafka.Reader // key = topic+groupId — consumer
writers map[string]*kafka.Writer // key = brokers-joined
readers map[string]*kafka.Reader // key = topic+groupId — consumer
writers map[string]*kafka.Writer // key = brokers-joined
ackState map[string]*kafka.Message // handle -> message (for commit lookup)
}

Expand Down Expand Up @@ -303,13 +304,24 @@ func (k *KafkaBackend) Nack(ctx context.Context, q *QueueDef, handle string, req
}
}

// Increment attempts header and re-publish.
// Increment attempts header and re-publish. The header's previous count
// is read from an attacker-controllable bytestream, so do the +1 in int64
// (no overflow) and clamp into int32 right at the cast — a maliciously
// huge value shouldn't wrap into a negative attempt count and bypass the
// max-attempts check below.
attempts := int32(0)
out := make([]kafka.Header, 0, len(msg.Headers))
for _, h := range msg.Headers {
if h.Key == "x-attempts" {
n, _ := strconv.Atoi(string(h.Value))
attempts = int32(n) + 1
v := int64(n) + 1
if v < 1 {
v = 1
}
if v > math.MaxInt32 {
v = math.MaxInt32
}
attempts = int32(v)
continue
}
if h.Key == "x-last-error" {
Expand Down
Loading