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

Add trusted types to react on client side #16157

Merged
merged 13 commits into from
Sep 16, 2019
Merged

Add trusted types to react on client side #16157

merged 13 commits into from
Sep 16, 2019

Conversation

Siegrift
Copy link
Contributor

@Siegrift Siegrift commented Jul 18, 2019

Trusted Types

Trusted Types (spec, introductory article) is a new experimental DOM API implemented within the WICG , with a working Chrome implementation.

The API creates a few new objects available on the global object in the browser, like most other web APIs (impl in TS and in Closure compiler).

Under certain conditions, controlled by a HTTP header (analogous to Content-Security-Policy behavior), the API can enable the enforcement - then it changes the signature of several DOM API functions and property setters, such that they accept specific object types, and reject strings. Colloquially, DOM API becomes strongly typed.

For example, with Trusted Types Element.innerHTML property setter accepts a TrustedHTML object.

Trusted Type objects stringify to their inner value. This API shape is a deliberate choice that enables existing web applications and libraries to gradually migrate from strings to Trusted Types without breaking functionality. In our example, it makes it possible to write the following:

const policy = TrustedTypes.createPolicy('foo', { 
  createHTML: (s) => { /* some validation*/; return s} 
});

const trustedHTML = policy.createHTML('bar');
anElement.innerHTML = trustedHTML

anElement.innerHTML === 'bar'

The above code works regardless if the Trusted Types enforcement is enabled or not.

Reading from the DOM is unaffected, so Element.innerHTML getter returns a string. That's for practical reasons -- web applications read from DOM more often than they write to it, and only writing exposes the application to DOM XSS risks. Typing only the setters allows us to secure web applications with minimal code changes.

Adding Trusted Types to React for client side

React applications rarely manipulate DOM directly as this is what React is for. That means that, if users want to opt in to Trusted Types, values assigned to execution sinks must be trusted. React on its own doesn't produce these execution sinks, except one hack for IE. The only problem in React is the stringifying of values before assigning them to element properties or attributes.

This PR enables applications to use Trusted Types on client side. The implementation removes stringifying of trusted values in reactDOM/client. It does so only when Trusted Types are available in global object. If they are available, there are special is* functions which are used to check if the value is trusted type or not. If the value is a trusted type, we return the value itself, otherwise we preserve the original logic and stringify the value.

Problems

We cannot use Trusted Types in Internet Explorer (IE) when using dangerouslySetInnerHTML on svg elements - The cause is that in IE svg elements doesn't have innerHTML property, which means that to support innerHTML, react needs to use a hack, which wraps the innerHTML of the svg and wraps them in an svg element and assigns this to innerHTML of some HTML element. This is a problem for Trusted Types because the original svg innerHTML value is stringified. There are a few workarounds for this:
a) Use default Trusted Type policy allowing creating svg nodes
b) Do not use svg with dangerouslySetInnerHTML but wrap the svg with div element and use dangerouslySetInnerHTML there

Reference

Update

@sizebot
Copy link

sizebot commented Jul 18, 2019

Details of bundled changes.

