Skip to content

Commit

Permalink
New: add vue/no-template-no-target-blank rule (#1086)
Browse files Browse the repository at this point in the history
* New: add `vue/no-template-no-target-blank` rule

* Fix document for no-template-target-blank
  • Loading branch information
sosukesuzuki committed Apr 21, 2020
1 parent 7609be6 commit 8d3c99a
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -274,6 +274,7 @@ For example:
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
| [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | |
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | |
| [vue/no-template-target-blank](./no-template-target-blank.md) | disallow target="_blank" attribute without rel="noopener noreferrer" | |
| [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: |
| [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
Expand Down
99 changes: 99 additions & 0 deletions docs/rules/no-template-target-blank.md
@@ -0,0 +1,99 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-template-target-blank
description: disallow target="_blank" attribute without rel="noopener noreferrer"
---
# vue/no-template-target-blank
> disallow target="_blank" attribute without rel="noopener noreferrer"
## :book: Rule Details

This rule disallows using `target="_blank"` attribute without `rel="noopener noreferrer"` to avoid a security vulnerability([see here for more details](https://mathiasbynens.github.io/rel-noopener/)).

<eslint-code-block :rules="{'vue/no-template-target-blank': ['error']}">

```vue
<template>
<!-- ✓ Good -->
<a link="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
<!-- ✗ BAD -->
<a link="http://example.com" target="_blank" >link</a>
</temlate>
```

## :wrench: Options

```json
{
"vue/no-template-target-blank": ["error", {
"allowReferrer": true,
"enforceDynamicLinks": "always"
}]
}
```

- `allowReferrer` ... If `true`, does not require noreferrer.default `false`
- `enforceDynamicLinks ("always" | "never")` ... If `always`, enforces the rule if the href is a dynamic link. default `always`.

### `{ allowReferrer: false }` (default)

<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { allowReferrer: false }]}">

```vue
<template>
<!-- ✓ Good -->
<a link="http://example.com" target="_blank" rel="noopener noreferrer">link</a>
<!-- ✗ BAD -->
<a link="http://example.com" target="_blank" rel="noopener">link</a>
</temlate>
```

### `{ allowReferrer: true }`

<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { allowReferrer: true }]}">

```vue
<template>
<!-- ✓ Good -->
<a link="http://example.com" target="_blank" rel="noopener">link</a>
<!-- ✗ BAD -->
<a link="http://example.com" target="_blank" >link</a>
</temlate>
```

### `{ "enforceDynamicLinks": "always" }` (default)

<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { enforceDynamicLinks: 'never' }]}">

```vue
<template>
<!-- ✓ Good -->
<a :link="link" target="_blank" rel="noopener noreferrer">link</a>
<!-- ✗ BAD -->
<a :link="link" target="_blank">link</a>
</temlate>
```

### `{ "enforceDynamicLinks": "never" }`

<eslint-code-block :rules="{'vue/no-template-target-blank': ['error', { enforceDynamicLinks: 'never' }]}">

