Skip to content
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] Taint APIs #27445

Merged
merged 9 commits into from
Oct 2, 2023
Merged

[Flight] Taint APIs #27445

merged 9 commits into from
Oct 2, 2023

Conversation

sebmarkbage
Copy link
Collaborator

This lets a registered object or value be "tainted", which we block from crossing the serialization boundary. It's only allowed to stay in-memory.

This is an extra layer of protection against mistakes of transferring data from a data access layer to a client. It doesn't provide perfect protection, because it doesn't trace through derived values and substrings. So it shouldn't be used as the only security layer but more layers are better.

taintObjectReference is for specific object instances, not any nested objects or values inside that object. It's useful to avoid specific objects from getting passed as is. It ensures that you don't accidentally leak values in a specific context. It can be for security reasons like tokens, privacy reasons like personal data or performance reasons like avoiding passing large objects over the wire.

It might be privacy violation to leak the age of a specific user, but the number itself isn't blocked in any other context. As soon as the value is extracted and passed specifically without the object, it can therefore leak.

taintUniqueValue is useful for high entropy values such as hashes, tokens or crypto keys that are very unique values. In that case it can be useful to taint the actual primitive values themselves. These can be encoded as a string, bigint or typed array. We don't currently check for this value in a substring or inside other typed arrays.

Since values can be created from different sources they don't just follow garbage collection. In this case an additional object must be provided that defines the life time of this value for how long it should be blocked. It can be globalThis for essentially forever, but that risks leaking memory for ever when you're dealing with dynamic values like reading a token from a database. So in that case the idea is that you pass the object that might end up in cache.

A request is the only thing that is expected to do any work. The principle is that you can derive values from out of a tainted
entry during a request. Including stashing it in a per request cache. What you can't do is store a derived value in a global module level cache. At least not without also tainting the object.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Oct 2, 2023
@react-sizebot
Copy link

react-sizebot commented Oct 2, 2023