Comparing: fc80772...eb1a123

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.profiling.min.js 0.0% 0.0% 115.25 KB 115.27 KB 36.33 KB 36.34 KB NODE_PROFILING
react-dom-server.browser.development.js 0.0% -0.0% 140.9 KB 140.9 KB 36.96 KB 36.96 KB UMD_DEV
ReactDOM-dev.js +0.4% +0.5% 933.83 KB 937.39 KB 206.71 KB 207.65 KB FB_WWW_DEV
react-dom-test-utils.production.min.js 0.0% -0.0% 11.18 KB 11.18 KB 4.15 KB 4.15 KB UMD_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% -0.1% 1.2 KB 1.2 KB 702 B 701 B UMD_PROD
react-dom-test-utils.development.js 0.0% -0.0% 55.49 KB 55.49 KB 15.45 KB 15.45 KB NODE_DEV
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 3.61 KB 3.61 KB 1.48 KB 1.48 KB NODE_DEV
react-dom-unstable-fizz.browser.production.min.js 0.0% -0.2% 1.04 KB 1.04 KB 633 B 632 B NODE_PROD
react-dom.development.js +0.4% +0.4% 909.72 KB 912.98 KB 206.59 KB 207.5 KB UMD_DEV
react-dom.production.min.js 0.0% 0.0% 111.64 KB 111.66 KB 36.01 KB 36.02 KB UMD_PROD
ReactTestUtils-dev.js +0.1% +0.1% 52.73 KB 52.8 KB 14.13 KB 14.15 KB FB_WWW_DEV
react-dom.profiling.min.js 0.0% 0.0% 115.05 KB 115.07 KB 37.05 KB 37.06 KB UMD_PROFILING
react-dom.development.js +0.4% +0.4% 904.01 KB 907.27 KB 204.98 KB 205.89 KB NODE_DEV
react-dom-server.node.development.js 0.0% -0.0% 138.05 KB 138.05 KB 36.25 KB 36.24 KB NODE_DEV
react-dom.production.min.js 0.0% 0.0% 111.61 KB 111.63 KB 35.42 KB 35.42 KB NODE_PROD
ReactDOM-prod.js 🔺+0.2% 🔺+0.3% 369.71 KB 370.52 KB 67.8 KB 67.99 KB FB_WWW_PROD
ReactDOM-profiling.js +0.2% +0.3% 374.44 KB 375.25 KB 68.85 KB 69.04 KB FB_WWW_PROFILING
react-dom-server.browser.development.js 0.0% -0.0% 137.03 KB 137.03 KB 36.02 KB 36.02 KB NODE_DEV
react-dom-server.browser.production.min.js 0.0% -0.0% 19.66 KB 19.66 KB 7.33 KB 7.33 KB NODE_PROD
react-dom-unstable-native-dependencies.development.js 0.0% -0.0% 60.71 KB 60.71 KB 15.85 KB 15.85 KB UMD_DEV
ReactDOMServer-dev.js +0.1% 0.0% 141.34 KB 141.41 KB 35.6 KB 35.61 KB FB_WWW_DEV
ReactDOMServer-prod.js 0.0% 0.0% 48.13 KB 48.13 KB 11.05 KB 11.05 KB FB_WWW_PROD
react-dom-unstable-native-dependencies.development.js 0.0% -0.0% 60.39 KB 60.39 KB 15.72 KB 15.72 KB NODE_DEV
react-dom-unstable-fizz.node.development.js 0.0% +0.1% 3.87 KB 3.87 KB 1.51 KB 1.51 KB NODE_DEV
react-dom-unstable-native-dependencies.production.min.js 0.0% -0.0% 10.49 KB 10.49 KB 3.58 KB 3.58 KB NODE_PROD
react-dom-unstable-fizz.node.production.min.js 0.0% -0.1% 1.1 KB 1.1 KB 667 B 666 B NODE_PROD

Generated by 🚫 dangerJS

@gaearon
Copy link
Collaborator

gaearon commented Jul 26, 2019

Thanks for the PR!

Since Oct 12, 2015 React is making sure scripts are not run inside React components. It does so by creating a parser-inserted script via innerHTML, which never executes. This is a problem for Trusted Types, because innerHTML must use TrustedHTML value. This behavior in React is not documented and there are also no tests for it. We've decided to completely remove that part, but if it's a problem we can preserve the same behavior when Trusted Types are not available.

I think that behavior was always there. It's not new since Oct 12. This is because in the past, we always used innerHTML for generating the markup. The Oct 12 change was a precursor to #5146 which switched us to document.createElement. So it aligned the createElement behavior to the old innerHTML behavior. Regardless, enabling scripts to execute would be a major breaking change. I don't have a good sense of whether we want to do this in longer term, but for now we should find a way to avoid it if we can.

Cannot use Trusted Types in Internet Explorer (IE) when using dangerouslySetInnerHTML on svg elements

I'm not following.. Does this mean IE has support for real Trusted Types? Is there any version of IE where this would matter?

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

Please see requested changes + we need to handle the "undesired changes" too, as I mentioned above. Thank you!

packages/react-dom/src/shared/trustedTypesAwareToString.js Outdated Show resolved Hide resolved
packages/react-dom/src/shared/trustedTypesAwareToString.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/setInnerHTML.js Show resolved Hide resolved
packages/react-dom/src/shared/trustedTypesAwareToString.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/ToStringValue.js Outdated Show resolved Hide resolved
@Siegrift
Copy link
Contributor Author

Siegrift commented Aug 7, 2019

@gaearon I have completed the changes, can you have a look?

@gaearon
Copy link
Collaborator

gaearon commented Aug 9, 2019

I will check on Monday, thank you!

packages/react-dom/src/client/ToStringValue.js Outdated Show resolved Hide resolved
.eslintrc.js Show resolved Hide resolved
packages/react-dom/src/client/ToStringValue.js Outdated Show resolved Hide resolved
@gaearon
Copy link
Collaborator

gaearon commented Sep 10, 2019

Should we put it behind a feature flag? See ReactFeatureFlags. The motivation is that we might want to tweak the semantics before getting it into a stable release. Putting the support behind a feature flag lets us continue to do release with it off, while testing with it on internally at FB. Then when we’re ready to commit to the semantics, we can flip the flag for everyone. What do you think?

