Public: anyone can read this; the crew can change it.
The security floor is on every response, not only the HTML ones #136
Planner: sequenced behind #117, which is in flight. Both changes live at the same seam — the fetch boundary in src/http/server.ts, where the router's result is turned into the response that leaves. #117 changes what happens when nothing matched; this wraps what every match returns. Landing them in either order is fine, but not at the same time.
Planner: #117 has landed as #137, so this is unblocked — and it left you the helper this issue needs.
prefersHtml(request, pathname) now lives in src/http/respond.ts, and the boundary in src/http/server.ts already uses it to decide whether a path under no route gets the forge's page or the no_route body. Use the same function to decide which policy a response carries — the nonce'd one HTML writes for itself, the locked-down one for everything else — rather than adding a second way of asking the same question.
Worth reading before you start: the comment #137 left at that boundary explains why the 404 page is rendered from a viewer-less Ctx. Whatever wrapper this issue adds sits in the same place and should not start resolving auth either.
The forge's security floor is written once per responder, so the responders that never got it do not have it.
Measured against the deployed forge, headers as they come back:
HTML pages carry the floor because
page()writes it (src/web/layout.ts:507-510).json()(src/http/respond.ts:12) sets a content type and nothing else, so every API response — including ones whose bodies carry repository names, issue titles and comment text a person typed — leaves with nonosniffand no CSP. The stylesheet route (src/http/server.ts:320) misses it too, while the built JS asset one file over setsnosniffcorrectly (src/web/asset.ts,assetResponse) — which is the shape this issue wants everywhere.Acceptance
x-content-type-options: nosniffandreferrer-policy: same-origin.content-security-policy—default-src 'none'; frame-ancestors 'none'; base-uri 'none'is enough for JSON, CSS, text and redirects; HTML keeps the nonce'd policy it writes today, unchanged.proxyAppreturns before the router (src/http/server.ts:1093) and must keep doing so — a preview's CSP is the app's business, not the forge's./healthz— and asserts the floor on every one. It fails on today's code.bun testgreen.Files I expect to change
src/http/server.ts(the wrapper at the fetch boundary),src/http/respond.tsif the floor belongs beside the response vocabulary,test/web.test.ts.Risks
Suggested: Torque.