Comparing: a6ed60a...c2e8442

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js = 167.55 kB 167.55 kB = 52.14 kB 52.14 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 176.21 kB 176.21 kB = 54.84 kB 54.84 kB
facebook-www/ReactDOM-prod.classic.js = 564.39 kB 564.39 kB = 99.37 kB 99.37 kB
facebook-www/ReactDOM-prod.modern.js = 548.11 kB 548.11 kB = 96.45 kB 96.45 kB
oss-experimental/react/cjs/react.shared-subset.production.min.js +18.39% 8.40 kB 9.95 kB +15.10% 3.44 kB 3.96 kB
oss-experimental/react/cjs/react.shared-subset.development.js +5.47% 86.16 kB 90.88 kB +5.34% 23.93 kB 25.21 kB
oss-experimental/react-server/cjs/react-server-flight.production.min.js +3.55% 18.03 kB 18.67 kB +3.72% 6.35 kB 6.59 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +3.43% 70.96 kB 73.39 kB +2.76% 17.05 kB 17.52 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.min.js +2.42% 26.70 kB 27.35 kB +2.30% 9.13 kB 9.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js +2.35% 27.48 kB 28.13 kB +2.30% 9.27 kB 9.49 kB
oss-experimental/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.production.min.js +2.34% 27.66 kB 28.31 kB +2.26% 9.38 kB 9.59 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js +2.33% 27.80 kB 28.45 kB +2.30% 9.38 kB 9.60 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +2.31% 105.21 kB 107.64 kB +1.94% 24.91 kB 25.40 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js +2.29% 28.23 kB 28.88 kB +2.33% 9.47 kB 9.69 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js +2.28% 28.43 kB 29.08 kB +2.27% 9.55 kB 9.76 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js +2.28% 28.39 kB 29.04 kB +2.22% 9.60 kB 9.81 kB
oss-experimental/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.development.js +2.26% 114.05 kB 116.63 kB +1.97% 26.27 kB 26.79 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +2.26% 107.93 kB 110.36 kB +1.85% 25.94 kB 26.42 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +2.25% 108.34 kB 110.77 kB +1.83% 26.06 kB 26.54 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js +2.24% 28.73 kB 29.37 kB +2.33% 9.72 kB 9.94 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js +2.21% 116.77 kB 119.35 kB +1.94% 27.01 kB 27.53 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +2.21% 110.40 kB 112.84 kB +1.80% 26.62 kB 27.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +2.20% 110.51 kB 112.95 kB +1.80% 26.66 kB 27.14 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +2.20% 110.62 kB 113.05 kB +1.84% 26.14 kB 26.62 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js +2.20% 29.23 kB 29.88 kB +2.28% 9.87 kB 10.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js +2.18% 29.53 kB 30.17 kB +2.29% 9.92 kB 10.14 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +2.15% 113.04 kB 115.48 kB +1.81% 26.77 kB 27.25 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +2.15% 113.26 kB 115.69 kB +1.76% 26.98 kB 27.45 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js +2.14% 30.04 kB 30.69 kB +2.23% 10.08 kB 10.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +2.10% 115.68 kB 118.11 kB +1.73% 27.64 kB 28.12 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react/cjs/react.shared-subset.production.min.js +18.39% 8.40 kB 9.95 kB +15.10% 3.44 kB 3.96 kB
oss-experimental/react/cjs/react.shared-subset.development.js +5.47% 86.16 kB 90.88 kB +5.34% 23.93 kB 25.21 kB
oss-experimental/react-server/cjs/react-server-flight.production.min.js +3.55% 18.03 kB 18.67 kB +3.72% 6.35 kB 6.59 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +3.43% 70.96 kB 73.39 kB +2.76% 17.05 kB 17.52 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.min.js +2.42% 26.70 kB 27.35 kB +2.30% 9.13 kB 9.34 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.min.js +2.35% 27.48 kB 28.13 kB +2.30% 9.27 kB 9.49 kB
oss-experimental/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.production.min.js +2.34% 27.66 kB 28.31 kB +2.26% 9.38 kB 9.59 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.min.js +2.33% 27.80 kB 28.45 kB +2.30% 9.38 kB 9.60 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +2.31% 105.21 kB 107.64 kB +1.94% 24.91 kB 25.40 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js +2.29% 28.23 kB 28.88 kB +2.33% 9.47 kB 9.69 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js +2.28% 28.43 kB 29.08 kB +2.27% 9.55 kB 9.76 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.production.min.js +2.28% 28.39 kB 29.04 kB +2.22% 9.60 kB 9.81 kB
oss-experimental/react-server-dom-turbopack/umd/react-server-dom-turbopack-server.browser.development.js +2.26% 114.05 kB 116.63 kB +1.97% 26.27 kB 26.79 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +2.26% 107.93 kB 110.36 kB +1.85% 25.94 kB 26.42 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +2.25% 108.34 kB 110.77 kB +1.83% 26.06 kB 26.54 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.min.js +2.24% 28.73 kB 29.37 kB +2.33% 9.72 kB 9.94 kB
oss-experimental/react-server-dom-webpack/umd/react-server-dom-webpack-server.browser.development.js +2.21% 116.77 kB 119.35 kB +1.94% 27.01 kB 27.53 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +2.21% 110.40 kB 112.84 kB +1.80% 26.62 kB 27.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +2.20% 110.51 kB 112.95 kB +1.80% 26.66 kB 27.14 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.development.js +2.20% 110.62 kB 113.05 kB +1.84% 26.14 kB 26.62 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.min.js +2.20% 29.23 kB 29.88 kB +2.28% 9.87 kB 10.10 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js +2.18% 29.53 kB 30.17 kB +2.29% 9.92 kB 10.14 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +2.15% 113.04 kB 115.48 kB +1.81% 26.77 kB 27.25 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +2.15% 113.26 kB 115.69 kB +1.76% 26.98 kB 27.45 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js +2.14% 30.04 kB 30.69 kB +2.23% 10.08 kB 10.31 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +2.10% 115.68 kB 118.11 kB +1.73% 27.64 kB 28.12 kB

Generated by 🚫 dangerJS against c2e8442

Clean up values when the life time object gets finalized.
Instead of copying all typed arrays that we serialize into a string,
we only do that if the length of the data is the length of any tainted
binary value first.

