-
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
Use a Symbol to tag every ReactElement #4832
Conversation
} else { | ||
this._store.validated = false; |
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.
Fixed this bug in older browsers.
this._store.validated = false; | ||
element._store.validated = false; | ||
element._self = self; | ||
element._source = source; |
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 used a single underscore here for consistency with _owner
and _store
while the properties in config
use the double underscore.
Fixes facebook#3473 I tag each React element with `$$typeof: Symbol.for('react.element')`. We need this to be able to safely distinguish these from plain objects that might have come from user provided JSON. The idiomatic JavaScript way of tagging an object is for it to inherent some prototype and then use `instanceof` to test for it. However, this has limitations since it doesn't work with value types which require `typeof` checks. They also don't work across realms. Which is why there are alternative tag checks like `Array.isArray` or the `toStringTag`. Another problem is that different instances of React that might have been created not knowing about eachother. npm tends to make this kind of problem occur a lot. Additionally, it is our hope that ReactElement will one day be specified in terms of a "Value Type" style record instead of a plain Object. This Value Types proposal by @nikomatsakis is currently on hold but does satisfy all these requirements: https://github.com/nikomatsakis/typed-objects-explainer/blob/master/valuetypes.md#the-typeof-operator Additionally, there is already a system for coordinating tags across module systems and even realms in ES6. Namely using `Symbol.for`. Currently these objects are not able to transfer between Workers but there is nothing preventing that from being possible in the future. You could imagine even `Symbol.for` working across Worker boundaries. You could also build a system that coordinates Symbols and Value Types from server to client or through serialized forms. That's beyond the scope of React itself, and if it was built it seems like it would belong with the `Symbol` system. A system could override the `Symbol.for('react.element')` to return a plain yet cryptographically random or unique number. That would allow ReactElements to pass through JSON without risking the XSS issue. The fallback solution is a plain well-known number. This makes it unsafe with regard to the XSS issue described in facebook#3473. We could have used a much more convoluted solution to protect against JSON specifically but that would require some kind of significant coordination, or change the check to do a `typeof element.$$typeof === 'function'` check which would not make it unique to React. It seems cleaner to just use a fixed number since the protection is just a secondary layer anyway. I'm not sure if this is the right tradeoff. In short, if you want the XSS protection, use a proper Symbol polyfill. Finally, the reason for calling it `$$typeof` is to avoid confusion with `.type` and the use case is to add a tag that the `typeof` operator would refer to. I would use `@@typeof` but that seems to deopt in JSC. I also don't use `__typeof` because this is more than a framework private. It should really be part of the polyfilling layer.
Use a Symbol to tag every ReactElement
This is a great solution to the XSS issue! Just need to make sure no-one adds a way to run symbol.for via JSON parsing :) |
I do believe this is very soon to change with the latest Safari for iOS 9 and El Capitan (16th September and 30th September according to yesterday's Apple event). :) |
// The Symbol used to tag the ReactElement type. If there is no native Symbol | ||
// nor polyfill, then a plain number is used for performance. | ||
var TYPE_SYMBOL = (typeof Symbol === 'function' && Symbol.for && | ||
Symbol.for('react.element')) || 0xeac7; |
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.
0xeac7 0x0cc5!
(edited for hexiness, thanks @RReverser for the tip)
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.
@gaearon o
is not valid hex char :P
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.
someone please tell me why 0xeac7? I can't sleep
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.
0xeac7
sorta kinda looks like React
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.
why not use Math.random()
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.
It doesn't cover postMessage though.
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.
We want postMessage
to work, right? In my testing, it actually does, since structured cloning supports much more advanced serialization than JSON.stringify().
This is confirmed in the w3c tests: https://github.com/w3c/web-platform-tests/blob/master/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/structured-clone-message.html#L32
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.
We don't want it to work cross domain by default. That's still a security risk.
However, we do want it to work if you're able to coordinate the Symbol across the worker boundaries.
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 argue that if you're deliberately catching cross-origin postMessage and inserting objects directly into your views without validation, you're asking for it.
This would be nice for webworkers though, as Symbols don't transfer via structured cloning (so you'd have to explicitly catch elements and re-tag them).
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.
See #3473 for more context.
It is too easy to expect a string and not realize it might be an object:
loadString(function(stringData) {
React.render(<div>Content: {stringData}</div>, container);
});
Adds minimal support for react 0.14 without affecting 0.13 users. React 0.14 removes React.initializeTouchEvents as it's no longer needed. _isReactElement was removed and replaced with the $$typeof field which contains an ES6 symbol if supported or a number. Warnings are visible for 0.14 which can be stopped by using ReactDOM for render and findDOMNode etc. https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#breaking-changes facebook/react#4832
Adds minimal support for react 0.14 without affecting 0.13 users. React 0.14 removes React.initializeTouchEvents as it's no longer needed. _isReactElement was removed and replaced with the $$typeof field which contains an ES6 symbol if supported or a number. Warnings are visible for 0.14 which can be stopped by using ReactDOM for render and findDOMNode etc. https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#breaking-changes facebook/react#4832
This closes the XSS hole on olders browsers that don't support Symbol. More discussion: facebook#4832 (comment)
This closes the XSS hole on older browsers that don't support Symbol. More discussion: facebook#4832 (comment)
Fixes #3473
I tag each React element with
$$typeof: Symbol.for('react.element')
. We needthis to be able to safely distinguish these from plain objects that might have
come from user provided JSON.
The idiomatic JavaScript way of tagging an object is for it to inherent some
prototype and then use
instanceof
to test for it.However, this has limitations since it doesn't work with value types which
require
typeof
checks. They also don't work across realms. Which is why thereare alternative tag checks like
Array.isArray
or thetoStringTag
. Anotherproblem is that different instances of React that might have been created not knowing about eachother. npm tends to make this kind of problem occur a lot.
Additionally, it is our hope that ReactElement will one day be specified in
terms of a "Value Type" style record instead of a plain Object.
This Value Types proposal by @nikomatsakis is currently on hold but does satisfy all these requirements:
https://github.com/nikomatsakis/typed-objects-explainer/blob/master/valuetypes.md#the-typeof-operator
Additionally, there is already a system for coordinating tags across module
systems and even realms in ES6. Namely using
Symbol.for
. (thx @sebmck)Currently these objects are not able to transfer between Workers but there is
nothing preventing that from being possible in the future. You could imagine
even
Symbol.for
working across Worker boundaries. You could also build asystem that coordinates Symbols and Value Types from server to client or through
serialized forms. That's beyond the scope of React itself, and if it was built
it seems like it would belong with the
Symbol
system. A system could overridethe
Symbol.for('react.element')
to return a plain yetcryptographically random or unique number. That would allow ReactElements to
pass through JSON without risking the XSS issue.
The fallback solution is a plain well-known number. This makes it unsafe with
regard to the XSS issue described in #3473. We could have used a much more
convoluted solution to protect against JSON specifically but that would require
some kind of significant coordination, or change the check to do a
typeof element.$$typeof === 'function'
check which would not make it unique toReact. It seems cleaner to just use a fixed number since the protection is just
a secondary layer anyway. I'm not sure if this is the right tradeoff.
In short, if you want the XSS protection, use a proper Symbol polyfill.
Finally, the reason for calling it
$$typeof
is to avoid confusion with.type
and the use case is to add a tag that the
typeof
operator would refer to.I would use
@@typeof
but that seems to deopt in JSC. I also don't use__typeof
because this is more than a framework private. It should really bepart of the polyfilling layer.
Test Plan:
examples/basic/index.html
works in Chrome and Firefox which both have native Symbols - and in Safari which doesn't.React.createElement('div').$$typeof // Symbol(react.element)
.