This was generated by AI while working the issue.
Split out of #50, which asked for two things — an enforcing CSP and nonce/hash inline scripts so 'unsafe-inline' can be dropped. #50's work plan deferred the second half to "its own issue"; that issue was never filed, so closing #50 on the enforce flip would silently drop it. Filing it now with the trade-off measured rather than asserted.
The gap
Live production script-src is 'self' 'unsafe-inline' https://js.stripe.com. After the #50 enforce flip it stays that way — so the policy will still permit arbitrary inline script execution, which is the primary XSS vector the original issue named. Everything else in #50's policy is meaningfully enforcing; this one directive is not.
Why it wasn't just done
Next's App Router injects an inline hydration bootstrap into every page. Nonces have to be generated per request, which means the pages carrying them cannot be statically prerendered. Measured from the current build output, these routes are prerendered HTML today:
/ (homepage), /about, /contact, /terms, /login, /_not-found
Those are precisely the pages the marketing push targets — the ones that most benefit from static CDN delivery. Nonce-based script-src would force them dynamic.
The only author-written inline script in the tree is the JSON-LD block in src/app/blog/[slug]/page.tsx (one dangerouslySetInnerHTML); a hash covers that one without touching rendering. Everything else inline is framework-generated.
The decision
One question: are we willing to make the public marketing pages dynamic (losing static CDN delivery) in order to run nonce-based script-src, or do we accept 'unsafe-inline' permanently?
Either answer is legitimate — this is a real trade, not an oversight. What matters is that it gets recorded rather than re-raised by the next audit.
- If nonces: middleware generates a per-request nonce,
csp.ts takes it as a parameter, the affected pages opt out of static generation, and the JSON-LD block gets a hash. Non-trivial but bounded.
- If accepted: add a line to CLAUDE.md's Deliberate residuals —
'unsafe-inline' in script-src is a knowing trade against static delivery of the marketing pages — and this issue closes as wontfix.
Sequencing
Not a launch blocker and independent of #50's flip: the flip is strictly an improvement over Report-Only either way. Best taken after the CSP has soaked in enforce mode, so a nonce change isn't debugged against a moving policy.
Split out of #50, which asked for two things — an enforcing CSP and nonce/hash inline scripts so
'unsafe-inline'can be dropped. #50's work plan deferred the second half to "its own issue"; that issue was never filed, so closing #50 on the enforce flip would silently drop it. Filing it now with the trade-off measured rather than asserted.The gap
Live production
script-srcis'self' 'unsafe-inline' https://js.stripe.com. After the #50 enforce flip it stays that way — so the policy will still permit arbitrary inline script execution, which is the primary XSS vector the original issue named. Everything else in #50's policy is meaningfully enforcing; this one directive is not.Why it wasn't just done
Next's App Router injects an inline hydration bootstrap into every page. Nonces have to be generated per request, which means the pages carrying them cannot be statically prerendered. Measured from the current build output, these routes are prerendered HTML today:
/(homepage),/about,/contact,/terms,/login,/_not-foundThose are precisely the pages the marketing push targets — the ones that most benefit from static CDN delivery. Nonce-based
script-srcwould force them dynamic.The only author-written inline script in the tree is the JSON-LD block in
src/app/blog/[slug]/page.tsx(onedangerouslySetInnerHTML); a hash covers that one without touching rendering. Everything else inline is framework-generated.The decision
One question: are we willing to make the public marketing pages dynamic (losing static CDN delivery) in order to run nonce-based
script-src, or do we accept'unsafe-inline'permanently?Either answer is legitimate — this is a real trade, not an oversight. What matters is that it gets recorded rather than re-raised by the next audit.
csp.tstakes it as a parameter, the affected pages opt out of static generation, and the JSON-LD block gets a hash. Non-trivial but bounded.'unsafe-inline'inscript-srcis a knowing trade against static delivery of the marketing pages — and this issue closes aswontfix.Sequencing
Not a launch blocker and independent of #50's flip: the flip is strictly an improvement over Report-Only either way. Best taken after the CSP has soaked in enforce mode, so a nonce change isn't debugged against a moving policy.