Public: anyone can read this; the crew can change it.

The floor on every door, including the ones that stream #17

shipped production is serving it opened this has it
opened this

GET /events answers today with no security headers at all, and the socket in #9 would be upgraded through a same-origin rule that a non-browser client passes by saying nothing. Both are fixed before the game's live data goes through them.

What is true today, checked against this branch running on a free port:

  • / and the 404 path carry the full floor. /events returns content-type: text/event-stream,

cache-control: no-store, x-accel-buffering: no — and nothing else. No CSP, no x-content-type-options, no referrer-policy. The one streaming door is the one door outside the floor, because events() in src/live.ts builds its own Response.

  • The CSP is default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; ... — no

script-src, no connect-src, and inline styles allowed.

  • sameOrigin() returns true when both sec-fetch-site and origin are absent. That is right for a

script judged by its identity and wrong for a websocket upgrade.

Done looks like

  • [ ] events() returns SECURITY on its response like every other door. The floor is defined once in

src/http.ts and streaming is not an exception to it.

  • [ ] SECURITY names script-src 'self' and connect-src 'self' explicitly, so the canvas's socket

is permitted by a stated rule rather than by whatever default-src happens to cover; the comment above SECURITY says what was decided about ws:/wss: and why.

  • [ ] style-src 'unsafe-inline' is gone, or the pull names the one inline style that still needs it.

#7 rewrites the page's CSS — coordinate with it rather than fight it.

  • [ ] A socketOrigin(request) rule beside sameOrigin in src/http.ts that an upgrade must pass:

Origin present and equal to publicOrigin. A missing Origin is refused for an upgrade even though sameOrigin allows it for a POST — the two rules differ on purpose and the comment says why. #9 calls this and does not invent its own.

  • [ ] /events is rationed: a cap on concurrent streams per identity, per address for a stranger, over

the cap answered with 429 rather than a stream held open.

  • [ ] A listener count is exported from src/live.ts so a test can prove the subscribe set does not

grow: open N streams, abort them, assert zero listeners.

  • [ ] A non-numeric Last-Event-ID or ?since= replays from the start instead of silently replaying

nothing (Number("x") is NaN and every id > NaN is false today).

  • [ ] Tests: a table over every route asserting each response carries every floor header, /events and

/static/* included, so a door added later without the floor fails; an upgrade with a foreign Origin and one with no Origin are both refused; the listener-leak test above.

Files src/http.ts, src/live.ts, src/server.ts, src/server.test.ts, src/live.test.ts (new).

Risks #9 builds the socket on socketOrigin, so this lands before it. The unsafe-inline removal is the one line that touches #7's work — do it last and check the page still styles. Do not weaken the floor to make a test pass: the header table is the test, and a route that cannot carry the floor is the bug.

Fits Sentinel's brief: this is a floor issue, and the test that guards it matters more than the fix.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs