← Server architecture · all tasks
Server hardening batch — the 010 audit's security fixes
server-architecture/014 · created 2026-08-13 · updated 2026-08-13
What
The 010 audit (../010-accounts-audit.md) —
recommendation 1’s batch, split out from task 013 (which takes the client-side
findings) by Kris’s call on 2026-08-13. All small, local, independently testable
server fixes; none touches the grant architecture. Work in
land-of-lor/game/director/ (plus one world_server.gd change for the takeover
reason).
The batch (audit reference in parens)
- Revoke all sessions on password reset (H1) — call the existing, tested
revokeAllSessionsfromhttp.ts’s POST/resetpath. The audit’s one high-severity security gap. - Strip
accountIdfrom client-facingworld_stateentities (M1) — the director’s join reply currently re-links every player’s opaquep-<hex>wire id to their accountId. Themembersarray stays; entityaccountIdgoes. - XFF parsing (M2) — stop trusting
split(',')[0]; use the rightmost untrusted entry, and record the Caddytrusted_proxiesinvariant in the deploy runbook (verify the live Caddyfile once). - Rate limiter (M3) — evict full/idle buckets periodically; add an overall
per-IP cap on
login_emailalongside the per-identity key; rate-limitcreate_link_code(currently unlimited QR-generation CPU + inserts). - Message-size limits (M4) —
maxPayloadon both WebSocketServers;.max()onpassword,email,create_world.name,set_world_note.note. - scrypt cost actually applied (M5) — pass
{ N: SCRYPT_N }at hash AND verify time; parse the stored N on verify so parameter upgrades work. - Same-account lifecycle, server halves (M6) — emit a
TAKEN_OVERreason on the newest-wins disconnect inworld_server.gd(013’s client already handles it, dormant); refcountActiveWorld.playersper connection instead of aSet<accountId>(closing one of two same-account tabs currently shuts the world down under the survivor); convertserver.ts’sjoinedWorldsMap to the pair set the 010 plan specified (one socket, two sessions currently leaks a refcount). - Timing-based account enumeration (M7) — dummy scrypt on the unknown-email
login path; ack
request_password_resetbefore (or without awaiting) the Resend HTTP call. - Server lows from the audit, same pass: don’t echo
err.messageinINTERNAL_ERROR; fix the grant-mint-failure phantom-player leak (server.ts:342-345); uppercase+trim link-code redemption input; prune worldmove_budgetentries for departed entities; consider movement-budget cap ~40 for honest diagonal headroom (audit low — Kris’s call if behaviour changes).
Definition of done
Each fix lands with a test where the suite can express it (reset-revocation,
world_state accountId absence, size-limit rejection, scrypt N round-trip, TAKEN_OVER
reason, refcounted players). Full suite green, deploy per the standing runbook,
existing verify scripts still pass live. Handoff note + review, per convention.
View source in repository · also available as raw markdown.