diff --git a/README.md b/README.md index f6cd505a54..651bf6956e 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ Enable the rules that you would like to use. * [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md): Prevent comments from being inserted as text nodes * [react/jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX * [react/jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings +* [react/jsx-no-script-url](docs/rules/jsx-no-script-url.md): Prevent usage of `javascript:` URLs * [react/jsx-no-target-blank](docs/rules/jsx-no-target-blank.md): Prevent usage of unsafe `target='_blank'` * [react/jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX * [react/jsx-no-useless-fragment](docs/rules/jsx-no-useless-fragment.md): Disallow unnescessary fragments (fixable) diff --git a/docs/rules/jsx-no-script-url.md b/docs/rules/jsx-no-script-url.md new file mode 100644 index 0000000000..53f9202ef4 --- /dev/null +++ b/docs/rules/jsx-no-script-url.md @@ -0,0 +1,22 @@ +# Prevent usage of `javascript:` URLs (react/jsx-no-script-url) + +**In React 16.9** any URLs starting with `javascript:` [scheme](https://wiki.whatwg.org/wiki/URL_schemes#javascript:_URLs) log a warning. +React considers the pattern as a dangerous attack surface, see [details](https://reactjs.org/blog/2019/08/08/react-v16.9.0.html#deprecating-javascript-urls). +**In a future major release**, React will throw an error if it encounters a `javascript:` URL. + +## Rule Details + +The following patterns are considered warnings: + +```jsx + + + +``` + +The following patterns are **not** considered warnings: + +```jsx + + +``` diff --git a/index.js b/index.js index c9511bb1e3..6db76d0aee 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,7 @@ const allRules = { 'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'), 'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'), 'jsx-no-literals': require('./lib/rules/jsx-no-literals'), + 'jsx-no-script-url': require('./lib/rules/jsx-no-script-url'), 'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'), 'jsx-no-useless-fragment': require('./lib/rules/jsx-no-useless-fragment'), 'jsx-one-expression-per-line': require('./lib/rules/jsx-one-expression-per-line'), diff --git a/lib/rules/jsx-no-script-url.js b/lib/rules/jsx-no-script-url.js new file mode 100644 index 0000000000..660672ca90 --- /dev/null +++ b/lib/rules/jsx-no-script-url.js @@ -0,0 +1,49 @@ +/** + * @fileoverview Prevent usage of `javascript:` URLs + * @author Sergei Startsev + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function isHref(attr) { + return attr.name && + attr.name.name === 'href'; +} + +// https://github.com/facebook/react/blob/d0ebde77f6d1232cefc0da184d731943d78e86f2/packages/react-dom/src/shared/sanitizeURL.js#L30 +/* eslint-disable-next-line max-len, no-control-regex */ +const isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i; + +function hasJavaScriptProtocol(attr) { + return attr.value.type === 'Literal' && + isJavaScriptProtocol.test(attr.value.value); +} + +module.exports = { + meta: { + docs: { + description: 'Forbid `javascript:` URLs', + category: 'Best Practices', + recommended: false, + url: docsUrl('jsx-no-script-url') + }, + schema: [] + }, + + create(context) { + return { + JSXAttribute(node) { + if (node.parent.name.name === 'a' && isHref(node) && hasJavaScriptProtocol(node)) { + context.report(node, 'A future version of React will block javascript: URLs as a security precaution. ' + + 'Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead.'); + } + } + }; + } +}; diff --git a/tests/lib/rules/jsx-no-script-url.js b/tests/lib/rules/jsx-no-script-url.js new file mode 100644 index 0000000000..a6aeb6c650 --- /dev/null +++ b/tests/lib/rules/jsx-no-script-url.js @@ -0,0 +1,53 @@ +/** + * @fileoverview Prevent usage of `javascript:` URLs + * @author Sergei Startsev + */ + +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const RuleTester = require('eslint').RuleTester; +const rule = require('../../../lib/rules/jsx-no-script-url'); + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } +}; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({parserOptions}); +const defaultErrors = [{ + message: 'A future version of React will block javascript: URLs as a security precaution. ' + + 'Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead.' +}]; + +ruleTester.run('jsx-no-script-url', rule, { + valid: [ + {code: ''}, + {code: ''}, + {code: ''}, + {code: ''}, + {code: ''}, + {code: ''}, + {code: ''} + ], + invalid: [{ + code: '', + errors: defaultErrors + }, { + code: '', + errors: defaultErrors + }, { + code: '', + errors: defaultErrors + }] +});