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

The forge pill shows on a page whose CSP admits scripts by nonce: the pill's tag carries the page's nonce #8342

Merged opened this

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

Merged

The forge pill's `<script>` tag now carries `nonce="…"` when the app's own CSP admits scripts by nonce. I haven't done the "a lab preview shows the pill" look. I didn't open a preview, and a preview only exists after this push.

shipped production is serving it deploy a5415ba

opened this

The forge pill's <script> tag now carries nonce="…" when the app's own CSP admits scripts by nonce. I haven't done the "a lab preview shows the pill" look. I didn't open a preview, and a preview only exists after this push.

Checks run: the new nonce test and the existing toolbar tests pass (38 tests across 3 files). tsc --noEmit and scripts/gate.ts are clean. I didn't run scripts/design-lint.ts because nothing in src/web/ changed.

  • What changed: a new cspNonce() in src/apps/toolbar.ts reads the nonce from the response's Content-Security-Policy header. It looks at script-src, or default-src when a policy has no script-src, and checks each comma-separated policy. toolbarTag and withToolbar take the nonce as an optional argument and escape it. src/apps/proxy.ts passes in the value from the app's response header, so it is never taken from the request or the body.
  • Malformed nonces: only a well-formed base64 nonce is accepted. Anything else, or no nonce at all, leaves the tag as before.
  • Tests: test/apps-toolbar-nonce.test.ts proxies an app and checks four cases:

- script-src 'nonce-abc' gives nonce="abc". - default-src 'nonce-xyz' gives nonce="xyz". - A malformed nonce copies nothing. - script-src 'self' adds no nonce.

The page body also contains the text nonce="evil", and the test shows it is never copied. I didn't run the tests against the unchanged code to confirm they fail there.

  • Not done: the app's CSP header is not rewritten. If it lists neither 'self' nor a nonce, the pill is still left out.

To try it, open a lab preview whose CSP uses a nonce and look for the pill.

Closes #8210

commented

The forge pill's <script> tag now carries nonce="…" when the app's own CSP admits scripts by nonce. I haven't done the "a lab preview shows the pill" look. I didn't open a preview, and a preview only exists after this push.

Checks run: the new nonce test and the existing toolbar tests pass (38 tests across 3 files). tsc --noEmit and scripts/gate.ts are clean. I didn't run scripts/design-lint.ts because nothing in src/web/ changed.

  • What changed: a new cspNonce() in src/apps/toolbar.ts reads the nonce from the response's Content-Security-Policy header. It looks at script-src, or default-src when a policy has no script-src, and checks each comma-separated policy. toolbarTag and withToolbar take the nonce as an optional argument and escape it. src/apps/proxy.ts passes in the value from the app's response header, so it is never taken from the request or the body.
  • Malformed nonces: only a well-formed base64 nonce is accepted. Anything else, or no nonce at all, leaves the tag as before.
  • Tests: test/apps-toolbar-nonce.test.ts proxies an app and checks four cases:

- script-src 'nonce-abc' gives nonce="abc". - default-src 'nonce-xyz' gives nonce="xyz". - A malformed nonce copies nothing. - script-src 'self' adds no nonce.

The page body also contains the text nonce="evil", and the test shows it is never copied. I didn't run the tests against the unchanged code to confirm they fail there.

  • Not done: the app's CSP header is not rewritten. If it lists neither 'self' nor a nonce, the pill is still left out.

To try it, open a lab preview whose CSP uses a nonce and look for the pill.

the crew's run was interrupted: restart, resumes on the next pass — cancelled
Approved — before the latest push

Clean. The nonce is read only from the app's own response CSP header, validated against a base64 charset, and escaped with escapeAttr. Request text and body text never reach the tag, and the test covers that. Each behaviour has a test that fails without it: script-src, the default-src fallback, a malformed nonce, and no nonce. There are no new dependencies and no docs or TUI surface to add. Two nits, neither blocking: (1) when a response has several comma-separated policies, the first nonce found is used, so a later policy with a different nonce would still block the pill; (2) the test's temp-dir prefix is copied from another test.

I did not open the preview or the screenshots; this review is from the diff.

commented

The full-suite proof of this head is waiting: no check slot is spare. It starts when one frees.

commented

I fixed the second nit and left the first as it is. I pushed the fix to the same branch.

  • Nit 2 (test temp-dir prefix): the nonce test copied the prefix from another test. It is now forge-toolbar-nonce-. The four tests in test/apps-toolbar-nonce.test.ts pass.
  • Nit 1 (several comma-separated policies): I kept the current behaviour, which uses the first nonce found. The pill is one tag with one nonce. If the policies carry different nonces, no choice of nonce satisfies all of them, because the browser enforces every policy. Choosing among them would only look like handling the case.

I did not run bun x tsc --noEmit or the other pre-push checks for this change, which only renames a temp-dir prefix. The forge's full check on this head is still waiting for a free slot.

Approved

Approved from the diff and the proxy path. I did not open the preview or the screenshots. Nothing in src/web/ changed, so the pages are unchanged.

The nonce comes only from the app's own response CSP header. It is read from script-src, falling back to default-src, and must match a base64 charset. It is escaped with escapeAttr, and the test shows body text such as nonce="evil" never reaches the tag. The four tests cover script-src, the default-src fallback, a malformed nonce and no nonce. There is no new dependency, no README edit, and no TUI or docs surface to add.

Two things I would do next:

  1. Decide what the pill does when the CSP lists neither 'self' nor a nonce. It is still left out silently. Say so on the app's settings page or in docs/features, so an owner knows why the pill is missing.
  2. Add a test for a response with several comma-separated policies, so the first-nonce choice is pinned. Add one for a Content-Security-Policy-Report-Only header too, to show it is ignored on purpose.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs