Skip to content

Commit

Permalink
⭐ new(rule): add no-v-html rule
Browse files Browse the repository at this point in the history
  • Loading branch information
kazupon committed Mar 17, 2019
1 parent 7612dfd commit f9636da
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
| Rule ID | Description | |
|:--------|:------------|:---|
| [vue-i18n/<wbr>no-missing-keys](./no-missing-keys.html) | disallow missing locale message key at localization methods | :star: |
| [vue-i18n/<wbr>no-v-html](./no-v-html.html) | disallow use of localization methods on v-html to prevent XSS attack | :star: |

## Best Practices

Expand Down
67 changes: 67 additions & 0 deletions docs/rules/no-v-html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# vue-i18n/no-v-html

> disallow use of localization methods on v-html to prevent XSS attack
- :star: The `"extends": "plugin:vue-i18n/recommended"` property in a configuration file enables this rule.

This rule reports all uses of localization methods on `v-html` directive in order to reduce the risk of injecting potentially unsafe / unescaped html into the browser leading to Cross-Site Scripting (XSS) attacks.

## :book: Rule Details

You can be detected with this rule the following:

- `$t`
- `t`
- `$tc`
- `tc`

:-1: Examples of **incorrect** code for this rule:

locale messages:
```json
{
"term": "<p>I accept xxx <a href=\"\/term\">Terms of Service Agreement</a></p>"
}
```

localization codes:

```vue
<template>
<div class="app">
<!-- ✗ BAD -->
<p v-html="$t('term')"></p>
</div>
</template>
```

:+1: Examples of **correct** code for this rule:

locale messages:
```json
{
"tos": "Term of Service",
"term": "I accept xxx {0}."
}
```

localization codes:

```vue
<template>
<div class="app">
<!-- ✗ GOOD -->
<i18n path="term" tag="label" for="tos">
<a :href="url" target="_blank">{{ $t('tos') }}</a>
</i18n>
</div>
</template>
```

## :mute: When Not To Use It

If you are certain the content passed to `v-html` is sanitized HTML you can disable this rule.

## :books: Further reading

- [XSS in Vue.js](https://blog.sqreen.io/xss-in-vue-js/)
3 changes: 2 additions & 1 deletion lib/configs/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module.exports = {
plugins: ['vue-i18n'],
rules: {
'vue-i18n/no-missing-keys': 'error'
'vue-i18n/no-missing-keys': 'error',
'vue-i18n/no-v-html': 'error'
}
}
3 changes: 2 additions & 1 deletion lib/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
module.exports = {
'no-dynamic-keys': require('./rules/no-dynamic-keys'),
'no-missing-keys': require('./rules/no-missing-keys'),
'no-unused-keys': require('./rules/no-unused-keys')
'no-unused-keys': require('./rules/no-unused-keys'),
'no-v-html': require('./rules/no-v-html')
}
47 changes: 47 additions & 0 deletions lib/rules/no-v-html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @author kazuya kawaguchi (a.k.a. kazupon)
*/
'use strict'

const { defineTemplateBodyVisitor } = require('../utils/index')

function checkDirective (context, node) {
if ((node.value && node.value.type === 'VExpressionContainer') &&
(node.value.expression && node.value.expression.type === 'CallExpression')) {
const expressionNode = node.value.expression
const funcName = (expressionNode.callee.type === 'MemberExpression' && expressionNode.callee.property.name) || expressionNode.callee.name
if (!/^(\$t|t|\$tc|tc)$/.test(funcName) || !expressionNode.arguments || !expressionNode.arguments.length) {
return
}
context.report({
node,
message: `Using ${funcName} on 'v-html' directive can lead to XSS attack.`
})
}
}

function create (context) {
return defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name='html']" (node) {
checkDirective(context, node)
},

"VAttribute[directive=true][key.name.name='html']" (node) {
checkDirective(context, node)
}
})
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow use of localization methods on v-html to prevent XSS attack',
category: 'Possible Errors',
recommended: true
},
fixable: null,
schema: []
},
create
}
40 changes: 40 additions & 0 deletions tests/lib/rules/no-v-html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @author kazuya kawaguchi (a.k.a. kazupon)
*/
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/no-v-html')

const tester = new RuleTester({
parser: 'vue-eslint-parser',
parserOptions: { ecmaVersion: 2015 }
})

tester.run('no-v-html', rule, {
valid: [{
code: `<template>
<div class="app">
<i18n path="term" tag="label" for="tos">
<a :href="url" target="_blank">{{ $t('tos') }}</a>
</i18n>
</div>
</template>`
}],

invalid: [{
code: `<template>
<p v-html="$t('hello')"></p>
</template>`,
errors: [
`Using $t on 'v-html' directive can lead to XSS attack.`
]
}, {
code: `<template>
<p v-html="this.t('hello')"></p>
</template>`,
errors: [
`Using t on 'v-html' directive can lead to XSS attack.`
]
}]
})

0 comments on commit f9636da

Please sign in to comment.