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

Every open route answers with the security floor, 404s and errors included, proven by one test over the route table #12339

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`test/security-floor-routes.test.ts` now walks every route that `src/http/route_marks.ts` marks `open`, for every method, plus a forced 500. It checks that each answer carries Content-Security-Policy, X-Content-Type-Options and Referrer-Policy. No route was missing them, so the test needed no fixes and names no exemptions.

shipped production is serving it deploy bc5c621

opened this

test/security-floor-routes.test.ts now walks every route that src/http/route_marks.ts marks open, for every method, plus a forced 500. It checks that each answer carries Content-Security-Policy, X-Content-Type-Options and Referrer-Policy. No route was missing them, so the test needed no fixes and names no exemptions.

What the test adds:

  • The other methods: each POST, PUT, PATCH and DELETE route marked open (492 of them) is sent an empty form body three times:

- cross-site; - same-site, signed out; - same-site, signed in.

That covers the origin refusal, the sign-in refusal and the handler's own answer (303s, 422s, 404s, 2xx). Paths are filled the same way the existing GET walk fills them.

  • Proof the signed-in pass reaches the handlers: it signs in with a session on pages and with a token on the API. The API doesn't count a session cookie as a token, and the earlier passes use up the address's 600-per-minute allowance for callers without a token, so a cookie alone answered 203 routes with 429 instead of running them. The test now fails if any 429 appears or if fewer than six answers are 2xx.
  • GET coverage: a check that every open GET pattern is in the existing GET walk, which already covered all GET routes and an unknown path.
  • Forced 500: a 500 under /explore and /api/v1/user, caused by making the database throw. It must carry the three headers and must not mention the fault.

To try it: bun test test/security-floor-routes.test.ts (8 tests, about 8 s). To check it catches a gap, make securityFloor skip Referrer-Policy on 303 and 422 answers: five of the walks go red.

The gate and bun x tsc --noEmit are clean, and the 80 test files scripts/fast.ts names pass (1364 tests). Nothing under src/ changed. I didn't keep a plan on the issue because the keep_plan tool wasn't available in this run.

🤖 Generated with Claude Code

Closes #12313

4 earlier comments
commented

test/security-floor-routes.test.ts now walks every route that src/http/route_marks.ts marks open, for every method, plus a forced 500. It checks that each answer carries Content-Security-Policy, X-Content-Type-Options and Referrer-Policy. No route was missing them, so the test needed no fixes and names no exemptions.

What the test adds:

  • The other methods: each POST, PUT, PATCH and DELETE route marked open (492 of them) is sent an empty form body three times:

- cross-site; - same-site, signed out; - same-site, signed in.

That covers the origin refusal, the sign-in refusal and the handler's own answer (303s, 422s, 404s, 2xx). Paths are filled the same way the existing GET walk fills them.

  • Proof the signed-in pass reaches the handlers: it signs in with a session on pages and with a token on the API. The API doesn't count a session cookie as a token, and the earlier passes use up the address's 600-per-minute allowance for callers without a token, so a cookie alone answered 203 routes with 429 instead of running them. The test now fails if any 429 appears or if fewer than six answers are 2xx.
  • GET coverage: a check that every open GET pattern is in the existing GET walk, which already covered all GET routes and an unknown path.
  • Forced 500: a 500 under /explore and /api/v1/user, caused by making the database throw. It must carry the three headers and must not mention the fault.

To try it: bun test test/security-floor-routes.test.ts (8 tests, about 8 s). To check it catches a gap, make securityFloor skip Referrer-Policy on 303 and 422 answers: five of the walks go red.

The gate and bun x tsc --noEmit are clean, and the 80 test files scripts/fast.ts names pass (1364 tests). Nothing under src/ changed. I didn't keep a plan on the issue because the keep_plan tool wasn't available in this run.

🤖 Generated with Claude Code

commented

Every route that route_marks.ts marks open is now checked for the security floor (securityFloor in src/http/respond.ts). The check covers every method and a forced 500. The test is test/security-floor-routes.test.ts, and it builds its route list from the router's own route table, so a new open route is covered without anyone adding it to the test.

  • GET routes: each one, plus an unknown path, is fetched signed out and signed in, and the floor headers must be on every response.
  • Route patterns the seed data can't fill: these are listed in UNSEEDED, each with a reason. The test fails if the computed list and the named list differ.
  • Other methods: each open route that isn't GET gets an empty form body. Whatever the handler sends back (a refusal, a validation error or a redirect) must carry the floor.
  • Forced 500: the error response must carry the floor too.

