Skip to content

Commit dec6791

Browse files
authored
Merge pull request #1784 from kenearley/target-blank-dynamic-link
Target blank dynamic link should fail
2 parents 368346e + d073dc7 commit dec6791

File tree

3 files changed

+70
-46
lines changed

3 files changed

+70
-46
lines changed

docs/rules/jsx-no-target-blank.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,28 @@ When creating a JSX element that has an `a` tag, it is often desired to have
44
the link open in a new tab using the `target='_blank'` attribute. Using this
55
attribute unaccompanied by `rel='noreferrer noopener'`, however, is a severe
66
security vulnerability ([see here for more details](https://mathiasbynens.github.io/rel-noopener))
7-
This rules requires that you accompany all `target='_blank'` attributes with `rel='noreferrer noopener'`.
7+
This rules requires that you accompany `target='_blank'` attributes with `rel='noreferrer noopener'`.
88

99
## Rule Details
1010

11-
The following patterns are considered errors:
11+
This rule aims to prevent user generated links from creating security vulerabilities by requiring
12+
`rel='noreferrer noopener'` for external links, and optionally any dynamically generated links.
13+
14+
## Rule Options
15+
16+
There are two main options for the rule:
17+
18+
* `{"enforceDynamicLinks": "always"}` enforces the rule if the href is a dyanamic link (default)
19+
* `{"enforceDynamicLinks": "never"}` does not enforce the rule if the href is a dyamic link
20+
21+
22+
### always (default)
23+
24+
When {"enforceDynamicLinks": "always"} is set, the following patterns are considered errors:
1225

1326
```jsx
1427
var Hello = <a target='_blank' href="http://example.com/"></a>
28+
var Hello = <a target='_blank' href={ dynamicLink }></a>
1529
```
1630

1731
The following patterns are **not** considered errors:
@@ -24,6 +38,14 @@ var Hello = <a target='_blank' href="/absolute/path/in/the/host"></a>
2438
var Hello = <a></a>
2539
```
2640

41+
### never
42+
43+
When {"enforceDynamicLinks": "never"} is set, the following patterns are **not** considered errors:
44+
45+
```jsx
46+
var Hello = <a target='_blank' href={ dynamicLink }></a>
47+
```
48+
2749
## When Not To Use It
2850

2951
If you do not have any external links, you can disable this rule

lib/rules/jsx-no-target-blank.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ function hasExternalLink(element) {
2323
/^(?:\w+:|\/\/)/.test(attr.value.value));
2424
}
2525

26+
function hasDynamicLink(element) {
27+
return element.attributes.some(attr => attr.name &&
28+
attr.name.name === 'href' &&
29+
attr.value.type === 'JSXExpressionContainer');
30+
}
31+
2632
function hasSecureRel(element) {
2733
return element.attributes.find(attr => {
2834
if (attr.type === 'JSXAttribute' && attr.name.name === 'rel') {
@@ -41,21 +47,28 @@ module.exports = {
4147
recommended: true,
4248
url: docsUrl('jsx-no-target-blank')
4349
},
44-
schema: []
50+
schema: [{
51+
type: 'object',
52+
properties: {
53+
enforceDynamicLinks: {
54+
enum: ['always', 'never']
55+
}
56+
},
57+
additionalProperties: false
58+
}]
4559
},
4660

4761
create: function(context) {
62+
const configuration = context.options[0] || {};
63+
const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always';
64+
4865
return {
4966
JSXAttribute: function(node) {
50-
if (node.parent.name.name !== 'a') {
67+
if (node.parent.name.name !== 'a' || !isTargetBlank(node) || hasSecureRel(node.parent)) {
5168
return;
5269
}
5370

54-
if (
55-
isTargetBlank(node) &&
56-
hasExternalLink(node.parent) &&
57-
!hasSecureRel(node.parent)
58-
) {
71+
if (hasExternalLink(node.parent) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))) {
5972
context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' +
6073
'is a security risk: see https://mathiasbynens.github.io/rel-noopener');
6174
}

tests/lib/rules/jsx-no-target-blank.js

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ const parserOptions = {
2525
// ------------------------------------------------------------------------------
2626

2727
const ruleTester = new RuleTester({parserOptions});
28+
const defaultErrors = [{
29+
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
30+
' see https://mathiasbynens.github.io/rel-noopener'
31+
}];
32+
2833
ruleTester.run('jsx-no-target-blank', rule, {
2934
valid: [
3035
{code: '<a href="foobar"></a>'},
@@ -38,61 +43,45 @@ ruleTester.run('jsx-no-target-blank', rule, {
3843
{code: '<a target="_blank" rel={relValue}></a>'},
3944
{code: '<a target={targetValue} rel="noopener noreferrer"></a>'},
4045
{code: '<a target={targetValue} href="relative/path"></a>'},
41-
{code: '<a target={targetValue} href="/absolute/path"></a>'}
46+
{code: '<a target={targetValue} href="/absolute/path"></a>'},
47+
{
48+
code: '<a target="_blank" href={ dynamicLink }></a>',
49+
options: [{enforceDynamicLinks: 'never'}]
50+
}
4251
],
4352
invalid: [{
4453
code: '<a target="_blank" href="http://example.com"></a>',
45-
errors: [{
46-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
47-
' see https://mathiasbynens.github.io/rel-noopener'
48-
}]
54+
errors: defaultErrors
4955
}, {
5056
code: '<a target="_blank" rel="" href="http://example.com"></a>',
51-
errors: [{
52-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
53-
' see https://mathiasbynens.github.io/rel-noopener'
54-
}]
57+
errors: defaultErrors
5558
}, {
5659
code: '<a target="_blank" rel="noopenernoreferrer" href="http://example.com"></a>',
57-
errors: [{
58-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
59-
' see https://mathiasbynens.github.io/rel-noopener'
60-
}]
60+
errors: defaultErrors
6161
}, {
6262
code: '<a target="_BLANK" href="http://example.com"></a>',
63-
errors: [{
64-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
65-
' see https://mathiasbynens.github.io/rel-noopener'
66-
}]
63+
errors: defaultErrors
6764
}, {
6865
code: '<a target="_blank" href="//example.com"></a>',
69-
errors: [{
70-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
71-
' see https://mathiasbynens.github.io/rel-noopener'
72-
}]
66+
errors: defaultErrors
7367
}, {
7468
code: '<a target="_blank" href="//example.com" rel={true}></a>',
75-
errors: [{
76-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
77-
' see https://mathiasbynens.github.io/rel-noopener'
78-
}]
69+
errors: defaultErrors
7970
}, {
8071
code: '<a target="_blank" href="//example.com" rel={3}></a>',
81-
errors: [{
82-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
83-
' see https://mathiasbynens.github.io/rel-noopener'
84-
}]
72+
errors: defaultErrors
8573
}, {
8674
code: '<a target="_blank" href="//example.com" rel={null}></a>',
87-
errors: [{
88-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
89-
' see https://mathiasbynens.github.io/rel-noopener'
90-
}]
75+
errors: defaultErrors
9176
}, {
9277
code: '<a target="_blank" href="//example.com" rel></a>',
93-
errors: [{
94-
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
95-
' see https://mathiasbynens.github.io/rel-noopener'
96-
}]
78+
errors: defaultErrors
79+
}, {
80+
code: '<a target="_blank" href={ dynamicLink }></a>',
81+
errors: defaultErrors
82+
}, {
83+
code: '<a target="_blank" href={ dynamicLink }></a>',
84+
options: [{enforceDynamicLinks: 'always'}],
85+
errors: defaultErrors
9786
}]
9887
});

0 commit comments

Comments
 (0)