Skip to content

Security hardening review fixes#12

Merged
danjdewhurst merged 11 commits into
mainfrom
security-review-hardening
Jun 15, 2026
Merged

Security hardening review fixes#12
danjdewhurst merged 11 commits into
mainfrom
security-review-hardening

Conversation

@forjd-hermes-bot

Copy link
Copy Markdown
Collaborator

Summary

  • fix dependency audit finding
  • bound protocol/server-message parsing
  • reject insecure remote runtime endpoints
  • rate limit purchases and websocket upgrades
  • bound blocked-user lists and pagination
  • pin CI actions and container image digests
  • isolate local database secrets
  • require bearer tokens for production metrics
  • hide unread DM counts for blocked/non-messageable threads
  • document DM plaintext privacy posture
  • fail safely on duplicate legacy users during migration

Verification

  • bun audit
  • bun run lint
  • bun run typecheck
  • bun run build
  • bun run test:coverage
  • bun run coverage:check

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added pagination for blocked user lists with configurable limits.
    • Added rate limiting for inventory purchases per user.
    • Added rate limiting and per-user connection caps for WebSocket upgrades.
  • Bug Fixes

    • Oversized server messages are now rejected before processing.
    • Direct message read counts now respect messaging permissions and block status.
  • Improvements

    • Metrics endpoint now requires bearer token authentication (query parameters no longer accepted).
  • Documentation

    • Added clarity on direct message privacy practices and storage model.
  • Chores

    • Enhanced security hardening through container image digest pinning and secure secret generation.

Walkthrough

This PR hardens infrastructure by pinning Docker/CI images to SHA digests, binding the DB port to loopback, generating per-worktree random secrets, and restricting metrics auth to bearer tokens only. It adds bounded server message size validation throughout the protocol layer, introduces inventory purchase and WebSocket upgrade rate limiters with per-user connection caps, adds cursor-based pagination and a hard cap to the block service, filters DM unread counts by messaging permissions, and converts a destructive duplicate-row migration into a safe guard-and-raise pattern.

Changes

Infrastructure and secret hardening

Layer / File(s) Summary
Image digest pinning and DB port restriction
Dockerfile, .github/workflows/ci.yml, compose.yml, compose.db.yml, .env.example, package.json
Pins Bun, Caddy, and Postgres images to SHA digests in Dockerfile, CI workflow, and both Compose files. Restricts DB host port to 127.0.0.1. Updates .env.example to document the binding and adds an esbuild resolution override.
Random per-worktree secret generation and bearer-only metrics auth
scripts/setup-worktree.ts, apps/server/src/http/router.ts, apps/server/src/http/router.test.ts, .env.example
Generates cryptographically random POSTGRES_PASSWORD and AUTH_SECRET per worktree via randomSecret(). Removes query-param token fallback from metricsAccessAllowed, accepting only bearer tokens. Updates .env.example placeholders and the production metrics router test expectation.

Protocol message size limits

Layer / File(s) Summary
SERVER_* size constants and bounded server message schema
packages/protocol/src/schemas.ts
Adds MAX_RAW_SERVER_MESSAGE_BYTES and nine SERVER_* max-length/count constants. Introduces boundedServerString, boundedServerText, and serverTimestamp helpers. Rewrites all serverMessageSchema discriminated union variants to enforce explicit field-level size bounds.
parseRawServerMessage and NetClient integration
packages/protocol/src/parse.ts, apps/client/src/game/NetClient.ts, packages/protocol/src/protocol.test.ts, apps/client/src/game/NetClient.test.ts
Exports parseRawServerMessage(), which validates raw byte size before JSON parsing and returns structured ok/error results. NetClient switches from manual JSON.parse + parseServerMessage to parseRawServerMessage. Tests cover oversized/malformed rejection at the protocol and client layers.

Server-side rate limiting, connection caps, and block pagination

