# Example decision: set the content security policy per request

What the Record a decision recipe produced from PR #72: why the CSP moved from next.config into proxy.ts, the options weighed, and what it commits us to.

> [!info]
> **Example output.** Claude Code ran the *Record a decision* recipe on 30 September 2026 against pull request #72 and its commit message. The decision was already made and merged, so it is recorded as accepted.

<!-- onecell:block {"date":"2026-09-29","type":"decision","title":"Set the content security policy per request, in proxy.ts","status":"accepted","context":"The app's CSP was set in next.config's headers(), which Next evaluates when the image is built. Production settings live in the box's .env and change without a rebuild. When SANDBOX_ORIGIN was added after the build, the CSP never included it, and browsers refused the sandbox frame.","decision":"proxy.ts sets Content-Security-Policy and its report-only twin on every response, reading the environment at request time. The sandbox bootstrap is skipped because it sends its own policy. The CSP is removed from next.config.","consequences":"Settings that shape the CSP take effect on restart, with no rebuild. The policy is computed on every request, which is cheap. Anything else in next.config headers() is still fixed at build time, so new runtime settings must not go there."} -->
**Decision: Set the content security policy per request, in proxy.ts** (accepted, 2026-09-29, by blondon)
**Context:** The app's CSP was set in next.config's headers(), which Next evaluates when the image is built. Production settings live in the box's .env and change without a rebuild. When SANDBOX\_ORIGIN was added after the build, the CSP never included it, and browsers refused the sandbox frame.
**Decision:** proxy.ts sets Content-Security-Policy and its report-only twin on every response, reading the environment at request time. The sandbox bootstrap is skipped because it sends its own policy. The CSP is removed from next.config.
**Consequences:** Settings that shape the CSP take effect on restart, with no rebuild. The policy is computed on every request, which is cheap. Anything else in next.config headers() is still fixed at build time, so new runtime settings must not go there.

## Options considered

The pull request describes only the option chosen. The agent reconstructed the other two from the problem it solved, and marked them so.

| Option | For | Against |
| --- | --- | --- |
| Keep it in next.config and rebuild when settings change (reconstructed) | No code change | Every settings change needs a new image; easy to forget, as the launch showed |
| Pass SANDBOX\_ORIGIN as a build argument (reconstructed) | Small change | Moves a runtime setting into the build, the same trap in a new place |
| Set it per request in proxy.ts (chosen) | Always matches the running settings; one place for all response policy | Runs on every request; the bootstrap needs an exception |

## Where it is enforced

A test sets `SANDBOX_ORIGIN` after start-up and checks that the next response's CSP follows it, and that the bootstrap gets none of it. See [pull request #72](https://github.com/bradlondon/onecell/pull/72) and the [postmortem of the sandbox launch](https://onecell.io/cells/recipes/9362cc0f-6959-49d4-a2f5-34b5fe56eae1), where the problem was found.