@koto
Copy link
Contributor

koto commented Sep 10, 2019

How can clients use feature flags outside of FB? I assume a rebuild of React is needed to have one's own flag set (based on this comment?

We can put it behind a flag, but so far the changes look quite inobtrusive (it's only removing stringification for sinks, warning about SVG interpolation in IE and <script>s in JSX). A flag might be an overkill for now. Your call though :)

@gaearon
Copy link
Collaborator

gaearon commented Sep 10, 2019

How can clients use feature flags outside of FB? I assume a rebuild of React is needed to have one's own flag set (based on this comment?

You'd need to do a build yourself, yes.

A flag might be an overkill for now. Your call though :)

A flag is a safer option because if something is wrong, we can disable it instead of reverting the whole PR to unblock other work. We use flags for all non-trivial changes, and this one is a bit non-trivial. I don't mean a long-lived flag, but just for a few weeks so we can start testing this safely without worrying about it affecting release timing.

packages/react-dom/src/client/ReactDOMComponent.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/ReactDOMComponent.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/ReactDOMComponent.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/ReactDOMComponent.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/setInnerHTML.js Outdated Show resolved Hide resolved
Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

  • Let's add deduplication to the warning. It shouldn't warn more than once.
  • Let's remove the typeof TrustedTypes check (PascalCase) we accidentally left in.
  • Let's create a feature flag and put all new logic behind it. When it's false, we should behave identically to how we used to. See any flag in ReactFeatureFlags and its usage as an example. You'll need to rename test to trustedTypes-test.internal.js and do require('ReactFeatureFlags') there like in other tests to set the flag.

Thank you!

@sizebot
Copy link

sizebot commented Sep 11, 2019

Details of bundled changes.

Comparing: 8f03109...941ec84

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.profiling.min.js 0.0% 0.0% 117.12 KB 117.16 KB 36.86 KB 36.87 KB NODE_PROFILING
react-dom-server.browser.development.js 0.0% -0.0% 142.1 KB 142.1 KB 37.12 KB 37.12 KB UMD_DEV
ReactDOM-dev.js +0.4% +0.5% 953.92 KB 957.59 KB 210.6 KB 211.61 KB FB_WWW_DEV
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 3.78 KB 3.78 KB 1.53 KB 1.53 KB UMD_DEV
react-dom-test-utils.production.min.js 0.0% -0.0% 11.19 KB 11.19 KB 4.16 KB 4.16 KB UMD_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.1% 1.2 KB 1.2 KB 701 B 702 B UMD_PROD
react-dom-test-utils.development.js 0.0% 0.0% 55.73 KB 55.73 KB 15.49 KB 15.49 KB NODE_DEV
react-dom-test-utils.production.min.js 0.0% -0.0% 10.96 KB 10.96 KB 4.09 KB 4.09 KB NODE_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.2% 1.04 KB 1.04 KB 632 B 633 B NODE_PROD
react-dom.development.js +0.4% +0.4% 929.7 KB 933.05 KB 210.01 KB 210.94 KB UMD_DEV
react-dom.production.min.js 0.0% 0.0% 113.47 KB 113.51 KB 36.55 KB 36.57 KB UMD_PROD
ReactTestUtils-dev.js +0.1% +0.2% 53.09 KB 53.17 KB 14.2 KB 14.22 KB FB_WWW_DEV
react-dom.profiling.min.js 0.0% -0.0% 116.99 KB 117.03 KB 37.55 KB 37.55 KB UMD_PROFILING
react-dom.development.js +0.4% +0.5% 923.78 KB 927.13 KB 208.38 KB 209.41 KB NODE_DEV
react-dom-server.node.development.js 0.0% -0.0% 139.05 KB 139.05 KB 36.34 KB 36.34 KB NODE_DEV
react-dom.production.min.js 0.0% 0.0% 113.4 KB 113.44 KB 35.86 KB 35.87 KB NODE_PROD
react-dom-server.node.production.min.js 0.0% -0.0% 20.13 KB 20.13 KB 7.51 KB 7.5 KB NODE_PROD
ReactDOM-prod.js 🔺+0.2% 🔺+0.3% 379.97 KB 380.86 KB 69.28 KB 69.5 KB FB_WWW_PROD
ReactDOM-profiling.js +0.2% +0.3% 386.01 KB 386.9 KB 70.38 KB 70.59 KB FB_WWW_PROFILING
react-dom-server.browser.development.js 0.0% -0.0% 138.04 KB 138.04 KB 36.11 KB 36.11 KB NODE_DEV
react-dom-unstable-native-dependencies.development.js 0.0% 0.0% 60.71 KB 60.71 KB 15.84 KB 15.84 KB UMD_DEV
react-dom-unstable-native-dependencies.production.min.js 0.0% 0.0% 10.75 KB 10.75 KB 3.67 KB 3.67 KB UMD_PROD
ReactDOMServer-dev.js +0.1% +0.1% 142.55 KB 142.62 KB 35.71 KB 35.73 KB FB_WWW_DEV
ReactDOMServer-prod.js 0.0% 0.0% 48.58 KB 48.58 KB 11.11 KB 11.11 KB FB_WWW_PROD
react-dom-unstable-native-dependencies.development.js 0.0% 0.0% 60.39 KB 60.39 KB 15.72 KB 15.72 KB NODE_DEV
react-dom-unstable-fizz.node.development.js 0.0% +0.1% 3.87 KB 3.87 KB 1.51 KB 1.51 KB NODE_DEV
react-dom-unstable-native-dependencies.production.min.js 0.0% 🔺+0.1% 10.49 KB 10.49 KB 3.57 KB 3.57 KB NODE_PROD

