Skip to content

Commit

Permalink
Adds nonce to preinit scripts (#54059)
Browse files Browse the repository at this point in the history
Fixes #54055.

A bug recently introduced in #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>
  • Loading branch information
nbhargava and danieltott committed Aug 20, 2023
1 parent f1c286f commit 24cd55c
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 3 deletions.
6 changes: 4 additions & 2 deletions packages/next/src/server/app-render/app-render.tsx
Expand Up @@ -1438,7 +1438,8 @@ export async function renderToHTMLOrFlight(
buildManifest,
assetPrefix,
subresourceIntegrityManifest,
getAssetQueryString(true)
getAssetQueryString(true),
nonce
)
const ServerComponentsRenderer = createServerComponentsRenderer(
tree,
Expand Down Expand Up @@ -1619,7 +1620,8 @@ export async function renderToHTMLOrFlight(
buildManifest,
assetPrefix,
subresourceIntegrityManifest,
getAssetQueryString(false)
getAssetQueryString(false),
nonce
)

const ErrorPage = createServerComponentRenderer(
Expand Down
5 changes: 4 additions & 1 deletion packages/next/src/server/app-render/required-scripts.tsx
Expand Up @@ -6,7 +6,8 @@ export function getRequiredScripts(
buildManifest: BuildManifest,
assetPrefix: string,
SRIManifest: undefined | Record<string, string>,
qs: string
qs: string,
nonce: string | undefined
): [() => void, string | { src: string; integrity: string }] {
let preinitScripts: () => void
let preinitScriptCommands: string[] = []
Expand All @@ -33,6 +34,7 @@ export function getRequiredScripts(
ReactDOM.preinit(preinitScriptCommands[i], {
as: 'script',
integrity: preinitScriptCommands[i + 1],
nonce,
})
}
}
Expand All @@ -47,6 +49,7 @@ export function getRequiredScripts(
for (let i = 0; i < preinitScriptCommands.length; i++) {
ReactDOM.preinit(preinitScriptCommands[i], {
as: 'script',
nonce,
})
}
}
Expand Down
30 changes: 30 additions & 0 deletions test/e2e/app-dir/app/index.test.ts
Expand Up @@ -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')

Expand Down

0 comments on commit 24cd55c

Please sign in to comment.