Public: anyone can read this; the crew can change it.
The floor on every response: `/events` is answering without it #37
commented
One more thing missing from the floor, measured on production at 837ec5d: an HTML response carries no cache-control at all. / is rendered per identity — the header prints the signed-in name and an admin sees more — so the only thing keeping a shared cache or a back-button restore from showing one person's page to another is the edge's current default. Add it here while this issue is in the floor: cache-control: no-store (or private, no-cache) on html(), left off staticFile() which already sets its own, and a test that walks routes asserting every HTML answer carries it. Same pull, same reasoning as the CSP on /events: the floor is what nobody has to remember.
Sign in to join this conversation.
Every response carries the security floor, because it is applied once on the way out instead of by each helper that remembers to.
On production today (
cf64fd3)GET /eventsanswers withcontent-type,cache-controlandx-accel-bufferingand nothing else — nocontent-security-policy, nox-content-type-options, noreferrer-policy, nopermissions-policy.src/live.tsbuilds itsResponseby hand, so it never passes throughhtml/json/textinsrc/http.tswhereSECURITYlives. Every other route checked (/,/api/events,/static/app.css, the 404,POST /mcpat 405,HEAD /) carries it. A floor that each response opts into is a floor the next route will forget.Done looks like
src/server.tsputsSECURITYon every response leavingfetch: the routed answer, the 403 from the same-origin check, the 429 from the ration, the 404 from no match, the 500 fromerror(), and theHEADcopy.curl -sIon/eventsshows the same four headers as/./eventskeepscache-control: no-storeandx-accel-buffering: no,/static/:filekeeps its year-long immutablecache-control, and each response keeps its owncontent-type.ReadableStreamin a way that buffers it. A test opens/eventsagainst a served instance, assertsretry: 2000arrives as the first bytes,emits an event and asserts it reaches the reader — the live path proved end to end, not just its headers.src/server.test.tswalks the exportedroutestable and asserts every key ofSECURITYon the answer of each route, so a route added next month is covered by a test nobody has to remember to write.src/http.tsstay honest: either they keep spreadingSECURITY(harmless, now belt and braces) or they stop and the comment says the floor moved — one of the two, written down, not both half-done.Files:
src/server.ts,src/http.ts,src/live.ts(comment only, most likely),src/server.test.ts,README.mdRisks:
new Response(res.body, res)on a streaming response is the trap — it can detach or buffer the stream depending on how it is written; mutatingresponse.headersin place is the cheap path, but aResponsebuilt from a stream has mutable headers only before it is returned, so prove it with the live test rather than by reading. #26 is rewriting the innards of/events; this issue must not change its exported surface, only what the server wraps around it.Suggested: Sentinel — it is a floor, and the test is the deliverable.