From 24cd55c09fc3676fd8eafcf66c174114fbc18362 Mon Sep 17 00:00:00 2001 From: Nikhil Bhargava <993682+nbhargava@users.noreply.github.com> Date: Sun, 20 Aug 2023 06:55:02 -0700 Subject: [PATCH] Adds nonce to preinit scripts (#54059) Fixes #54055. A bug recently introduced in https://github.com/vercel/next.js/pull/53705 made it so that we were now preinitializing some of our scripts slightly better, but in doing so, we failed to pass in a nonce. This broke nonce-based CSP usage. The fix was to add the `nonce` to our `ReactDOM.preinit` calls. Manual testing shows that this change fixes the error and the nonce is now passed in as expected. Co-authored-by: Dan Ott <360261+danieltott@users.noreply.github.com> --- .../next/src/server/app-render/app-render.tsx | 6 ++-- .../server/app-render/required-scripts.tsx | 5 +++- test/e2e/app-dir/app/index.test.ts | 30 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index eb734749d4847..4567619a07192 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -1438,7 +1438,8 @@ export async function renderToHTMLOrFlight( buildManifest, assetPrefix, subresourceIntegrityManifest, - getAssetQueryString(true) + getAssetQueryString(true), + nonce ) const ServerComponentsRenderer = createServerComponentsRenderer( tree, @@ -1619,7 +1620,8 @@ export async function renderToHTMLOrFlight( buildManifest, assetPrefix, subresourceIntegrityManifest, - getAssetQueryString(false) + getAssetQueryString(false), + nonce ) const ErrorPage = createServerComponentRenderer( diff --git a/packages/next/src/server/app-render/required-scripts.tsx b/packages/next/src/server/app-render/required-scripts.tsx index 8faa76b060a33..d8da09d9c016c 100644 --- a/packages/next/src/server/app-render/required-scripts.tsx +++ b/packages/next/src/server/app-render/required-scripts.tsx @@ -6,7 +6,8 @@ export function getRequiredScripts( buildManifest: BuildManifest, assetPrefix: string, SRIManifest: undefined | Record, - qs: string + qs: string, + nonce: string | undefined ): [() => void, string | { src: string; integrity: string }] { let preinitScripts: () => void let preinitScriptCommands: string[] = [] @@ -33,6 +34,7 @@ export function getRequiredScripts( ReactDOM.preinit(preinitScriptCommands[i], { as: 'script', integrity: preinitScriptCommands[i + 1], + nonce, }) } } @@ -47,6 +49,7 @@ export function getRequiredScripts( for (let i = 0; i < preinitScriptCommands.length; i++) { ReactDOM.preinit(preinitScriptCommands[i], { as: 'script', + nonce, }) } } diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts index 735656550c9f7..c0b21cbe03ab9 100644 --- a/test/e2e/app-dir/app/index.test.ts +++ b/test/e2e/app-dir/app/index.test.ts @@ -1353,6 +1353,36 @@ createNextDescribe( } }) + 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==' + + // 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) + + // Find all the script tags without src attributes. + const elements = $('script[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 an integrity attribute on scripts', async () => { const $ = await next.render$('/dashboard')