This push only rewraps one doc comment at line 266 of the test, which the gate refused because it ran past 100 columns. bun scripts/gate.ts, bun x tsc --noEmit and bun test test/security-floor-routes.test.ts (8 pass) are green locally.

To try it: bun test test/security-floor-routes.test.ts.

🤖 Generated with Claude Code

commented

This pull tests, in test/security-floor-routes.test.ts, that every route marked open in src/http/route_marks.ts answers with the security floor (securityFloor: Content-Security-Policy, X-Content-Type-Options, Referrer-Policy), whatever its method. It also brings in current main and fixes the two problems that turned the last check red.

Routes with methods other than GET. Each route marked open is called with an empty form body three times:

  • Cross-site: the origin check refuses it.
  • Same-site, signed out: the sign-in check refuses it.
  • Same-site, signed in: a session on the pages and a token on the API. Without the token, the API would answer 429 instead of reaching the handlers.

The signed-in pass also asserts that no 429 came back and that more than five handlers answered 2xx, so the walk is known to reach the handlers. Separate checks confirm the walk includes POST, PUT, PATCH and DELETE, and that every open GET route is already in the GET walk. Finally, a forced database failure under /explore and /api/v1/user must still return a 500 with the floor and without the internal error text.

Fixes for the red check:

  • Spawn lists. On main, src/git/own_refs.ts starts git processes but was not on the lists in test/spawn-sites.test.ts and test/spawn-pipes.test.ts. Both now name it with a reason: git with fixed flags, ids checked by the HEX/FULL patterns before they reach the command line, lists of ids sent on stdin, stdout read under a kill timer, stderr ignored.
  • Test budget. The signed-in GET walk took 2.12 s against the 2 s per-test budget. No single route is slow: one at a time, the 539 requests add up to 3.3 s. So the GET table is now walked in three parts, each signed out and signed in: repository pages (/:owner/:name…), other pages, and the API. Every request is still made. A new test checks that the three parts together cover every GET pattern and that each holds more than 50. Locally the slowest test is now 1.08 s. I did not add the file to SLOW in scripts/gate.ts.

To try it: bun test test/security-floor-routes.test.ts test/spawn-sites.test.ts test/spawn-pipes.test.ts. All three pass locally, as do bun scripts/gate.ts and bun x tsc --noEmit. The full suite was not run here; the forge runs it on the push.

🤖 Generated with Claude Code

commented

I merged main into this branch again; the only conflict was the src/git/own_refs.ts entry in test/spawn-pipes.test.ts. Both sides had added that entry with the same four stdout sites. I kept main's wording of the reason, because it describes the version of own_refs.ts that the merged tree now has: three text reads alongside the exit, a rev-list walk read chunk by chunk, and a kill timer on any walk that hangs.

This pull is unchanged: every open GET route is checked for the securityFloor headers, and the route table is walked in three parts so no single test goes over the 2 s budget. src/git/own_refs.ts stays listed among the spawn sites and pipes.

What I ran after the merge:

  • bun scripts/gate.ts, bun x tsc --noEmit and test/spawn-pipes.test.ts all pass.
  • test/security-floor-routes.test.ts and test/spawn-sites.test.ts together pass 18 of 18.

🤖 Generated with Claude Code

commented

This pull checks that every open GET route answers with the securityFloor headers. It walks the route table in three parts so no single floor test goes over the 2 s budget, and it lists src/git/own_refs.ts among the spawn sites and pipes.

This update merges main into the branch again. The only conflict was the src/git/own_refs.ts entry in test/spawn-sites.test.ts, which both sides had added. main's wording is kept, because it describes the own_refs.ts that the merged tree has: object ids travel on stdin, and a short hex id reaches argv only behind refs/…/ or --disambiguate=.

After the merge, bun scripts/gate.ts and bun x tsc --noEmit pass. test/security-floor-routes.test.ts, test/spawn-sites.test.ts and test/spawn-pipes.test.ts pass 19 of 19.

🤖 Generated with Claude Code

Approved

Test-only change, nothing under src/ touched. It walks every open non-GET route three ways (cross-site, same-site signed out, same-site signed in), checks that every open GET is in the GET walk, and forces a 500 on an open page and an open API route. It asserts the signed-in pass reached handlers (no 429, some 2xx), so it is not vacuous. The GET table is split into three parts to stay under the per-test budget, with a coverage check that the parts together hold every pattern. No exemptions were needed. The preview was not exercised because there is no served behaviour change. Two next things: (1) add a mutation-testing note or a test that removing the floor on a 422/303 path turns a walk red, so the guard is proven in the suite rather than only in the pull text; (2) keep an eye on the file time, since 492 mutations x3 passes sits near the budget and may need a SLOW entry in scripts/gate.ts later.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs