Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First attempt on iframe-missing-sandbox rule
- Loading branch information
Showing
5 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Enforce sandbox attribute on iframe elements (react/iframe-missing-sandbox) | ||
|
||
The sandbox attribute enables an extra set of restrictions for the content in the iframe. Using sandbox attribute is considered a good security practice. | ||
|
||
See https://www.w3schools.com/tags/att_iframe_sandbox.asp | ||
|
||
## Rule Details | ||
|
||
This rule checks all JSX iframe elements and verifies that there is sandbox attribute and that it's value is valid. In addition to that it also reports cases where attribute contains `allow-scripts` and `allow-same-origin` at the same time as this combination allows the embedded document to remove the sandbox attribute and bypass the restrictions. | ||
|
||
The following patterns are considered warnings: | ||
|
||
```jsx | ||
var React = require('react'); | ||
|
||
var Frame = <iframe></iframe>; | ||
``` | ||
|
||
The following patterns are **not** considered warnings: | ||
|
||
```jsx | ||
var React = require('react'); | ||
|
||
var Frame = <iframe sandbox="allow-popups"/>; | ||
``` | ||
|
||
## When not to use | ||
|
||
If you don't want to enforce sandbox attribute on iframe elements. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/** | ||
* @fileoverview TBD | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const docsUrl = require('../util/docsUrl'); | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'Enforce sandbox attribute on iframe elements', | ||
category: 'Best Practices', | ||
recommended: false, | ||
url: docsUrl('iframe-missing-sandbox') | ||
}, | ||
|
||
schema: [], | ||
|
||
messages: { | ||
attributeMissing: 'An iframe element is missing a sandbox attribute', | ||
invalidValue: 'An iframe element defines a sandbox attribute with invalid value "{{ value }}"', | ||
invalidCombination: 'An iframe element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid' | ||
} | ||
}, | ||
|
||
create(context) { | ||
const ALLOWED_VALUES = [ | ||
// From https://www.w3schools.com/tags/att_iframe_sandbox.asp | ||
'', | ||
'allow-forms', | ||
'allow-modals', | ||
'allow-orientation-lock', | ||
'allow-pointer-lock', | ||
'allow-popups', | ||
'allow-popups-to-escape-sandbox', | ||
'allow-presentation', | ||
'allow-same-origin', | ||
'allow-scripts', | ||
'allow-top-navigation', | ||
'allow-top-navigation-by-user-activation' | ||
]; | ||
|
||
function validateSandboxAttribute(node, attribute) { | ||
const values = attribute.value.value.split(' '); | ||
let allowScripts = false; | ||
let allowSameOrigin = false; | ||
values.forEach((attributeValue) => { | ||
const trimmedAttributeValue = attributeValue.trim(); | ||
if (ALLOWED_VALUES.indexOf(trimmedAttributeValue) === -1) { | ||
context.report({ | ||
node, | ||
messageId: 'invalidValue', | ||
data: { | ||
value: trimmedAttributeValue | ||
} | ||
}); | ||
} | ||
if (trimmedAttributeValue === 'allow-scripts') { | ||
allowScripts = true; | ||
} | ||
if (trimmedAttributeValue === 'allow-same-origin') { | ||
allowSameOrigin = true; | ||
} | ||
}); | ||
if (allowScripts && allowSameOrigin) { | ||
context.report({ | ||
node, | ||
messageId: 'invalidCombination' | ||
}); | ||
} | ||
} | ||
|
||
return { | ||
'JSXOpeningElement[name.name="iframe"]'(node) { | ||
let sandboxAttributeFound = false; | ||
node.attributes.forEach((attribute) => { | ||
if (attribute.type === 'JSXAttribute' | ||
&& attribute.name | ||
&& attribute.name.type === 'JSXIdentifier' | ||
&& attribute.name.name === 'sandbox' | ||
) { | ||
sandboxAttributeFound = true; | ||
if ( | ||
attribute.value | ||
&& attribute.value.type === 'Literal' | ||
&& attribute.value.value | ||
) { | ||
// Only string literals are supported for now | ||
validateSandboxAttribute(node, attribute); | ||
} | ||
} | ||
}); | ||
if (!sandboxAttributeFound) { | ||
context.report({ | ||
node, | ||
messageId: 'attributeMissing' | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* @fileoverview TBD | ||
*/ | ||
|
||
'use strict'; | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Requirements | ||
// ------------------------------------------------------------------------------ | ||
|
||
const RuleTester = require('eslint').RuleTester; | ||
const rule = require('../../../lib/rules/iframe-missing-sandbox'); | ||
|
||
const parserOptions = { | ||
ecmaVersion: 2018, | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
jsx: true | ||
} | ||
}; | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Tests | ||
// ------------------------------------------------------------------------------ | ||
|
||
const ruleTester = new RuleTester({parserOptions}); | ||
ruleTester.run('iframe-missing-sandbox', rule, { | ||
valid: [ | ||
{code: '<div sandbox="__unknown__" />;'}, | ||
{code: '<iframe sandbox="" />;'}, | ||
{code: '<iframe src="foo.htm" sandbox></iframe>'}, | ||
{code: '<iframe src="foo.htm" sandbox sandbox></iframe>'}, | ||
{code: '<iframe sandbox={""} />'}, | ||
{code: '<iframe sandbox="allow-forms"></iframe>'}, | ||
{code: '<iframe sandbox="allow-modals"></iframe>'}, | ||
{code: '<iframe sandbox="allow-orientation-lock"></iframe>'}, | ||
{code: '<iframe sandbox="allow-pointer-lock"></iframe>'}, | ||
{code: '<iframe sandbox="allow-popups"></iframe>'}, | ||
{code: '<iframe sandbox="allow-popups-to-escape-sandbox"></iframe>'}, | ||
{code: '<iframe sandbox="allow-presentation"></iframe>'}, | ||
{code: '<iframe sandbox="allow-same-origin"></iframe>'}, | ||
{code: '<iframe sandbox="allow-scripts"></iframe>'}, | ||
{code: '<iframe sandbox="allow-top-navigation"></iframe>'}, | ||
{code: '<iframe sandbox="allow-top-navigation-by-user-activation"></iframe>'}, | ||
{code: '<iframe sandbox="allow-forms allow-modals"></iframe>'}, | ||
{code: '<iframe sandbox="allow-popups allow-popups-to-escape-sandbox allow-pointer-lock allow-same-origin allow-top-navigation"></iframe>'} | ||
], | ||
invalid: [{ | ||
code: '<iframe></iframe>;', | ||
errors: [{messageId: 'attributeMissing'}] | ||
}, | ||
{ | ||
code: '<iframe/>;', | ||
errors: [{messageId: 'attributeMissing'}] | ||
}, | ||
{ | ||
code: '<iframe sandbox="__unknown__"></iframe>', | ||
errors: [{messageId: 'invalidValue', data: {value: '__unknown__'}}] | ||
}, | ||
{ | ||
code: '<iframe sandbox="allow-popups __unknown__"/>', | ||
errors: [{messageId: 'invalidValue', data: {value: '__unknown__'}}] | ||
}, | ||
{ | ||
code: '<iframe sandbox="__unknown__ allow-popups"/>', | ||
errors: [{messageId: 'invalidValue', data: {value: '__unknown__'}}] | ||
}, | ||
{ | ||
code: '<iframe sandbox=" allow-forms __unknown__ allow-popups __unknown__ "/>', | ||
errors: [ | ||
{messageId: 'invalidValue', data: {value: '__unknown__'}}, | ||
{messageId: 'invalidValue', data: {value: '__unknown__'}} | ||
] | ||
}, | ||
{ | ||
code: '<iframe sandbox="allow-scripts allow-same-origin"></iframe>;', | ||
errors: [{messageId: 'invalidCombination'}] | ||
}, | ||
{ | ||
code: '<iframe sandbox="allow-same-origin allow-scripts"/>;', | ||
errors: [{messageId: 'invalidCombination'}] | ||
}] | ||
}); |