-
Notifications
You must be signed in to change notification settings - Fork 46.9k
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
[Flight Reply] Don't allow Symbols to be passed to a reply #28610
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
facebook-github-bot
added
CLA Signed
React Core Team
Opened by a member of the React Core Team
labels
Mar 21, 2024
gnoff
approved these changes
Mar 21, 2024
sebmarkbage
commented
Mar 21, 2024
case 'S': { | ||
// Symbol | ||
return Symbol.for(value.slice(2)); | ||
} |
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 the functional change since it doesn't matter what the client does if you can still fake a payload that includes this.
sebmarkbage
added a commit
that referenced
this pull request
Mar 21, 2024
…eration (#28611) This a follow up to #28564. It's alternative to #28609 which takes #28610 into account. It used to be possible to return JSX from an action with `useActionState`. ```js async function action(errors, payload) { "use server"; try { ... } catch (x) { return <div>Error message</div>; } } ``` ```js const [errors, formAction] = useActionState(action); return <div>{errors}</div>; ``` Returning JSX from an action is itself not anything problematic. It's that it also has to return the previous state to the action reducer again that's the problem. When this happens we accidentally could serialize an Element back to the server. I fixed this in #28564 so it's now blocked if you don't have a temporary reference set. However, you can't have that for the progressive enhancement case. The reply is eagerly encoded as part of the SSR render. Typically you wouldn't have these in the initial state so the common case is that they show up after the first POST back that yields an error and it's only in the no-JS case where this happens so it's hard to discover. As noted in #28609 there's a security implication with allowing elements to be sent across this kind of payload, so we can't just make it work. When an error happens during SSR our general policy is to try to recover on the client instead. After all, SSR is mainly a perf optimization in React terms and it's not primarily intended for a no JS solution. This PR takes the approach that if we fail to generate the progressive enhancement payload. I.e. if the serialization of previous state / closures throw. Then we fallback to the replaying semantics just client actions instead which will succeed. The effect of this is that this pattern mostly just works: - First render in the typical case doesn't have any JSX in it so it just renders a progressive enhanced form. - If JS fails to hydrate or you click early we do a form POST. If that hits an error and it tries to render it using JSX, then the new page will render successfully - however this time with a Replaying form instead. - If you try to submit the form again it'll have to be using JS. Meaning if you use JSX as the error return value of form state and you make a first attempt that fails, then no JS won't work because either the first or second attempt has to hydrate. We have ideas for potentially optimizing away serializing unused arguments like if you don't actually use previous state which would also solve it but it wouldn't cover all cases such as if it was deeply nested in complex state. Another approach that I considered was to poison the prev state if you passed an element back but let it through to the action but if you try to render the poisoned value, it wouldn't work. The downside of this is when to error. Because in the progressive enhancement case it wouldn't error early but when you actually try to invoke it at which point it would be too late to fallback to client replaying. It would probably have to always error even on the client which is unfortunate since this mostly just works as long as it hydrates.
github-actions bot
pushed a commit
that referenced
this pull request
Mar 21, 2024
…eration (#28611) This a follow up to #28564. It's alternative to #28609 which takes #28610 into account. It used to be possible to return JSX from an action with `useActionState`. ```js async function action(errors, payload) { "use server"; try { ... } catch (x) { return <div>Error message</div>; } } ``` ```js const [errors, formAction] = useActionState(action); return <div>{errors}</div>; ``` Returning JSX from an action is itself not anything problematic. It's that it also has to return the previous state to the action reducer again that's the problem. When this happens we accidentally could serialize an Element back to the server. I fixed this in #28564 so it's now blocked if you don't have a temporary reference set. However, you can't have that for the progressive enhancement case. The reply is eagerly encoded as part of the SSR render. Typically you wouldn't have these in the initial state so the common case is that they show up after the first POST back that yields an error and it's only in the no-JS case where this happens so it's hard to discover. As noted in #28609 there's a security implication with allowing elements to be sent across this kind of payload, so we can't just make it work. When an error happens during SSR our general policy is to try to recover on the client instead. After all, SSR is mainly a perf optimization in React terms and it's not primarily intended for a no JS solution. This PR takes the approach that if we fail to generate the progressive enhancement payload. I.e. if the serialization of previous state / closures throw. Then we fallback to the replaying semantics just client actions instead which will succeed. The effect of this is that this pattern mostly just works: - First render in the typical case doesn't have any JSX in it so it just renders a progressive enhanced form. - If JS fails to hydrate or you click early we do a form POST. If that hits an error and it tries to render it using JSX, then the new page will render successfully - however this time with a Replaying form instead. - If you try to submit the form again it'll have to be using JS. Meaning if you use JSX as the error return value of form state and you make a first attempt that fails, then no JS won't work because either the first or second attempt has to hydrate. We have ideas for potentially optimizing away serializing unused arguments like if you don't actually use previous state which would also solve it but it wouldn't cover all cases such as if it was deeply nested in complex state. Another approach that I considered was to poison the prev state if you passed an element back but let it through to the action but if you try to render the poisoned value, it wouldn't work. The downside of this is when to error. Because in the progressive enhancement case it wouldn't error early but when you actually try to invoke it at which point it would be too late to fallback to client replaying. It would probably have to always error even on the client which is unfortunate since this mostly just works as long as it hydrates. DiffTrain build for [fee786a](fee786a)
gnoff
added a commit
to gnoff/next.js
that referenced
this pull request
Mar 25, 2024
- facebook/react#28596 - facebook/react#28625 - facebook/react#28616 - facebook/react#28491 - facebook/react#28583 - facebook/react#28427 - facebook/react#28613 - facebook/react#28599 - facebook/react#28611 - facebook/react#28610 - facebook/react#28606 - facebook/react#28598 - facebook/react#28549 - facebook/react#28557 - facebook/react#28467 - facebook/react#28591 - facebook/react#28459 - facebook/react#28590 - facebook/react#28564 - facebook/react#28582 - facebook/react#28579 - facebook/react#28578 - facebook/react#28521 - facebook/react#28550 - facebook/react#28576 - facebook/react#28577 - facebook/react#28571 - facebook/react#28572 - facebook/react#28560 - facebook/react#28569 - facebook/react#28573 - facebook/react#28546 - facebook/react#28568 - facebook/react#28562 - facebook/react#28566 - facebook/react#28565 - facebook/react#28559 - facebook/react#28508 - facebook/react#20432 - facebook/react#28555 - facebook/react#24730 - facebook/react#28472 - facebook/react#27991 - facebook/react#28514 - facebook/react#28548 - facebook/react#28526 - facebook/react#28515 - facebook/react#28533 - facebook/react#28532 - facebook/react#28531 - facebook/react#28407 - facebook/react#28522 - facebook/react#28538 - facebook/react#28509 - facebook/react#28534 - facebook/react#28527 - facebook/react#28528 - facebook/react#28519 - facebook/react#28411 - facebook/react#28520 - facebook/react#28518 - facebook/react#28493 - facebook/react#28504 - facebook/react#28499 - facebook/react#28501 - facebook/react#28496 - facebook/react#28471 - facebook/react#28351 - facebook/react#28486 - facebook/react#28490 - facebook/react#28488 - facebook/react#28468 - facebook/react#28321 - facebook/react#28477 - facebook/react#28479 - facebook/react#28480 - facebook/react#28478 - facebook/react#28464 - facebook/react#28475 - facebook/react#28456 - facebook/react#28319 - facebook/react#28345 - facebook/react#28337 - facebook/react#28335 - facebook/react#28466 - facebook/react#28462 - facebook/react#28322 - facebook/react#28444 - facebook/react#28448 - facebook/react#28449 - facebook/react#28446 - facebook/react#28447 - facebook/react#24580 - facebook/react#28514 - facebook/react#28548 - facebook/react#28526 - facebook/react#28515 - facebook/react#28533 - facebook/react#28532 - facebook/react#28531 - facebook/react#28407 - facebook/react#28522 - facebook/react#28538 - facebook/react#28509 - facebook/react#28534 - facebook/react#28527 - facebook/react#28528 - facebook/react#28519 - facebook/react#28411 - facebook/react#28520 - facebook/react#28518 - facebook/react#28493 - facebook/react#28504 - facebook/react#28499 - facebook/react#28501 - facebook/react#28496 - facebook/react#28471 - facebook/react#28351 - facebook/react#28486 - facebook/react#28490 - facebook/react#28488 - facebook/react#28468 - facebook/react#28321 - facebook/react#28477 - facebook/react#28479 - facebook/react#28480 - facebook/react#28478 - facebook/react#28464 - facebook/react#28475 - facebook/react#28456 - facebook/react#28319 - facebook/react#28345 - facebook/react#28337 - facebook/react#28335 - facebook/react#28466 - facebook/react#28462 - facebook/react#28322 - facebook/react#28444 - facebook/react#28448 - facebook/react#28449 - facebook/react#28446 - facebook/react#28447 - facebook/react#24580
gnoff
added a commit
to gnoff/next.js
that referenced
this pull request
Mar 25, 2024
- facebook/react#28596 - facebook/react#28625 - facebook/react#28616 - facebook/react#28491 - facebook/react#28583 - facebook/react#28427 - facebook/react#28613 - facebook/react#28599 - facebook/react#28611 - facebook/react#28610 - facebook/react#28606 - facebook/react#28598 - facebook/react#28549 - facebook/react#28557 - facebook/react#28467 - facebook/react#28591 - facebook/react#28459 - facebook/react#28590 - facebook/react#28564 - facebook/react#28582 - facebook/react#28579 - facebook/react#28578 - facebook/react#28521 - facebook/react#28550 - facebook/react#28576 - facebook/react#28577 - facebook/react#28571 - facebook/react#28572 - facebook/react#28560 - facebook/react#28569 - facebook/react#28573 - facebook/react#28546 - facebook/react#28568 - facebook/react#28562 - facebook/react#28566 - facebook/react#28565 - facebook/react#28559 - facebook/react#28508 - facebook/react#20432 - facebook/react#28555 - facebook/react#24730 - facebook/react#28472 - facebook/react#27991 - facebook/react#28514 - facebook/react#28548 - facebook/react#28526 - facebook/react#28515 - facebook/react#28533 - facebook/react#28532 - facebook/react#28531 - facebook/react#28407 - facebook/react#28522 - facebook/react#28538 - facebook/react#28509 - facebook/react#28534 - facebook/react#28527 - facebook/react#28528 - facebook/react#28519 - facebook/react#28411 - facebook/react#28520 - facebook/react#28518 - facebook/react#28493 - facebook/react#28504 - facebook/react#28499 - facebook/react#28501 - facebook/react#28496 - facebook/react#28471 - facebook/react#28351 - facebook/react#28486 - facebook/react#28490 - facebook/react#28488 - facebook/react#28468 - facebook/react#28321 - facebook/react#28477 - facebook/react#28479 - facebook/react#28480 - facebook/react#28478 - facebook/react#28464 - facebook/react#28475 - facebook/react#28456 - facebook/react#28319 - facebook/react#28345 - facebook/react#28337 - facebook/react#28335 - facebook/react#28466 - facebook/react#28462 - facebook/react#28322 - facebook/react#28444 - facebook/react#28448 - facebook/react#28449 - facebook/react#28446 - facebook/react#28447 - facebook/react#24580
EdisonVan
pushed a commit
to EdisonVan/react
that referenced
this pull request
Apr 15, 2024
…28610) As mentioned in facebook#28609 there's a potential security risk if you allow a passed value to the server to spoof Elements because it allows a hacker to POST cross origin. This is only an issue if your framework allows this which it shouldn't but it seems like we should provide an extra layer of security here. ```js function action(errors, payload) { try { ... } catch (x) { return [newError].concat(errors); } } ``` ```js const [errors, formAction] = useActionState(action); return <div>{errors}</div>; ``` This would allow you to construct a payload where the previous "errors" set includes something like `<script src="danger.js" />`. We could block only elements from being received but it could potentially be a risk with creating other React types like Context too. We use symbols as a way to securely brand these. Most JS don't use this kind of branding with symbols like we do. They're generally properties which we don't support anyway. However in theory someone else could be using them like we do. So in an abundance of carefulness I just ban all symbols from being passed (except by temporary reference) - not just ours. This means that the format isn't fully symmetric even beyond just React Nodes. facebook#28611 allows code that includes symbols/elements to continue working but may have to bail out to replaying instead of no JS sometimes. However, you still can't access the symbols inside the server - they're by reference only.
EdisonVan
pushed a commit
to EdisonVan/react
that referenced
this pull request
Apr 15, 2024
…eration (facebook#28611) This a follow up to facebook#28564. It's alternative to facebook#28609 which takes facebook#28610 into account. It used to be possible to return JSX from an action with `useActionState`. ```js async function action(errors, payload) { "use server"; try { ... } catch (x) { return <div>Error message</div>; } } ``` ```js const [errors, formAction] = useActionState(action); return <div>{errors}</div>; ``` Returning JSX from an action is itself not anything problematic. It's that it also has to return the previous state to the action reducer again that's the problem. When this happens we accidentally could serialize an Element back to the server. I fixed this in facebook#28564 so it's now blocked if you don't have a temporary reference set. However, you can't have that for the progressive enhancement case. The reply is eagerly encoded as part of the SSR render. Typically you wouldn't have these in the initial state so the common case is that they show up after the first POST back that yields an error and it's only in the no-JS case where this happens so it's hard to discover. As noted in facebook#28609 there's a security implication with allowing elements to be sent across this kind of payload, so we can't just make it work. When an error happens during SSR our general policy is to try to recover on the client instead. After all, SSR is mainly a perf optimization in React terms and it's not primarily intended for a no JS solution. This PR takes the approach that if we fail to generate the progressive enhancement payload. I.e. if the serialization of previous state / closures throw. Then we fallback to the replaying semantics just client actions instead which will succeed. The effect of this is that this pattern mostly just works: - First render in the typical case doesn't have any JSX in it so it just renders a progressive enhanced form. - If JS fails to hydrate or you click early we do a form POST. If that hits an error and it tries to render it using JSX, then the new page will render successfully - however this time with a Replaying form instead. - If you try to submit the form again it'll have to be using JS. Meaning if you use JSX as the error return value of form state and you make a first attempt that fails, then no JS won't work because either the first or second attempt has to hydrate. We have ideas for potentially optimizing away serializing unused arguments like if you don't actually use previous state which would also solve it but it wouldn't cover all cases such as if it was deeply nested in complex state. Another approach that I considered was to poison the prev state if you passed an element back but let it through to the action but if you try to render the poisoned value, it wouldn't work. The downside of this is when to error. Because in the progressive enhancement case it wouldn't error early but when you actually try to invoke it at which point it would be too late to fallback to client replaying. It would probably have to always error even on the client which is unfortunate since this mostly just works as long as it hydrates.
eps1lon
added a commit
to vercel/next.js
that referenced
this pull request
Apr 19, 2024
### React upstream changes - facebook/react#28643 - facebook/react#28628 - facebook/react#28361 - facebook/react#28513 - facebook/react#28299 - facebook/react#28617 - facebook/react#28618 - facebook/react#28621 - facebook/react#28614 - facebook/react#28596 - facebook/react#28625 - facebook/react#28616 - facebook/react#28491 - facebook/react#28583 - facebook/react#28427 - facebook/react#28613 - facebook/react#28599 - facebook/react#28611 - facebook/react#28610 - facebook/react#28606 - facebook/react#28598 - facebook/react#28549 - facebook/react#28557 - facebook/react#28467 - facebook/react#28591 - facebook/react#28459 - facebook/react#28590 - facebook/react#28564 - facebook/react#28582 - facebook/react#28579 - facebook/react#28578 - facebook/react#28521 - facebook/react#28550 - facebook/react#28576 - facebook/react#28577 - facebook/react#28571 - facebook/react#28572 - facebook/react#28560 - facebook/react#28569 - facebook/react#28573 - facebook/react#28546 - facebook/react#28568 - facebook/react#28562 - facebook/react#28566 - facebook/react#28565 - facebook/react#28559 - facebook/react#28508 - facebook/react#20432 - facebook/react#28555 - facebook/react#24730 - facebook/react#28472 - facebook/react#27991 - facebook/react#28514 - facebook/react#28548 - facebook/react#28526 - facebook/react#28515 - facebook/react#28533 - facebook/react#28532 - facebook/react#28531 - facebook/react#28407 - facebook/react#28522 - facebook/react#28538 - facebook/react#28509 - facebook/react#28534 - facebook/react#28527 - facebook/react#28528 - facebook/react#28519 - facebook/react#28411 - facebook/react#28520 - facebook/react#28518 - facebook/react#28493 - facebook/react#28504 - facebook/react#28499 - facebook/react#28501 - facebook/react#28496 - facebook/react#28471 - facebook/react#28351 - facebook/react#28486 - facebook/react#28490 - facebook/react#28488 - facebook/react#28468 - facebook/react#28321 - facebook/react#28477 - facebook/react#28479 - facebook/react#28480 - facebook/react#28478 - facebook/react#28464 - facebook/react#28475 - facebook/react#28456 - facebook/react#28319 - facebook/react#28345 - facebook/react#28337 - facebook/react#28335 - facebook/react#28466 - facebook/react#28462 - facebook/react#28322 - facebook/react#28444 - facebook/react#28448 - facebook/react#28449 - facebook/react#28446 - facebook/react#28447 - facebook/react#24580 - facebook/react#28514 - facebook/react#28548 - facebook/react#28526 - facebook/react#28515 - facebook/react#28533 - facebook/react#28532 - facebook/react#28531 - facebook/react#28407 - facebook/react#28522 - facebook/react#28538 - facebook/react#28509 - facebook/react#28534 - facebook/react#28527 - facebook/react#28528 - facebook/react#28519 - facebook/react#28411 - facebook/react#28520 - facebook/react#28518 - facebook/react#28493 - facebook/react#28504 - facebook/react#28499 - facebook/react#28501 - facebook/react#28496 - facebook/react#28471 - facebook/react#28351 - facebook/react#28486 - facebook/react#28490 - facebook/react#28488 - facebook/react#28468 - facebook/react#28321 - facebook/react#28477 - facebook/react#28479 - facebook/react#28480 - facebook/react#28478 - facebook/react#28464 - facebook/react#28475 - facebook/react#28456 - facebook/react#28319 - facebook/react#28345 - facebook/react#28337 - facebook/react#28335 - facebook/react#28466 - facebook/react#28462 - facebook/react#28322 - facebook/react#28444 - facebook/react#28448 - facebook/react#28449 - facebook/react#28446 - facebook/react#28447 - facebook/react#24580
13 tasks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
As mentioned in #28609 there's a potential security risk if you allow a passed value to the server to spoof Elements because it allows a hacker to POST cross origin. This is only an issue if your framework allows this which it shouldn't but it seems like we should provide an extra layer of security here.
This would allow you to construct a payload where the previous "errors" set includes something like
<script src="danger.js" />
.We could block only elements from being received but it could potentially be a risk with creating other React types like Context too. We use symbols as a way to securely brand these.
Most JS don't use this kind of branding with symbols like we do. They're generally properties which we don't support anyway. However in theory someone else could be using them like we do. So in an abundance of carefulness I just ban all symbols from being passed (except by temporary reference) - not just ours.
This means that the format isn't fully symmetric even beyond just React Nodes.
#28611 allows code that includes symbols/elements to continue working but may have to bail out to replaying instead of no JS sometimes. However, you still can't access the symbols inside the server - they're by reference only.