You're not really supposed to use this with arbitrary binary data but
mostly things like hashes or tokens that end up with the same length.
A request is the only thing that is expected to do any work.
The principle is that you can derive values from out of a tainted
entry during a request. Including stashing it in a per request cache.

What you can't do is store a derived value in a global module level
cache. At least not without also tainting the object.
Copy link
Contributor

@sophiebits sophiebits left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a meaningful reason to use the set of pending requests when the object is freed vs when taint is called?


const process = {
env: {
SECRET: '3e971ecc1485fe78625598bf9b6f85db',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cute

$ echo -n Seb | md5
3e971ecc1485fe78625598bf9b6f85db

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering who would be the first nerd to figure this out. Did you notice the hidden pattern for the other ones tho?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no!

(typeof lifetime !== 'object' && typeof lifetime !== 'function')
) {
throw new Error(
'To taint a value, a life time must be defined by passing an object that holds ' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lifetime

'Cannot taint a ' +
kind +
' because the value is too general and cannot be ' +
'a secret by',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo?

@sebmarkbage
Copy link
Collaborator Author

Is there a meaningful reason to use the set of pending requests when the object is freed vs when taint is called?

When you add it a value to a shared in memory cache, then it can become associated with multiple other pending requests before it becomes freed.

Typically a shared cache might hold onto it but even that cache might eventually free it, such as an LRU. That freeing could happen while a request that has extracted it from the cache has already gotten a handle to the data.

import LRU from '...';
function Component({ id }) {
  let user = LRU.getIfCached(id);
  LRU.setCached(otherId, otherThing); // clears the other entry
  // gc
  return <Other value={user.token} />
}

@sophiebits
Copy link
Contributor

Makes sense, good thinking.

@sebmarkbage sebmarkbage merged commit 843ec07 into facebook:main Oct 2, 2023
36 checks passed
alunyov pushed a commit to alunyov/react that referenced this pull request Oct 11, 2023
This lets a registered object or value be "tainted", which we block from
crossing the serialization boundary. It's only allowed to stay
in-memory.

This is an extra layer of protection against mistakes of transferring
data from a data access layer to a client. It doesn't provide perfect
protection, because it doesn't trace through derived values and
substrings. So it shouldn't be used as the only security layer but more
layers are better.

`taintObjectReference` is for specific object instances, not any nested
objects or values inside that object. It's useful to avoid specific
objects from getting passed as is. It ensures that you don't
accidentally leak values in a specific context. It can be for security
reasons like tokens, privacy reasons like personal data or performance
reasons like avoiding passing large objects over the wire.

It might be privacy violation to leak the age of a specific user, but
the number itself isn't blocked in any other context. As soon as the
value is extracted and passed specifically without the object, it can
therefore leak.

`taintUniqueValue` is useful for high entropy values such as hashes,
tokens or crypto keys that are very unique values. In that case it can
be useful to taint the actual primitive values themselves. These can be
encoded as a string, bigint or typed array. We don't currently check for
this value in a substring or inside other typed arrays.

Since values can be created from different sources they don't just
follow garbage collection. In this case an additional object must be
provided that defines the life time of this value for how long it should
be blocked. It can be `globalThis` for essentially forever, but that
risks leaking memory for ever when you're dealing with dynamic values
like reading a token from a database. So in that case the idea is that
you pass the object that might end up in cache.

A request is the only thing that is expected to do any work. The
principle is that you can derive values from out of a tainted
entry during a request. Including stashing it in a per request cache.
What you can't do is store a derived value in a global module level
cache. At least not without also tainting the object.
ztanner added a commit to vercel/next.js that referenced this pull request Oct 16, 2023
…experimental prefix for server action APIs (#56809)

The latest React canary builds have a few changes that need to be
adopted for compatability.

1. the `useFormState` and `useFormStatus` hooks in `react-dom` and the
`formData` opiont in `react-dom/server` are no longer prefixed with
`experimental_`
2. server content (an undocumented React feature) has been removed. Next
only had trivial intenral use of this API and did not expose a coherent
feature to Next users (no ability to seed context on refetches). It is
still possible that some users used the React server context APIs which
is why this should go into Next 14.

### React upstream changes

- facebook/react#27513
- facebook/react#27514
- facebook/react#27511
- facebook/react#27508
- facebook/react#27502
- facebook/react#27474
- facebook/react#26789
- facebook/react#27500
- facebook/react#27488
- facebook/react#27458
- facebook/react#27471
- facebook/react#27470
- facebook/react#27464
- facebook/react#27456
- facebook/react#27462
- facebook/react#27461
- facebook/react#27460
- facebook/react#27459
- facebook/react#27454
- facebook/react#27457
- facebook/react#27453
- facebook/react#27401
- facebook/react#27443
- facebook/react#27445
- facebook/react#27364
- facebook/react#27440
- facebook/react#27436

---------

Co-authored-by: Zack Tanner <zacktanner@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Jiachi Liu <inbox@huozhi.im>
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
This lets a registered object or value be "tainted", which we block from
crossing the serialization boundary. It's only allowed to stay
in-memory.

This is an extra layer of protection against mistakes of transferring
data from a data access layer to a client. It doesn't provide perfect
protection, because it doesn't trace through derived values and
substrings. So it shouldn't be used as the only security layer but more
layers are better.

`taintObjectReference` is for specific object instances, not any nested
objects or values inside that object. It's useful to avoid specific
objects from getting passed as is. It ensures that you don't
accidentally leak values in a specific context. It can be for security
reasons like tokens, privacy reasons like personal data or performance
reasons like avoiding passing large objects over the wire.

It might be privacy violation to leak the age of a specific user, but
the number itself isn't blocked in any other context. As soon as the
value is extracted and passed specifically without the object, it can
therefore leak.

`taintUniqueValue` is useful for high entropy values such as hashes,
tokens or crypto keys that are very unique values. In that case it can
be useful to taint the actual primitive values themselves. These can be
encoded as a string, bigint or typed array. We don't currently check for
this value in a substring or inside other typed arrays.

Since values can be created from different sources they don't just
follow garbage collection. In this case an additional object must be
provided that defines the life time of this value for how long it should
be blocked. It can be `globalThis` for essentially forever, but that
risks leaking memory for ever when you're dealing with dynamic values
like reading a token from a database. So in that case the idea is that
you pass the object that might end up in cache.

A request is the only thing that is expected to do any work. The
principle is that you can derive values from out of a tainted
entry during a request. Including stashing it in a per request cache.
What you can't do is store a derived value in a global module level
cache. At least not without also tainting the object.
bigfootjon pushed a commit that referenced this pull request Apr 18, 2024
This lets a registered object or value be "tainted", which we block from
crossing the serialization boundary. It's only allowed to stay
in-memory.

This is an extra layer of protection against mistakes of transferring
data from a data access layer to a client. It doesn't provide perfect
protection, because it doesn't trace through derived values and
substrings. So it shouldn't be used as the only security layer but more
layers are better.

`taintObjectReference` is for specific object instances, not any nested
objects or values inside that object. It's useful to avoid specific
objects from getting passed as is. It ensures that you don't
accidentally leak values in a specific context. It can be for security
reasons like tokens, privacy reasons like personal data or performance
reasons like avoiding passing large objects over the wire.

It might be privacy violation to leak the age of a specific user, but
the number itself isn't blocked in any other context. As soon as the
value is extracted and passed specifically without the object, it can
therefore leak.

`taintUniqueValue` is useful for high entropy values such as hashes,
tokens or crypto keys that are very unique values. In that case it can
be useful to taint the actual primitive values themselves. These can be
encoded as a string, bigint or typed array. We don't currently check for
this value in a substring or inside other typed arrays.

Since values can be created from different sources they don't just
follow garbage collection. In this case an additional object must be
provided that defines the life time of this value for how long it should
be blocked. It can be `globalThis` for essentially forever, but that
risks leaking memory for ever when you're dealing with dynamic values
like reading a token from a database. So in that case the idea is that
you pass the object that might end up in cache.

A request is the only thing that is expected to do any work. The
principle is that you can derive values from out of a tainted
entry during a request. Including stashing it in a per request cache.
What you can't do is store a derived value in a global module level
cache. At least not without also tainting the object.

DiffTrain build for commit 843ec07.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants