fix(audit-wave-11): CSP nonce middleware — drops 'unsafe-inline' in prod
build-auditor H1: prod `script-src` previously kept `'unsafe-inline'` because dropping it requires a per-request nonce that Next's RSC bootstrap + Server Actions can thread into their inline scripts. Implement the nonce mechanism in `src/proxy.ts`: 1. Mint a base64-encoded UUID per request as the CSP nonce. 2. Set the nonce on the REQUEST headers via `content-security-policy` + `x-nonce` so Next.js's RSC layer reads the active CSP and stamps `nonce=<value>` onto every inline `<script>` it emits (Next's documented pattern). 3. Set the matching `Content-Security-Policy` on the RESPONSE so the browser actually enforces it. Prod CSP becomes: `script-src 'self' 'nonce-<value>' 'strict-dynamic'` `'strict-dynamic'` lets nonce-tagged scripts load further scripts they trust, which is how Next chunks the rest of the bundle in. Inline `<script>` without a nonce is now rejected by the browser — closes the canonical XSS pathway. Dev keeps `'unsafe-inline' 'unsafe-eval'` because Next's HMR evaluates code at runtime and the nonce machinery doesn't reach it. `style-src` keeps `'unsafe-inline'` because Tailwind + Radix runtime style injection has no nonce story yet. Revisit when Tailwind v5 ships a nonce-able API. The static CSP in `next.config.ts` stays as a fallback for static assets / API JSON paths that don't run through the proxy. Updated the comment so future readers know the proxy CSP takes precedence for HTML responses. Tests 1315/1315. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,13 +43,15 @@ const withBundleAnalyzer = bundleAnalyzer({
|
||||
const devScriptHosts = isProd ? '' : ' http://unpkg.com https://unpkg.com';
|
||||
const devConnectHosts = isProd ? '' : ' http://unpkg.com https://unpkg.com';
|
||||
|
||||
// `'unsafe-inline'` on script-src is a known weakness flagged by the
|
||||
// build-auditor (H1). Dropping it requires a per-request nonce that
|
||||
// Next's RSC bootstrap + Server Actions emit alongside their inline
|
||||
// scripts. Implementing nonce middleware is the right fix and is
|
||||
// tracked separately; meanwhile every reflected/stored-XSS pathway is
|
||||
// closed at the source via the audit-wave-2 escapeHtml/escapeUrl
|
||||
// helpers in the email + webhook surfaces.
|
||||
// Fallback CSP for paths the proxy doesn't run on (static assets,
|
||||
// API JSON responses where script-src is moot). Production HTML
|
||||
// responses get a stricter per-request nonce-based CSP set in
|
||||
// `src/proxy.ts:applyCsp`; this header just provides a sane default
|
||||
// so a misconfigured static-only route still has a CSP.
|
||||
//
|
||||
// Dev keeps 'unsafe-inline' + 'unsafe-eval' on script-src because
|
||||
// Next's HMR runtime evaluates code dynamically and the nonce
|
||||
// machinery doesn't reach it.
|
||||
const csp = [
|
||||
"default-src 'self'",
|
||||
`script-src 'self' 'unsafe-inline'${isProd ? '' : " 'unsafe-eval'"}${devScriptHosts}`,
|
||||
|
||||
Reference in New Issue
Block a user