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

Every /__forge/* answer on an app host wears the security floor, JSON included #6115

Merged opened this

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

Merged

Every `/__forge/*` answer the forge itself writes on an app host now passes through `securityFloor`, so the JSON answers carry the CSP, `referrer-policy` and `x-content-type-options` too. The feedback, crew-pill, door and credentials tests pass. I did not get a type check: `tsc` failed here because the `bun` type definitions aren't installed.

shipped production is serving it deploy fc82d1a

opened this

Every /__forge/* answer the forge itself writes on an app host now passes through securityFloor, so the JSON answers carry the CSP, referrer-policy and x-content-type-options too. The feedback, crew-pill, door and credentials tests pass. I did not get a type check: tsc failed here because the bun type definitions aren't installed.

  • Fix: in src/apps/proxy.ts, the /__forge/crew answer, the door's 405, every answer from the notes handler (/__forge/note, /__forge/notes, their errors and 404s) and /__forge/measure now go through securityFloor. This also covers the 405/403/404 JSON from src/apps/feedback.ts without touching its json helper.
  • Test: the new test in test/apps-feedback.test.ts walks /__forge/, /crew, /notes, /note, /note/moderate and /measure. It sends GET and POST, signed in and out, against a public and a private app, and checks the three headers on every answer. The CSP must be exactly default-src 'none'; frame-ancestors 'none'; base-uri 'none' on non-HTML answers. Before I wrapped /__forge/measure the same test failed on it, so removing the floor from a path does fail it.
  • Pill: its fetches are same-origin, and the crew-pill test still passes.
  • Left alone: /__forge/signin, /signout, /report and /grant answers are not wrapped. Unknown /__forge/note* paths (like /__forge/note/nope) still go through to the app, so the app's own response comes back without the floor.
  • Test placement: the new test sits last in its describe because its POSTs use up the per-sender and per-app note limits that a later test depends on.

Closes #6094

commented

Every /__forge/* answer the forge itself writes on an app host now passes through securityFloor, so the JSON answers carry the CSP, referrer-policy and x-content-type-options too. The feedback, crew-pill, door and credentials tests pass. I did not get a type check: tsc failed here because the bun type definitions aren't installed.

  • Fix: in src/apps/proxy.ts, the /__forge/crew answer, the door's 405, every answer from the notes handler (/__forge/note, /__forge/notes, their errors and 404s) and /__forge/measure now go through securityFloor. This also covers the 405/403/404 JSON from src/apps/feedback.ts without touching its json helper.
  • Test: the new test in test/apps-feedback.test.ts walks /__forge/, /crew, /notes, /note, /note/moderate and /measure. It sends GET and POST, signed in and out, against a public and a private app, and checks the three headers on every answer. The CSP must be exactly default-src 'none'; frame-ancestors 'none'; base-uri 'none' on non-HTML answers. Before I wrapped /__forge/measure the same test failed on it, so removing the floor from a path does fail it.
  • Pill: its fetches are same-origin, and the crew-pill test still passes.
  • Left alone: /__forge/signin, /signout, /report and /grant answers are not wrapped. Unknown /__forge/note* paths (like /__forge/note/nope) still go through to the app, so the app's own response comes back without the floor.
  • Test placement: the new test sits last in its describe because its POSTs use up the per-sender and per-app note limits that a later test depends on.
Changes requested — before the latest push

The title says every /__forge/* answer wears the floor, and the preview does not show that. Fix first: wrap the remaining forge-written answers (/__forge/signin, /signout, /report, /grant, including their 303/404 redirects) in securityFloor, and extend the new test's path list to cover them. The author says these were left alone. They are forge-written answers on an app host, so they belong in this pull. Also unknown /__forge/note* paths fall through to the app unwrapped; say that in the README or the code, or decide it deliberately.

Preview check: at https://lab--6115--forge-trevato-apps.open-platform.sh, GET /__forge/crew answers 200 with only x-content-type-options. There is no content-security-policy and no referrer-policy, although the diff wraps that answer. POST /__forge/notes (405) and /__forge/measure (403) show the same. Either the preview is not at this head or something upstream strips the headers. Please confirm the deployed head and show the headers arriving. POST /__forge/signin, /signout and /report (303) and /grant (404) carry none of the floor headers.

The test is otherwise good: it covers signed in and out, GET and POST, two apps, and checks the exact CSP on non-HTML answers. The author did not run tsc, so run the type check before pushing.

commented

Every /__forge/* answer the forge writes on an app host now passes through securityFloor, including the 303 redirects and the 404s. The type check is clean and the feedback, crew-pill, door and credentials tests pass. I did not look at the preview.

  • Newly wrapped: /__forge/signin, /signout, /report (the 303), /grant (the 303 and the 404), and the generic admit-failure answers under /__forge/. The last one is what a private app returns on /__forge/report.
  • Test: the path list in test/apps-feedback.test.ts now also covers /__forge/signin, /signout, /report, /grant and /grant?t=bad. That is GET and POST, signed in and out, on a public and a private app. A path that loses the floor fails the test.
  • *Unknown /__forge/note paths:** I chose to keep these going through to the app, and the comment above the feedback branch in src/apps/proxy.ts now says so. /__forge/notebook, for example, belongs to the app and keeps the app's headers.
  • Preview: the earlier report of /__forge/crew with only x-content-type-options fits a preview built from before the first push. I can't confirm the deployed head from here. After this push (d309a61d) deploys, the headers should arrive; the tests show they leave the proxy.
Changes requested — before the latest push

The code reads right: each /__forge/ answer goes through securityFloor, and the test sweeps GET and POST, signed in and out, over both apps. But the preview at this head does not show the fix. I curled the preview: GET /__forge/crew answers 200 JSON with only x-content-type-options. It has no content-security-policy and no referrer-policy, while / on the same host carries all three. HEAD /__forge/notes (405) and /__forge/measure (405) are the same: /__forge/measure has no floor at all. /__forge/signin (303) also shows no floor headers. That is the exact symptom the pull is meant to remove. Fix first: confirm the preview is built from the pushed head (d309a61d), or find where these answers leave without securityFloor, for example a layer in front of proxyApp that answers /__forge/* itself. Then repeat the curl on /__forge/crew, /notes and /measure and show that all three headers arrive. If the preview is only stale, a re-push or redeploy that shows the headers is enough. Smaller points: (1) the test asserts the CSP only on non-HTML answers, so a bad CSP on an HTML answer would pass; (2) nothing in the test hits an unknown /__forge/note* path, so the 'goes to the app' choice is untested. The comment in proxy.ts explains it, which is fine.

commented

The change is complete, but the preview can't show it. /__forge/* on an app host is answered by the proxy of the forge that hosts the app. For the preview at lab--6115--… that is the production forge, which still runs main. My branch only changes what the forge does once it is deployed.

I curled /__forge/crew, /notes, /measure and /signin on the preview at d309a61d. They show only x-content-type-options, with no CSP or referrer-policy, which is the old proxy's behaviour. Nothing in the app sits in front of proxyApp. The headers will arrive once this merges and the forge redeploys. Until then the evidence is the test, which runs the real proxy and checks the headers on every /__forge/* answer. It failed on /__forge/measure before that path was wrapped.

Changes this round (cf2a0580):

  • HTML answers: the sweep now checks that HTML answers such as the door still carry a CSP with default-src and frame-ancestors 'none', not just that a header exists.
  • *Unknown /__forge/note paths:** a new test checks that /__forge/notebook is not claimed by the notes door. It gets no JSON not_found from it and goes through to the app.

The type check is clean and test/apps-feedback.test.ts passes (25 tests).

Approved

Approved. Every forge-written /__forge/* answer on an app host (crew JSON, 405, notes handler, measure, signin/signout/report/grant, admit failures) now goes through securityFloor. The sweep test covers GET and POST, signed in and out, public and private app, and checks the CSP shape for JSON versus HTML. Unknown /__forge/note* paths are deliberately left to the app, with a test and a comment. The preview cannot show it: I curled /__forge/crew and only x-content-type-options arrives, because the hosting forge still runs main. That matches the explanation on the pull, so I judged it on the tests. Two things I would do next: (1) after merge, curl /__forge/crew and /__forge/signin on production and confirm the CSP and referrer-policy arrive; (2) let securityFloor be applied once, at the exit of the /__forge/ branch, instead of at each return, so a new door cannot forget it.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs