diff --git a/website/API_V2_PLAN.md b/website/API_V2_PLAN.md index 0a1b4a3..03557a3 100644 --- a/website/API_V2_PLAN.md +++ b/website/API_V2_PLAN.md @@ -181,22 +181,64 @@ form-action 'self'; frame-ancestors 'none'; ``` -Delta vs. the policy in `server/src/app.js` today — the whole change is two directives: +Delta vs. the policy in `server/src/app.js` today. This was written as two directives; on +implementation it turned out to be **one**: -- **Add `form-action 'self'`** (currently absent) — blocks an injected `
` - from POSTing credentials off-origin, an exfil path `connect-src` doesn't cover. -- **Tighten `frame-ancestors`** `'self'` → `'none'` — nothing legitimately frames the site. +- ~~**Add `form-action 'self'`** (currently absent)~~ — **it was not absent.** The directives object in + `app.js` does not list it, but the middleware is configured `useDefaults: true`, and helmet's default + set already supplies `form-action 'self'` — so the header served in production has carried it all + along. Verified by capturing the live `Content-Security-Policy` header from the running app rather + than reading the config, which is how the plan got this wrong. **No behavioural change here.** It is + now written out explicitly in `config/csp.js` anyway: a security directive should not depend on a + third-party library's defaults surviving its next major version. +- **Tighten `frame-ancestors`** `'self'` → `'none'` — nothing legitimately frames the site. **This is + the entire behavioural delta of the phase.** - Unchanged: `default-src`, `script-src`, `connect-src`, `object-src 'none'`, `base-uri 'self'`, and `img-src … https:` (external `BRAND_*` logo/hero and `` in sanitized wiki/news bodies rely on `https:`). +The soak is still worth running for that one directive, and arguably it is the directive that most +needs one: a `frame-ancestors` report is generated by the browser of *whoever framed the site*, so it +is the only way to discover that something legitimately embeds us before the enforcing policy breaks +it. Nothing else can tell us that. + **Rollout:** ship via `Content-Security-Policy-Report-Only` with `report-to` for one release, watch for violations, then flip to enforce. Before trusting `script-src 'self'`: Vite's build injects an inline modulepreload-polyfill `