diff --git a/.claude/references/security-headers.md b/.claude/references/security-headers.md index 990e7d78..5837079d 100644 --- a/.claude/references/security-headers.md +++ b/.claude/references/security-headers.md @@ -23,28 +23,47 @@ is the modern equivalent for the rest. They are *not* both CSP headers — two `Content-Security-Policy` headers on one response are enforced as an intersection, which is a miserable thing to debug. -## The CSP ships report-only +## The CSP enforces -`CSP_ENFORCE=true` switches `src/proxy.ts` from -`Content-Security-Policy-Report-Only` to `Content-Security-Policy`. Only that -exact string enforces; anything else, unset included, stays report-only, so a -typo in the variable cannot take a deploy down. +`src/proxy.ts` sends `Content-Security-Policy`. `CSP_ENFORCE=false` — and only +that exact string — drops back to `Content-Security-Policy-Report-Only`. +Anything else, unset included, enforces, so a typo fails loud (too strict) +rather than silent (no policy). -A nonce CSP is the one security header that can white-screen an app. Before -flipping it on, walk the app with devtools open and confirm a clean console: +It shipped report-only first and was flipped on 2026-09-12 after the policy was +walked through a real browser against a **production build**, clean: -- sign-in (the redirect out to MP and back) -- **sign-out** — the likeliest breakage; see `form-action` below +- sign-in (the redirect out to MP and back), and sign-out to MP's endsession - contact photos on the header avatar, search results, and the detail page -- every Radix surface: dropdown, dialog, select, tooltip, vaul drawer -- contact search, and contact-log create/edit +- every Radix surface: dropdown, dialog, the select inside the dialog +- contact search and the contact-log dialog + +**Report-only is not a substitute for that walk.** In the report-only pass the +console was completely clean; enforcing the same policy immediately blocked a +runtime-injected `