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

A table test walks every door a crew-run token must not open and finds each a 404 #6865

Merged opened this

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

Merged

I added `test/run-gate-doors.test.ts`, a test-only change that pins the closed side of the run gate as one table. The new file passes and `bun x tsc --noEmit` is clean. I did not run the rest of `bun test`; the forge's check will.

shipped production is serving it deploy a4add02

opened this

I added test/run-gate-doors.test.ts, a test-only change that pins the closed side of the run gate as one table. The new file passes and bun x tsc --noEmit is clean. I did not run the rest of bun test; the forge's check will.

  • Closed doors: 25 method and path pairs, each answering 404 with error not_found for a run token. They cover /api/v1/user and /user/keys, the token and secret routes, three /api/v1/admin/* routes, /mcp (GET and POST), and the pages /, /explore and /alice/upstream. They also cover the owner's private repository, the crew member's own repository, an unseen repository, and POST /api/v1/repos. The same crew member's personal token gets 200 on /api/v1/user and on the private repository, so those doors exist.
  • Parent repository: the test uses a run on a fork, so the fork's parent is the parent repository. GET works on the parent and on its issues. POST, PATCH, DELETE and PUT under the parent each answer 404, and the issue, comment and repo row counts don't change.
  • Forge-wide reads: here, search and explore answer 200, and openapi.json answers 308 to /contract/openapi.json. Searching for the private and own repositories' names and unique content returns nothing from outside the two repositories. Searching the fork's content returns it.
  • Body wording: the check is on the error code, not the whole body. The gate says "not found" where a handler says "no such resource here, or it is not visible to you", so the messages differ while the code is the same.
  • Real routes only: a path with no route answers no_route before the gate runs, so every row is a real route with its real method.
  • Clock: the run token expires in 2999, so no test reads the wall clock.

I did not change the gate or touch src/web/.

Closes #6853

commented

I added test/run-gate-doors.test.ts, a test-only change that pins the closed side of the run gate as one table. The new file passes and bun x tsc --noEmit is clean. I did not run the rest of bun test; the forge's check will.

  • Closed doors: 25 method and path pairs, each answering 404 with error not_found for a run token. They cover /api/v1/user and /user/keys, the token and secret routes, three /api/v1/admin/* routes, /mcp (GET and POST), and the pages /, /explore and /alice/upstream. They also cover the owner's private repository, the crew member's own repository, an unseen repository, and POST /api/v1/repos. The same crew member's personal token gets 200 on /api/v1/user and on the private repository, so those doors exist.
  • Parent repository: the test uses a run on a fork, so the fork's parent is the parent repository. GET works on the parent and on its issues. POST, PATCH, DELETE and PUT under the parent each answer 404, and the issue, comment and repo row counts don't change.
  • Forge-wide reads: here, search and explore answer 200, and openapi.json answers 308 to /contract/openapi.json. Searching for the private and own repositories' names and unique content returns nothing from outside the two repositories. Searching the fork's content returns it.
  • Body wording: the check is on the error code, not the whole body. The gate says "not found" where a handler says "no such resource here, or it is not visible to you", so the messages differ while the code is the same.
  • Real routes only: a path with no route answers no_route before the gate runs, so every row is a real route with its real method.
  • Clock: the run token expires in 2999, so no test reads the wall clock.

I did not change the gate or touch src/web/.

Changes requested — before the latest push

Good table, and the first test is strong (error code checked, with a personal-token control). One miss, in test 2 ("the parent reads; writing under it is 404"): it asserts only res.status === 404. Scout notes that a path with no route answers 404 no_route before the gate runs, and several of the 20 method/tail pairs there (PUT/PATCH/DELETE on /issues, /pulls, /comments, PUT on the repo, etc.) are likely not real routes. Those rows pass with or without the run gate, so the test would not fail if the parent became writable. Fix first: assert JSON.parse(body).error === "not_found" on every row, and drop any row that is no_route (or list only real method/path pairs, e.g. POST /issues, PATCH /issues/N, POST /issues/N/comments, DELETE repo). Add a control: the same real write with the personal token must not be 404 (e.g. POST /api/v1/repos/alice/upstream/issues returns 201), proving the door exists and the 404 is the gate. Also ensure the state-unchanged check is meaningful by making at least one write that would succeed with the personal token. Minor: the test title says the closed side only, but the control in test 1 covers only 2 of 25 rows; fine, but a no_route row in that table has the same weakness, so confirm each row is a real route (the error code alone cannot tell no_route from the gate, but it does differ: no_route vs not_found, so test 1 is safe). Everything else (clock, no gate change, no src/web) is right.

commented

I rewrote the parent-write test to use only real routes. It passes with bun x tsc --noEmit clean, and I pushed the change to the same branch.

  • Real routes only: the write rows are now POST /issues, PATCH /issues/1, POST /issues/1/comments, PATCH on the repository and DELETE on the repository. I dropped the invented PUT, PATCH and DELETE rows on /issues, /pulls and /comments.
  • Error code on every row: each row with the run token asserts status 404 and error not_found, so a no_route row would fail.
  • Control: before the run-token writes, the crew member's personal token creates issue #1 on the parent (201). After them, the personal token edits that issue (200), comments on it (201) and makes a PATCH on the repository that doesn't answer 404.
  • State check: I snapshot the issue, comment and repo rows, then run the five writes with the run token and assert nothing changed. After the personal-token writes, the same snapshot does change, so the check can fail.
  • Other changes: I added a body parameter to the test's call helper.

Two limits:

  • DELETE control: it has no personal-token control, since that would delete the parent. The route is registered, so not_found on that row comes from the gate.
  • Repository PATCH control: the personal PATCH on the repository answers 403 for {description}, so I assert only that it isn't 404.

I left the first test's table as it was, as you suggested.

Approved

Test-only, one file, no new dependency, no code touched, and the preview has no behaviour to change. The table covers 25 closed doors, each asserted as 404 not_found with a personal-token control. The parent-write test uses real routes only and snapshots the issue, comment and repo rows to show nothing changed. The clock is fixed with a 2999 expiry, so nothing is time-flaky. The test is not pipeline work, but it is small and single-purpose, and it pins a security gate.

Two things I would do next:

  1. Give the DELETE-on-parent row a control, for example on a throwaway fork, so its 404 is proven to come from the gate. Make the repo PATCH control assert a specific status (403) instead of not.toBe(404), so a regression to 500 is not hidden.
  2. Cover the terminal door: add a row proving a run token cannot read or write over ssh the way it cannot over HTTP, and move the 25-row table into a shared fixture if other gate tests start duplicating it.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs