diff --git a/docs/rules/jsx-no-target-blank.md b/docs/rules/jsx-no-target-blank.md index ae92669f39..906e852b4a 100644 --- a/docs/rules/jsx-no-target-blank.md +++ b/docs/rules/jsx-no-target-blank.md @@ -4,14 +4,28 @@ When creating a JSX element that has an `a` tag, it is often desired to have the link open in a new tab using the `target='_blank'` attribute. Using this attribute unaccompanied by `rel='noreferrer noopener'`, however, is a severe security vulnerability ([see here for more details](https://mathiasbynens.github.io/rel-noopener)) -This rules requires that you accompany all `target='_blank'` attributes with `rel='noreferrer noopener'`. +This rules requires that you accompany `target='_blank'` attributes with `rel='noreferrer noopener'`. ## Rule Details -The following patterns are considered errors: +This rule aims to prevent user generated links from creating security vulerabilities by requiring +`rel='noreferrer noopener'` for external links, and optionally any dynamically generated links. + +## Rule Options + +There are two main options for the rule: + +* `{"enforceDynamicLinks": "always"}` enforces the rule if the href is a dyanamic link (default) +* `{"enforceDynamicLinks": "never"}` does not enforce the rule if the href is a dyamic link + + +### always (default) + +When {"enforceDynamicLinks": "always"} is set, the following patterns are considered errors: ```jsx var Hello = +var Hello = ``` The following patterns are **not** considered errors: @@ -24,6 +38,14 @@ var Hello = var Hello = ``` +### never + +When {"enforceDynamicLinks": "never"} is set, the following patterns are **not** considered errors: + +```jsx +var Hello = +``` + ## When Not To Use It If you do not have any external links, you can disable this rule diff --git a/lib/rules/jsx-no-target-blank.js b/lib/rules/jsx-no-target-blank.js index a5b34c2d19..e4492874e0 100644 --- a/lib/rules/jsx-no-target-blank.js +++ b/lib/rules/jsx-no-target-blank.js @@ -23,6 +23,12 @@ function hasExternalLink(element) { /^(?:\w+:|\/\/)/.test(attr.value.value)); } +function hasDynamicLink(element) { + return element.attributes.some(attr => attr.name && + attr.name.name === 'href' && + attr.value.type === 'JSXExpressionContainer'); +} + function hasSecureRel(element) { return element.attributes.find(attr => { if (attr.type === 'JSXAttribute' && attr.name.name === 'rel') { @@ -41,21 +47,28 @@ module.exports = { recommended: true, url: docsUrl('jsx-no-target-blank') }, - schema: [] + schema: [{ + type: 'object', + properties: { + enforceDynamicLinks: { + enum: ['always', 'never'] + } + }, + additionalProperties: false + }] }, create: function(context) { + const configuration = context.options[0] || {}; + const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'; + return { JSXAttribute: function(node) { - if (node.parent.name.name !== 'a') { + if (node.parent.name.name !== 'a' || !isTargetBlank(node) || hasSecureRel(node.parent)) { return; } - if ( - isTargetBlank(node) && - hasExternalLink(node.parent) && - !hasSecureRel(node.parent) - ) { + if (hasExternalLink(node.parent) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))) { context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' + 'is a security risk: see https://mathiasbynens.github.io/rel-noopener'); } diff --git a/tests/lib/rules/jsx-no-target-blank.js b/tests/lib/rules/jsx-no-target-blank.js index a0c756cbcc..aba9d080bb 100644 --- a/tests/lib/rules/jsx-no-target-blank.js +++ b/tests/lib/rules/jsx-no-target-blank.js @@ -25,6 +25,11 @@ const parserOptions = { // ------------------------------------------------------------------------------ const ruleTester = new RuleTester({parserOptions}); +const defaultErrors = [{ + message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + + ' see https://mathiasbynens.github.io/rel-noopener' +}]; + ruleTester.run('jsx-no-target-blank', rule, { valid: [ {code: ''}, @@ -38,61 +43,45 @@ ruleTester.run('jsx-no-target-blank', rule, { {code: ''}, {code: ''}, {code: ''}, - {code: ''} + {code: ''}, + { + code: '', + options: [{enforceDynamicLinks: 'never'}] + } ], invalid: [{ code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors }, { code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors }, { code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors }, { code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors }, { code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors }, { code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors }, { code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors }, { code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors }, { code: '', - errors: [{ - message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' + - ' see https://mathiasbynens.github.io/rel-noopener' - }] + errors: defaultErrors + }, { + code: '', + errors: defaultErrors + }, { + code: '', + options: [{enforceDynamicLinks: 'always'}], + errors: defaultErrors }] });