Security & remediation

No critical or high-severity issues.

Independently audited across 11 security categories — 0 critical, 0 high. 8 of 12 verified findings fully resolved and 4 more partially mitigated; 33 areas examined and cleared.

Posture

0
Critical
0
High
2
Medium
10
Low
Remediation8 / 12 resolved · 4 partial

Audited 2026-05-02 · commit 536ced4 · automated multi-agent pipeline · remediation verified in code 2026-05-30

What we checked & cleared

33 areas examined and found secure. 6 reported issues were rejected as false positives after deep-dive verification.

what was checked
  • SQL injection across database queries (parameterized through the ORM)
  • Cross-site scripting on share images and leaderboard nicknames
  • Prototype pollution and unsafe deserialization in message parsing
  • Command injection, server-side template injection and path traversal
  • Internal error / stack-trace leakage in HTTP responses
  • Secrets and tokens leaked through logs
  • Source maps exposed in the production build
  • Credentials committed to git (.env / .dev.vars)
  • Session endpoint over-exposing user fields

Findings — before & after

Medium auth Fixed

A shared friend-invite link could be claimed more than once

Before

A friend-invite link's open slot had no single-use guard, so whoever opened the link first could take the slot, and it could even reopen if the match's server state was evicted — and the inviter could not tell who had joined.

After

A single-use flag now marks the slot claimed on first use and rejects later attempts; it is persisted in the room snapshot, so it survives server-state eviction and the slot never reopens. packages/partykit-server/src/match/match-room.ts:221-234

Medium auth Fixed

Anonymous account creation had no effective rate limit in production

Before

The auth framework's rate limiting was effectively off in production because it defaulted to an environment flag that was never set, allowing unbounded anonymous account creation and database-quota / billing abuse.

After

An explicit rate-limit block is now enabled in code (5 requests per 60s, with the correct client-IP header for the Workers runtime), independent of any environment flag. apps/web/src/lib/server/auth.ts:103-113

Low auth Fixed

Game-identity tokens could be issued for any display name without signing in

Before

A token-issuing endpoint handed out player tokens for any chosen display name with no sign-in or identity check, allowing nick spoofing and leaderboard / queue noise.

After

The old anonymous endpoint now returns 410 Gone and is replaced by a web endpoint that requires an authenticated session, deriving identity and display name from the server-side account instead of client input. apps/web/src/routes/api/partykit-token/+server.ts:12-17

Low auth Fixed

The hidden item's other attribute values were still sent to the client

Before

When a round was sent to clients, only the challenged attribute of the hidden item was stripped — its other numeric attributes were still leaked, contradicting the documented promise and giving a small cheating aid.

After

The public round now strips all of the hidden item's attribute values, keeping only name and image, and a regression test asserts the values object is empty. packages/game-engine/src/public-round.ts:43-48

Low auth Fixed

A match token was checked only at connect, not re-checked on later messages

Before

The spec promised the match token would be re-checked on every message, but it was only verified once when the socket opened; afterwards the connection's side binding was trusted for the socket's lifetime, so an expired token could keep acting in a stalled match.

After

The server now re-checks the token's expiry on every privileged in-match message and drops a connection whose token has expired, matching the documented promise; a regression test covers it. packages/partykit-server/src/match/match-room.ts:130-133

Low config Fixed

The realtime service accepted requests from any website origin

Before

The realtime endpoints answered every request with a wildcard cross-origin header, so any website's JavaScript could call them from a visitor's browser and read the response — letting a malicious page burn a visitor's rate-limit budget and drive lobby noise.

After

A strict Origin allowlist (the production domains plus configured origins) now replaces the wildcard, and unknown origins are blocked; a regression test asserts an unknown origin is denied. packages/partykit-server/src/util/cors.ts

Low business-logic Fixed

Both sides of a 1v1 match could be driven by one player

Before

One client could re-authenticate with a second identity on the same connection and bind itself to both sides of a 1v1 match, then play against itself to fabricate the result.

After

Authentication now rejects a second side on a connection that is already bound to one (with a conn_taken error), so a single connection can never hold both sides; a regression test covers it. packages/partykit-server/src/match/match-room.ts:222-228

Low concurrency Fixed

A simultaneous-disconnect edge case could mis-assign a match result

Before

The match room kept only a single shared forfeit timer, so if both players dropped within the 30-second grace window the second disconnect cancelled the first player's forfeit — letting a player who abandoned a losing match still come away with the win.

After

Each side now has its own forfeit timer plus a finished-match guard, so a second disconnect can no longer cancel the first player's forfeit or flip the winner; a regression test covers it. packages/partykit-server/src/match/match-room.ts:80,423-451

Low config Partially fixed

Browser protection headers are now sent on pages

Mitigated — protective headers and a content-security-policy ship on responses; a static-asset header file and a regression test are still tracked.

Low crypto Partially fixed

Weak or placeholder auth secrets are now rejected at startup

Mitigated — a minimum-length and leaked-placeholder check now fails startup on a weak key; a dedicated regression test is still tracked.

Low dependency Partially fixed

Outdated third-party libraries and no automated update process

Mitigated — the production-reachable libraries were pinned to safe versions; an automated CI dependency-audit gate is still tracked.

Low logging Partially fixed

Limited visibility into suspicious activity

Mitigated — the key anti-cheat and authentication deny-points now emit log lines; structured / SIEM-ready logging is still tracked.

Severity & categories

Critical
Directly exploitable with severe impact (RCE, auth bypass, data loss). Must be fixed before anything ships.
High
Exploitable with serious impact; fix before launch.
Medium
A real weakness with limited or conditional impact; fix soon.
Low
Minor or hard-to-exploit issue; defense-in-depth.
auth
Authentication & authorization — who is allowed to do what.
business-logic
Abuse of the app's own rules — match ownership, results and workflow.
concurrency
Race conditions and timing edge cases between simultaneous events.
config
Security-relevant configuration — headers, CORS and transport.
crypto
Cryptography — secrets, hashing, tokens and randomness.
dependency
Third-party packages with known advisories or supply-chain drift.
logging
Audit trail and security-event visibility.

How this was done

Found and fixed with an automated security pipeline

Recon mapped the attack surface, then category auditors swept auth, injection, crypto, concurrency, config, logging and dependencies in parallel. A deep-dive verifier re-checked every candidate and rejected 6 false positives, and a test-quality pass graded coverage. Each fix shown here was then verified directly in the current code — never taken on trust.