Generated by 🚫 dangerJS against 941ec84

packages/react-dom/src/client/ReactDOMComponent.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/ReactDOMComponent.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/ReactDOMComponent.js Outdated Show resolved Hide resolved
packages/react-dom/src/client/ToStringValue.js Outdated Show resolved Hide resolved
trustedTypes.isHTML(value) ||
trustedTypes.isScript(value) ||
trustedTypes.isScriptURL(value) ||
// TrustedURLs are deprecated and will be removed soon: https://github.com/WICG/trusted-types/pull/204
Copy link
Collaborator

@gaearon gaearon Sep 11, 2019

Choose a reason for hiding this comment

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

Do we even want to support them then?

Copy link
Contributor Author

@Siegrift Siegrift Sep 11, 2019

Choose a reason for hiding this comment

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

I think at least for now we shouldn't. Both chrome and the polyfill currently support them and if we don't, trying out the integration will for the users will be harder (because the values will be stringified). They would need to create a default policy that would bless all URLs.

@gaearon
Copy link
Collaborator

gaearon commented Sep 11, 2019

This is getting close. Exciting!

One more thing I’d like to ask you is to compare yarn build --type=UMD_PROD --pretty react-dom output in build folder before and after this PR. I’d expect the diff to be empty or minimal since the flag is off.

Also, do we have a server rendering story?

Copy link
Contributor Author

@Siegrift Siegrift left a comment

Choose a reason for hiding this comment

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

I compared the build using diff utility using and it looks pretty similar. Here is the result of the comparison.

There is also a server rendering draft PR. However, the reason of it being a draft was that getProprtyType function defintion might have changed at that time. This has been resolved and the function definition is going to remain the same. However, currently the polyfill doesn't work in nodejs. But I have already fired a PR will make it work.
So basically, the implementation of the PR is ready and can be reviewed.

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

One minor nit and I'll merge this on Monday

packages/react-dom/src/client/setInnerHTML.js Outdated Show resolved Hide resolved
@Siegrift
Copy link
Contributor Author

Resolved the minors :) looking forward to 🚢

@gaearon gaearon merged commit b8d079b into facebook:master Sep 16, 2019
@gaearon
Copy link
Collaborator

gaearon commented Sep 16, 2019

Looks good, thanks.

@gaearon
Copy link
Collaborator

gaearon commented Sep 16, 2019

@Siegrift @koto Can you please review this? #16795 I've made a few changes.

kassens pushed a commit that referenced this pull request Jan 30, 2023
…26016)

## Summary

The flag was first added in #16157 and was rolled out to employees in
D17430095. #25997 removed this flag because it wasn't dynamically set to
a value in www. The www side was mistakenly removed in D41851685 due to
deprecation of a TypedJSModule but we still want to keep this flag, so
let's add it back in + add a GK on the www side to match the previous
rollout.

See D42574435 for the dynamic value change in www

## How did you test this change?

```
yarn test
yarn test --prod
```
github-actions bot pushed a commit that referenced this pull request Jan 30, 2023
…26016)

## Summary

The flag was first added in #16157 and was rolled out to employees in
D17430095. #25997 removed this flag because it wasn't dynamically set to
a value in www. The www side was mistakenly removed in D41851685 due to
deprecation of a TypedJSModule but we still want to keep this flag, so
let's add it back in + add a GK on the www side to match the previous
rollout.

See D42574435 for the dynamic value change in www

## How did you test this change?

```
yarn test
yarn test --prod
```

DiffTrain build for [48b687f](48b687f)
[View git log for this commit](https://github.com/facebook/react/commits/48b687fc95a172cec8f305312a27d105e5719581)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants