Addresses planner-audit #1 (CRITICAL — WebSocket joins unauthenticated) with the narrow fix. The upgrade handler used to immediately \`handleUpgrade\` for any \`/sync/<id>\` path, then \`getOrLoadDoc\` *created* a Y.Doc on demand. An attacker connecting to random sessionIds could: - exhaust process memory by spinning up Y.Doc objects for sessions that don't exist (no DB row backed them), and - resurrect closed/expired sessions by reconnecting (closed rows were still cacheable to \`docs\`). Now: the upgrade handler calls \`getSession(sessionId)\` first (\`SELECT \\* FROM planner.sessions WHERE id = ? AND closed = false\`). Missing or closed → \`socket.destroy()\` before handshake. DB unreachable also closes — fail closed; the client reconnects after backoff. Costs one DB roundtrip per upgrade. Upgrades are rare vs message volume, so the overhead is negligible. Note: we are NOT adding a join token. The planner is anonymous-by-design (see CLAUDE.md \"sessions are anonymous and ephemeral\") — knowing the URL still equals membership. The check here only guards against attacker-supplied sessionIds with no backing row. Full repo: pnpm typecheck / lint / test all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| journal | ||
| mobile | ||
| planner | ||