-
Notifications
You must be signed in to change notification settings - Fork 47.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add nonce support to bootstrap scripts and external runtime #26738
Conversation
Comparing: 18282f8...d2d41c4 Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: (No significant changes) |
Seems like PROD tests fail? |
@gaearon I see that! Looking into it now... |
Test in —prod actually executes the boostrapScriptContent code
@gaearon all fixed 👍 |
@danieltott what’s the rationale for automatically applying the nonce to all scripts? Also there are hydration implications because attributes are validated in dev. From a. Security angle it seems good that you need to opt your scripts into being nonced then again if you have a supply chain attack taking advantage of this auto nonce then they can also do a lot of other bad things |
const startScriptWithNonce = | ||
nonce === undefined | ||
? startScriptSrc | ||
: stringToPrecomputedChunk( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Precomputed chunks are generally constructed in module scope. The idiomatic way of doing this is to break these chunks up in a static-dynamic-static sandwich
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gnoff I updated the code to sandwich style 🥪
I had originally based my code on the pre-existing nonce setup for inline scripts:
react/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Lines 210 to 215 in b1b1940
const inlineScriptWithNonce = | |
nonce === undefined | |
? startInlineScript | |
: stringToPrecomputedChunk( | |
'<script nonce="' + escapeTextForBrowser(nonce) + '">', | |
); |
If the updated code looks good, should I apply it there as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is different. We use this repeatedly when we emit our fizz runtime scripts. The idea with precomputed chunks is you can do the text encoding once (if configured for the host runtime) so we only gain a benefit if the value is reused. pulling the chunk creation to module scope is our way of reusing the chunk which is why I suggested the sandwhich style for your original use case. Here we are getting reuse by referencing this precomputed chunk on each instruction flush but we can't make it in module scope because we embed the nonce within it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK - if I understand correctly, this is because you return startInlineScript
from createResponseState
and use it other places. Since we don't have to do that with src/module scripts, it's actually more efficient to construct them the way you suggested, since we can precompile the nonce=
chunk once inside the module. 👍
That was a tough one to wrap my head around but I'm pretty sure I have it - thank you for your explanations 🙌
@gnoff Honestly I might have gotten a little overzealous here. I was concerned with server-rendered script tags since that is the important thing in |
@gnoff Updated 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nits but looks good
if (nonce === undefined) { | ||
bootstrapChunks.push( | ||
startModuleSrc, | ||
stringToChunk(escapeTextForBrowser(src)), | ||
); | ||
} else { | ||
bootstrapChunks.push( | ||
startModuleSrcWithNonce, | ||
stringToChunk(escapeTextForBrowser(nonce)), | ||
middleModuleSrcWithNonce, | ||
stringToChunk(escapeTextForBrowser(src)), | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would maybe just do this like the integrity attribute
if (nonce === undefined) { | |
bootstrapChunks.push( | |
startModuleSrc, | |
stringToChunk(escapeTextForBrowser(src)), | |
); | |
} else { | |
bootstrapChunks.push( | |
startModuleSrcWithNonce, | |
stringToChunk(escapeTextForBrowser(nonce)), | |
middleModuleSrcWithNonce, | |
stringToChunk(escapeTextForBrowser(src)), | |
); | |
} | |
bootstrapChunks.push( | |
startModuleSrc, | |
stringToChunk(escapeTextForBrowser(src)) | |
); | |
if (nonce) { | |
bootstrapChunks.push( | |
scriptNonce, | |
stringToChunk(escapeTextForBrowser(nonce)), | |
); | |
} |
if (nonce === undefined) { | ||
bootstrapChunks.push( | ||
startScriptSrc, | ||
stringToChunk(escapeTextForBrowser(src)), | ||
); | ||
} else { | ||
bootstrapChunks.push( | ||
startScriptSrcWithNonce, | ||
stringToChunk(escapeTextForBrowser(nonce)), | ||
middleScriptSrcWithNonce, | ||
stringToChunk(escapeTextForBrowser(src)), | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same recc as for modules
@danieltott I'll merge once you make those last changes. Or I can make them for you if preferred |
@gnoff makes sense! will update here in a minute |
@gnoff this is all set |
@danieltott congrats on landing! Thanks for making the PR |
@gnoff thank you!! And thank you for your time helping me work through it a bit 🙌 Any idea on release scheduling? Not sure how that sort of thing works with the React codebase/package |
It's been a while since we've done a release and we're due for one soon. You'll be able to test our the changes in the |
@gnoff awesome - thanks again! |
in #26738 we added nonce to the ResponseState. Initially it was used in a variety of places but the version that got merged only included it with the external fizz runtime. This PR updates the config for the external fizz runtime so that the nonce is encoded into the script chunks at request creation time. The rationale is that for live-requests, streaming is more likely than not so doing the encoding work at the start is better than during flush. For cases such as SSG where the runtime is not required the extra encoding is tolerable (not a live request). Bots are an interesting case because if you want fastest TTFB you will end up requiring the runtime but if you are withholding until the stream is done you have already sacrificed fastest TTFB and the marginal slowdown of the extraneous encoding is hopefully neglibible I'm writing this so later if we learn that this tradeoff isn't worth it we at least understand why I made the change in the first place.
in #26738 we added nonce to the ResponseState. Initially it was used in a variety of places but the version that got merged only included it with the external fizz runtime. This PR updates the config for the external fizz runtime so that the nonce is encoded into the script chunks at request creation time. The rationale is that for live-requests, streaming is more likely than not so doing the encoding work at the start is better than during flush. For cases such as SSG where the runtime is not required the extra encoding is tolerable (not a live request). Bots are an interesting case because if you want fastest TTFB you will end up requiring the runtime but if you are withholding until the stream is done you have already sacrificed fastest TTFB and the marginal slowdown of the extraneous encoding is hopefully neglibible I'm writing this so later if we learn that this tradeoff isn't worth it we at least understand why I made the change in the first place. DiffTrain build for [8ea96ef](8ea96ef)
Includes the following upstream changes: - [5dd90c562](https://github.com/facebook/react/commits/5dd90c562) Use content hash for react-native builds ([vercel#26734](facebook/react#26734)) (Samuel Susla) - [559e83aeb](https://github.com/facebook/react/commits/559e83aeb) [Fizz] Allow an action provide a custom set of props to use for progressive enhancement ([vercel#26749](facebook/react#26749)) (Sebastian Markbåge) - [67f4fb021](https://github.com/facebook/react/commits/67f4fb021) Allow forms to skip hydration of hidden inputs ([vercel#26735](facebook/react#26735)) (Sebastian Markbåge) - [8ea96ef84](https://github.com/facebook/react/commits/8ea96ef84) [Fizz] Encode external fizz runtime into chunks eagerly ([vercel#26752](facebook/react#26752)) (Josh Story) - [491aec5d6](https://github.com/facebook/react/commits/491aec5d6) Implement experimental_useOptimisticState ([vercel#26740](facebook/react#26740)) (Andrew Clark) - [9545e4810](https://github.com/facebook/react/commits/9545e4810) Add nonce support to bootstrap scripts and external runtime ([vercel#26738](facebook/react#26738)) (Dan Ott) - [86b0e9199](https://github.com/facebook/react/commits/86b0e9199) Gate DevTools test to fix CI ([#26742](facebook/react#26742)) (Andrew Clark) - [b12bea62d](https://github.com/facebook/react/commits/b12bea62d) Preinits should support a nonce option ([#26744](facebook/react#26744)) (Josh Story) - [efbd68511](https://github.com/facebook/react/commits/efbd68511) Remove unused `initialStatus` parameter from `useHostTransitionStatus` ([vercel#26743](facebook/react#26743)) (Sebastian Silbermann) - [18282f881](https://github.com/facebook/react/commits/18282f881) Fix: Update while suspended fails to interrupt ([vercel#26739](facebook/react#26739)) (Andrew Clark) - [540bab085](https://github.com/facebook/react/commits/540bab085) Implement experimental_useFormStatus ([#26722](facebook/react#26722)) (Andrew Clark)
Includes the following upstream changes: - [5dd90c562](https://github.com/facebook/react/commits/5dd90c562) Use content hash for react-native builds ([vercel#26734](facebook/react#26734)) (Samuel Susla) - [559e83aeb](https://github.com/facebook/react/commits/559e83aeb) [Fizz] Allow an action provide a custom set of props to use for progressive enhancement ([vercel#26749](facebook/react#26749)) (Sebastian Markbåge) - [67f4fb021](https://github.com/facebook/react/commits/67f4fb021) Allow forms to skip hydration of hidden inputs ([vercel#26735](facebook/react#26735)) (Sebastian Markbåge) - [8ea96ef84](https://github.com/facebook/react/commits/8ea96ef84) [Fizz] Encode external fizz runtime into chunks eagerly ([vercel#26752](facebook/react#26752)) (Josh Story) - [491aec5d6](https://github.com/facebook/react/commits/491aec5d6) Implement experimental_useOptimisticState ([vercel#26740](facebook/react#26740)) (Andrew Clark) - [9545e4810](https://github.com/facebook/react/commits/9545e4810) Add nonce support to bootstrap scripts and external runtime ([vercel#26738](facebook/react#26738)) (Dan Ott) - [86b0e9199](https://github.com/facebook/react/commits/86b0e9199) Gate DevTools test to fix CI ([#26742](facebook/react#26742)) (Andrew Clark) - [b12bea62d](https://github.com/facebook/react/commits/b12bea62d) Preinits should support a nonce option ([#26744](facebook/react#26744)) (Josh Story) - [efbd68511](https://github.com/facebook/react/commits/efbd68511) Remove unused `initialStatus` parameter from `useHostTransitionStatus` ([vercel#26743](facebook/react#26743)) (Sebastian Silbermann) - [18282f881](https://github.com/facebook/react/commits/18282f881) Fix: Update while suspended fails to interrupt ([vercel#26739](facebook/react#26739)) (Andrew Clark) - [540bab085](https://github.com/facebook/react/commits/540bab085) Implement experimental_useFormStatus ([#26722](facebook/react#26722)) (Andrew Clark)
Includes the following upstream changes: - [b7972822b](https://github.com/facebook/react/commits/b7972822b) useOptimisticState -> useOptimistic ([#26772](facebook/react#26772)) (Andrew Clark) - [388686f29](https://github.com/facebook/react/commits/388686f29) Add "canary" to list of allowed npm dist tags ([#26767](facebook/react#26767)) (Andrew Clark) - [8a25302c6](https://github.com/facebook/react/commits/8a25302c6) fix[dynamic-scripts-injection]: unregister content scripts before registration ([#26765](facebook/react#26765)) (Ruslan Lesiutin) - [2c2476834](https://github.com/facebook/react/commits/2c2476834) Rename "next" prerelease channel to "canary" ([#26761](facebook/react#26761)) (Andrew Clark) - [fa4314841](https://github.com/facebook/react/commits/fa4314841) Remove deprecated workflow key from Circle config ([#26762](facebook/react#26762)) (Andrew Clark) - [5dd90c562](https://github.com/facebook/react/commits/5dd90c562) Use content hash for react-native builds ([#26734](facebook/react#26734)) (Samuel Susla) - [559e83aeb](https://github.com/facebook/react/commits/559e83aeb) [Fizz] Allow an action provide a custom set of props to use for progressive enhancement ([#26749](facebook/react#26749)) (Sebastian Markbåge) - [67f4fb021](https://github.com/facebook/react/commits/67f4fb021) Allow forms to skip hydration of hidden inputs ([#26735](facebook/react#26735)) (Sebastian Markbåge) - [8ea96ef84](https://github.com/facebook/react/commits/8ea96ef84) [Fizz] Encode external fizz runtime into chunks eagerly ([#26752](facebook/react#26752)) (Josh Story) - [491aec5d6](https://github.com/facebook/react/commits/491aec5d6) Implement experimental_useOptimisticState ([#26740](facebook/react#26740)) (Andrew Clark) - [9545e4810](https://github.com/facebook/react/commits/9545e4810) Add nonce support to bootstrap scripts and external runtime ([#26738](facebook/react#26738)) (Dan Ott) - [86b0e9199](https://github.com/facebook/react/commits/86b0e9199) Gate DevTools test to fix CI ([#26742](facebook/react#26742)) (Andrew Clark) - [b12bea62d](https://github.com/facebook/react/commits/b12bea62d) Preinits should support a nonce option ([#26744](facebook/react#26744)) (Josh Story) - [efbd68511](https://github.com/facebook/react/commits/efbd68511) Remove unused `initialStatus` parameter from `useHostTransitionStatus` ([#26743](facebook/react#26743)) (Sebastian Silbermann) - [18282f881](https://github.com/facebook/react/commits/18282f881) Fix: Update while suspended fails to interrupt ([#26739](facebook/react#26739)) (Andrew Clark) - [540bab085](https://github.com/facebook/react/commits/540bab085) Implement experimental_useFormStatus ([#26722](facebook/react#26722)) (Andrew Clark) ---------
Summary: in facebook/react#26738 we added nonce to the ResponseState. Initially it was used in a variety of places but the version that got merged only included it with the external fizz runtime. This PR updates the config for the external fizz runtime so that the nonce is encoded into the script chunks at request creation time. The rationale is that for live-requests, streaming is more likely than not so doing the encoding work at the start is better than during flush. For cases such as SSG where the runtime is not required the extra encoding is tolerable (not a live request). Bots are an interesting case because if you want fastest TTFB you will end up requiring the runtime but if you are withholding until the stream is done you have already sacrificed fastest TTFB and the marginal slowdown of the extraneous encoding is hopefully neglibible I'm writing this so later if we learn that this tradeoff isn't worth it we at least understand why I made the change in the first place. DiffTrain build for commit facebook/react@8ea96ef. Changelog: [Internal] << DO NOT EDIT BELOW THIS LINE >> Reviewed By: sammy-SC Differential Revision: D45449587 Pulled By: tyao1 fbshipit-source-id: 76a5f34e42db5e9ad5a78b4b8f0d4e3dad2e7fcd
Re: releases, see https://react.dev/blog/2023/05/03/react-canaries |
Summary: in facebook/react#26738 we added nonce to the ResponseState. Initially it was used in a variety of places but the version that got merged only included it with the external fizz runtime. This PR updates the config for the external fizz runtime so that the nonce is encoded into the script chunks at request creation time. The rationale is that for live-requests, streaming is more likely than not so doing the encoding work at the start is better than during flush. For cases such as SSG where the runtime is not required the extra encoding is tolerable (not a live request). Bots are an interesting case because if you want fastest TTFB you will end up requiring the runtime but if you are withholding until the stream is done you have already sacrificed fastest TTFB and the marginal slowdown of the extraneous encoding is hopefully neglibible I'm writing this so later if we learn that this tradeoff isn't worth it we at least understand why I made the change in the first place. DiffTrain build for commit facebook/react@8ea96ef. Changelog: [Internal] << DO NOT EDIT BELOW THIS LINE >> Reviewed By: sammy-SC Differential Revision: D45449587 Pulled By: tyao1 fbshipit-source-id: 76a5f34e42db5e9ad5a78b4b8f0d4e3dad2e7fcd
in facebook/react#26738 we added nonce to the ResponseState. Initially it was used in a variety of places but the version that got merged only included it with the external fizz runtime. This PR updates the config for the external fizz runtime so that the nonce is encoded into the script chunks at request creation time. The rationale is that for live-requests, streaming is more likely than not so doing the encoding work at the start is better than during flush. For cases such as SSG where the runtime is not required the extra encoding is tolerable (not a live request). Bots are an interesting case because if you want fastest TTFB you will end up requiring the runtime but if you are withholding until the stream is done you have already sacrificed fastest TTFB and the marginal slowdown of the extraneous encoding is hopefully neglibible I'm writing this so later if we learn that this tradeoff isn't worth it we at least understand why I made the change in the first place. DiffTrain build for [8ea96ef84d8f08ed1846dec9e8ed20d2225db0d3](facebook/react@8ea96ef)
…#26738) Adds support for nonce on `bootstrapScripts`, `bootstrapModules` and the external fizz runtime
in facebook#26738 we added nonce to the ResponseState. Initially it was used in a variety of places but the version that got merged only included it with the external fizz runtime. This PR updates the config for the external fizz runtime so that the nonce is encoded into the script chunks at request creation time. The rationale is that for live-requests, streaming is more likely than not so doing the encoding work at the start is better than during flush. For cases such as SSG where the runtime is not required the extra encoding is tolerable (not a live request). Bots are an interesting case because if you want fastest TTFB you will end up requiring the runtime but if you are withholding until the stream is done you have already sacrificed fastest TTFB and the marginal slowdown of the extraneous encoding is hopefully neglibible I'm writing this so later if we learn that this tradeoff isn't worth it we at least understand why I made the change in the first place.
Adds support for nonce on `bootstrapScripts`, `bootstrapModules` and the external fizz runtime DiffTrain build for commit 9545e48.
Summary
When using a Content Security Policy with the
strict-dynamic
source list keyword, all other non-nonce keywords and sources are ignored, specifically'self'
. This means that scripts that are inserted via<script src=... />
into the HTML violate the content security policy.This fix also supports CSPs that are not using
strict-dynamic
, but are usingnonce-xxx
sourcesRight now the render functions (
renderToReadableStream
,renderToPipeableStream
etc) viacreateResponseState
add the nonce attribute to inline scripts set inbootstrapScriptContent
, but not scripts viabootstrapScripts
andbootstrapModules
. Also missing the nonce value are external runtime scripts, as well as scripts rendered at runtime that could be rendered on the server.Adding nonce support to those scripts will allow them to pass a
content-security-policy
that usesstrict-dynamic
.Related issues:
appDir
preventing use of strict CSP vercel/next.js#43743Changes:
nonce
to bootstrap scripts/modules inReactFizzConfigDOM -> createResponseState()
nonce
property to the return object forcreateResponseState()
nonce
prop tointernalPreinitScript()
(external runtime scripts)Inject(removed based on Add nonce support to bootstrap scripts and external runtime #26738 (comment))nonce
prop inpushStartInstance()
wheretype=='script'
if it exists inresponseState
FizzTestUtils.js
to re-add nonce attribute after executing inline scriptsHow did you test this change?
I added the following tests:
ReactDOMFizzServer-test - it should render scripts with nonce added
Test that
nonce
values are applied correctly in rendered scriptsReactDOMFizzServer-test - it should support nonce for bootstrap and runtime scripts
Update the existing nonce test to test all bootstrap scripts, as well as the runtime script