```vue
<template>
<!-- ✓ Good -->
<a :link="link" target="_blank">link</a>
<!-- ✗ BAD -->
<a link="http://example.com" target="_blank" >link</a>
</temlate>
```

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-template-target-blank.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-template-target-blank.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -66,6 +66,7 @@ module.exports = {
'no-static-inline-styles': require('./rules/no-static-inline-styles'),
'no-template-key': require('./rules/no-template-key'),
'no-template-shadow': require('./rules/no-template-shadow'),
'no-template-target-blank': require('./rules/no-template-target-blank'),
'no-textarea-mustache': require('./rules/no-textarea-mustache'),
'no-unsupported-features': require('./rules/no-unsupported-features'),
'no-unused-components': require('./rules/no-unused-components'),
Expand Down
111 changes: 111 additions & 0 deletions lib/rules/no-template-target-blank.js
@@ -0,0 +1,111 @@
/**
* @fileoverview disallow target="_blank" attribute without rel="noopener noreferrer"
* @author Sosukesuzuki
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const utils = require('../utils')

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
function isTargetBlank (node) {
return node.key &&
node.key.name === 'target' &&
node.value &&
node.value.value === '_blank'
}

function hasSecureRel (node, allowReferrer) {
return node.attributes.some(attr => {
if (attr.key && attr.key.name === 'rel') {
const tags = attr.value && attr.value.value.toLowerCase().split(' ')
return tags &&
tags.includes('noopener') &&
(allowReferrer || tags.includes('noreferrer'))
} else {
return false
}
})
}

function hasExternalLink (node) {
return node.attributes.some(attr =>
attr.key &&
attr.key.name === 'href' &&
attr.value && /^(?:\w+:|\/\/)/.test(attr.value.value)
)
}

function hasDynamicLink (node) {
return node.attributes.some(attr =>
attr.key &&
attr.key.type === 'VDirectiveKey' &&
attr.key.name &&
attr.key.name.name === 'bind' &&
attr.key.argument &&
attr.key.argument.name === 'href'
)
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'problem',
docs: {
description:
'disallow target="_blank" attribute without rel="noopener noreferrer"',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-template-target-blank.html'
},
schema: [{
type: 'object',
properties: {
allowReferrer: {
type: 'boolean'
},
enforceDynamicLinks: {
enum: ['always', 'never']
}
},
additionalProperties: false
}]
},

/**
* Creates AST event handlers for no-template-target-blank
*
* @param {RuleContext} context - The rule context.
* @returns {Object} AST event handlers.
*/
create (context) {
const configuration = context.options[0] || {}
const allowReferrer = configuration.allowReferrer || false
const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'

return utils.defineTemplateBodyVisitor(context, {
'VAttribute' (node) {
if (!isTargetBlank(node) || hasSecureRel(node.parent, allowReferrer)) {
return
}

const hasDangerHref = hasExternalLink(node.parent) ||
(enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))

if (hasDangerHref) {
context.report({
node,
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk.'
})
}
}
})
}
}
68 changes: 68 additions & 0 deletions tests/lib/rules/no-template-target-blank.js
@@ -0,0 +1,68 @@
/**
* @fileoverview disallow target="_blank" attribute without rel="noopener noreferrer"
* @author Sosukesuzuki
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const rule = require('../../../lib/rules/no-template-target-blank')

const RuleTester = require('eslint').RuleTester

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const ruleTester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: { ecmaVersion: 2015 }
})

ruleTester.run('no-template-target-blank', rule, {
valid: [
{ code: '<template><a>link</a></template>' },
{ code: '<template><a attr>link</a></template>' },
{ code: '<template><a target>link</a></template>' },
{ code: '<template><a href="https://eslint.vuejs.org">link</a></template>' },
{ code: '<template><a :href="link">link</a></template>' },
{ code: '<template><a :href="link" target="_blank" rel="noopener noreferrer">link</a></template>' },
{ code: '<template><a href="https://eslint.vuejs.org" target="_blank" rel="noopener noreferrer">link</a></template>' },
{
code: '<template><a href="https://eslint.vuejs.org" target="_blank" rel="noopener">link</a></template>',
options: [{ allowReferrer: true }]
},
{ code: '<template><a href="/foo" target="_blank">link</a></template>' },
{ code: '<template><a href="/foo" target="_blank" rel="noopener noreferrer">link</a></template>' },
{ code: '<template><a href="foo/bar" target="_blank">link</a></template>' },
{ code: '<template><a href="foo/bar" target="_blank" rel="noopener noreferrer">link</a></template>' },
{
code: '<template><a :href="link" target="_blank">link</a></template>',
options: [{ enforceDynamicLinks: 'never' }]
}
],
invalid: [
{
code: '<template><a href="https://eslint.vuejs.org" target="_blank">link</a></template>',
errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
},
{
code: '<template><a href="https://eslint.vuejs.org" target="_blank" rel="noopenernoreferrer">link</a></template>',
errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
},
{
code: '<template><a :href="link" target="_blank" rel=3>link</a></template>',
errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
},
{
code: '<template><a :href="link" target="_blank">link</a></template>',
errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
},
{
code: '<template><a href="https://eslint.vuejs.org" target="_blank" rel="noopener">link</a></template>',
errors: ['Using target="_blank" without rel="noopener noreferrer" is a security risk.']
}
]
})

0 comments on commit 8d3c99a

Please sign in to comment.