Layer / File(s) Summary
ServerConfig: new rate-limit and per-user limit fields
apps/server/src/config.ts, apps/server/src/config.test.ts
Extends ServerConfig with inventory purchase and WebSocket upgrade rate-limit fields and per-user maxima for blocked users and WebSocket connections. Adds exported default constants, parses env vars in getConfig(), and validates them in tests.
BlockService cursor pagination and maxBlockedUsers cap
apps/server/src/blocks/blocks.ts, apps/server/src/blocks/blocks.test.ts, apps/server/src/http/router.ts, apps/server/src/http/router.test.ts
Adds BlockListOptions, countBlockedUsers, and cursor-based listBlockedUsers with clampLimit/normalizeCursor helpers. BlockService enforces a configurable maxBlockedUsers cap. The blocked-users GET endpoint reads limit and afterUsername query params via a new parseBoundedQueryInteger helper.
Inventory purchase and WebSocket upgrade rate limiters
apps/server/src/serverRuntime.ts, apps/server/src/serverRuntime.test.ts, apps/server/src/http/router.ts, apps/server/src/http/router.test.ts, apps/client/src/config.ts, apps/client/src/config.test.ts
Creates FixedWindowRateLimiter instances for inventory purchases and WebSocket upgrades, wires them into the pruning loop and router. handleWebSocketUpgrade applies a two-stage limiter (IP then user-id) and a per-user active-connection cap. The inventory endpoint enforces a per-user rate limit before body parsing. Client normalizeBaseUrl restricts insecure protocols to local hostnames only.
DM unread counts filtered by messaging permissions
apps/server/src/messaging/messaging.ts, apps/server/src/messaging/messaging.test.ts, docs/development.md
DirectMessageService.unreadCounts() now filters store results through canMessage(), excluding blocked or non-friend conversations. Adds plaintext-storage doc comments to the messaging module and a "Direct message privacy" section to docs/development.md.
DB migration: raise on duplicate username_key instead of deleting
apps/server/src/db/migrations/0001_green_edwin_jarvis.sql, apps/server/src/db/migrations.test.ts
Replaces the DELETE-based duplicate-row cleanup with a DO $$ ... $$ guard block that raises an exception when duplicates are detected, requiring manual resolution before the unique constraint is added.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant handleWebSocketUpgrade
    participant wsUpgradeRateLimiter as websocketUpgradeRateLimiter
    participant TokenVerification
    participant ActiveSockets

    Client->>handleWebSocketUpgrade: WS upgrade request
    handleWebSocketUpgrade->>wsUpgradeRateLimiter: check(clientIP)
    alt IP rate limited
        wsUpgradeRateLimiter-->>handleWebSocketUpgrade: exceeded
        handleWebSocketUpgrade-->>Client: 429 + retry-after header
    else IP allowed
        handleWebSocketUpgrade->>TokenVerification: verify bearer token
        alt invalid token
            TokenVerification-->>handleWebSocketUpgrade: 401
            handleWebSocketUpgrade-->>Client: 401 Unauthorised
        else valid token → userId
            handleWebSocketUpgrade->>wsUpgradeRateLimiter: check(userId)
            alt user rate limited
                wsUpgradeRateLimiter-->>handleWebSocketUpgrade: exceeded
                handleWebSocketUpgrade-->>Client: 429 + retry-after header
            else user allowed
                handleWebSocketUpgrade->>ActiveSockets: count active sockets for userId
                alt count >= maxWebSocketConnectionsPerUser
                    ActiveSockets-->>handleWebSocketUpgrade: cap exceeded
                    handleWebSocketUpgrade-->>Client: 429 TOO_MANY_CONNECTIONS
                else
                    handleWebSocketUpgrade-->>Client: 101 Switching Protocols
                end
            end
        end
    end
Loading
sequenceDiagram
    participant WebSocket
    participant NetClient
    participant parseRawServerMessage
    participant serverMessageSchema

    WebSocket->>NetClient: raw message (string | Buffer)
    NetClient->>parseRawServerMessage: parseRawServerMessage(raw)
    parseRawServerMessage->>parseRawServerMessage: byteLength vs MAX_RAW_SERVER_MESSAGE_BYTES
    alt oversized
        parseRawServerMessage-->>NetClient: {ok: false, "Server message is too large"}
        NetClient->>NetClient: emit "received invalid server message" status
    else malformed JSON
        parseRawServerMessage-->>NetClient: {ok: false, "Malformed server JSON"}
        NetClient->>NetClient: emit "received invalid server message" status
    else valid
        parseRawServerMessage->>serverMessageSchema: parse(json) with SERVER_* bounds
        serverMessageSchema-->>parseRawServerMessage: ServerMessage
        parseRawServerMessage-->>NetClient: {ok: true, value: ServerMessage}
        NetClient->>NetClient: dispatch to onMessage handlers
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • forjd/tilezo#10: Modifies packages/protocol/src/schemas.ts and serverMessageSchema — the earlier PR adds economy message variants (balance.updated, inventory.updated, connected) that this PR now applies bounded validation to.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Security hardening review fixes' directly describes the primary focus of the changeset across all modified files.
Description check ✅ Passed The description relates to the changeset by listing specific security hardening changes matching the file modifications and objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@danjdewhurst danjdewhurst merged commit 6984d14 into main Jun 15, 2026
1 of 2 checks passed
@danjdewhurst danjdewhurst deleted the security-review-hardening branch June 15, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants