@samitouri / QOS-React-2 / commits / db489a4ade

Only do runtime validation of tag names when createElement is not used

We introduced runtime validation of tag names because we used to generate HTML that was supposed to be inserted into a HTML string which could've been an XSS attack. However, these days we use document.createElement in most cases. That already does its internal validation in the browser which throws. We're now double validating it. Stack still has a path where innerHTML is used and we still need it there. However in Fiber we can remove it completely.

Sebastian Markbage committed Dec 12, 2016 at 19:38 UTC db489a4adeea8edd2aab1d47b3ce55dd28198e4b
4 files changed +21 -22
scripts/fiber/tests-passing.txt
+2
@@ -664,6 +664,8 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
664 * should properly escape text content and attributes values
665 * unmounts children before unsetting DOM node info
666 * should warn about the `onScroll` issue when unsupported (IE8)
667 +* should throw when an invalid tag name is used server-side
668 +* should throw when an attack vector is used server-side
669 * should throw when an invalid tag name is used
670 * should throw when an attack vector is used
671 * should warn about props that are no longer supported
src/renderers/dom/fiber/ReactDOMFiberComponent.js
-17
@@ -279,21 +279,6 @@ var voidElementTags = {
279 ...omittedCloseTags,
280 };
281
282 -// We accept any tag to be rendered but since this gets injected into arbitrary
283 -// HTML, we want to make sure that it's a safe tag.
284 -// http://www.w3.org/TR/REC-xml/#NT-Name
285 -
286 -var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset
287 -var validatedTagCache = {};
288 -var hasOwnProperty = {}.hasOwnProperty;
289 -
290 -function validateDangerousTag(tag) {
291 - if (!hasOwnProperty.call(validatedTagCache, tag)) {
292 - invariant(VALID_TAG_REGEX.test(tag), 'Invalid tag: %s', tag);
293 - validatedTagCache[tag] = true;
294 - }
295 -}
296 -
282 function isCustomComponent(tagName, props) {
283 return tagName.indexOf('-') >= 0 || props.is != null;
284 }
@@ -488,8 +473,6 @@ var ReactDOMFiberComponent = {
473 rootContainerElement : Element,
474 parentNamespace : string | null
475 ) : Element {
491 - validateDangerousTag(type);
492 -
476 // We create tags in the namespace of their parent container, except HTML
477 // tags get no namespace.
478 var ownerDocument = rootContainerElement.ownerDocument;
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
+18 -4
@@ -1207,23 +1207,37 @@ describe('ReactDOMComponent', () => {
1207 });
1208
1209 describe('tag sanitization', () => {
1210 - it('should throw when an invalid tag name is used', () => {
1210 + it('should throw when an invalid tag name is used server-side', () => {
1211 var hackzor = React.createElement('script tag');
1212 expect(
1213 - () => ReactTestUtils.renderIntoDocument(hackzor)
1213 + () => ReactDOMServer.renderToString(hackzor)
1214 ).toThrowError(
1215 'Invalid tag: script tag'
1216 );
1217 });
1218
1219 - it('should throw when an attack vector is used', () => {
1219 + it('should throw when an attack vector is used server-side', () => {
1220 var hackzor = React.createElement('div><img /><div');
1221 expect(
1222 - () => ReactTestUtils.renderIntoDocument(hackzor)
1222 + () => ReactDOMServer.renderToString(hackzor)
1223 ).toThrowError(
1224 'Invalid tag: div><img /><div'
1225 );
1226 });
1227 +
1228 + it('should throw when an invalid tag name is used', () => {
1229 + var hackzor = React.createElement('script tag');
1230 + expect(
1231 + () => ReactTestUtils.renderIntoDocument(hackzor)
1232 + ).toThrow();
1233 + });
1234 +
1235 + it('should throw when an attack vector is used', () => {
1236 + var hackzor = React.createElement('div><img /><div');
1237 + expect(
1238 + () => ReactTestUtils.renderIntoDocument(hackzor)
1239 + ).toThrow();
1240 + });
1241 });
1242
1243 describe('nesting validation', () => {
src/renderers/dom/stack/client/ReactDOMComponent.js
+1 -1
@@ -413,7 +413,6 @@ var globalIdCounter = 1;
413 */
414 function ReactDOMComponent(element) {
415 var tag = element.type;
416 - validateDangerousTag(tag);
416 this._currentElement = element;
417 this._tag = tag.toLowerCase();
418 this._namespaceURI = null;
@@ -605,6 +604,7 @@ ReactDOMComponent.Mixin = {
604 this._createInitialChildren(transaction, props, context, lazyTree);
605 mountImage = lazyTree;
606 } else {
607 + validateDangerousTag(this._tag);
608 var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
609 var tagContent = this._createContentMarkup(transaction, props, context);
610 if (!tagContent && omittedCloseTags[this._tag]) {