diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 53664456c2119..407b66af3adeb 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -658,7 +658,9 @@ async function renderToHTMLOrFlightImpl( : null // Get the nonce from the incoming request if it has one. - const csp = req.headers['content-security-policy'] + const csp = + req.headers['content-security-policy'] || + req.headers['content-security-policy-report-only'] let nonce: string | undefined if (csp && typeof csp === 'string') { nonce = getScriptNonceFromHeader(csp) diff --git a/test/production/app-dir/subresource-integrity/subresource-integrity.test.ts b/test/production/app-dir/subresource-integrity/subresource-integrity.test.ts index 7664abf7fd129..61cd9be169a1c 100644 --- a/test/production/app-dir/subresource-integrity/subresource-integrity.test.ts +++ b/test/production/app-dir/subresource-integrity/subresource-integrity.test.ts @@ -9,18 +9,24 @@ createNextDescribe( files: path.join(__dirname, 'fixture'), }, ({ next }) => { - function fetchWithPolicy(policy: string | null) { + function fetchWithPolicy(policy: string | null, reportOnly?: boolean) { + const cspKey = reportOnly + ? 'Content-Security-Policy-Report-Only' + : 'Content-Security-Policy' return next.fetch('/dashboard', { headers: policy ? { - 'Content-Security-Policy': policy, + [cspKey]: policy, } : {}, }) } - async function renderWithPolicy(policy: string | null) { - const res = await fetchWithPolicy(policy) + async function renderWithPolicy( + policy: string | null, + reportOnly?: boolean + ) { + const res = await fetchWithPolicy(policy, reportOnly) expect(res.ok).toBe(true) @@ -78,6 +84,36 @@ createNextDescribe( } }) + it('includes a nonce value with inline scripts when Content-Security-Policy-Report-Only header is defined', async () => { + // A random nonce value, base64 encoded. + const nonce = 'cmFuZG9tCg==' + + // Validate all the cases where we could parse the nonce. + const policies = [ + `script-src 'nonce-${nonce}'`, // base case + ` script-src 'nonce-${nonce}' `, // extra space added around sources and directive + `style-src 'self'; script-src 'nonce-${nonce}'`, // extra directives + `script-src 'self' 'nonce-${nonce}' 'nonce-othernonce'`, // extra nonces + `default-src 'nonce-othernonce'; script-src 'nonce-${nonce}';`, // script and then fallback case + `default-src 'nonce-${nonce}'`, // fallback case + ] + + for (const policy of policies) { + const $ = await renderWithPolicy(policy, true) + + // Find all the script tags without src attributes. + const elements = $('script:not([src])') + + // Expect there to be at least 1 script tag without a src attribute. + expect(elements.length).toBeGreaterThan(0) + + // Expect all inline scripts to have the nonce value. + elements.each((i, el) => { + expect(el.attribs['nonce']).toBe(nonce) + }) + } + }) + it('includes a nonce value with bootstrap scripts when Content-Security-Policy header is defined', async () => { // A random nonce value, base64 encoded. const nonce = 'cmFuZG9tCg=='