This repository has been archived by the owner on Jul 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 199
/
reactIframeMissingSandboxRule.ts
113 lines (100 loc) · 4.3 KB
/
reactIframeMissingSandboxRule.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as tsutils from 'tsutils';
import { ExtendedMetadata } from './utils/ExtendedMetadata';
const FAILURE_NOT_FOUND: string = 'An iframe element requires a sandbox attribute';
const FAILURE_INVALID_ENTRY: string = 'An iframe element defines an invalid sandbox attribute: ';
const FAILURE_INVALID_COMBINATION: string = 'An iframe element defines a sandbox with both allow-scripts and allow-same-origin';
// If specified as an empty string, this attribute enables extra restrictions on the content that can
// appear in the inline frame. The value of the attribute can either be an empty string (all the restrictions
// are applied), or a space-separated list of tokens that lift particular restrictions.
// Setting both 'allow-scripts'; and 'allow-same-origin' for a single <iframe> tag is not advised. Valid tokens are:
const ALLOWED_VALUES: string[] = [
'',
'allow-forms',
'allow-modals',
'allow-orientation-lock',
'allow-pointer-lock',
'allow-popups',
'allow-popups-to-escape-sandbox',
'allow-same-origin',
'allow-scripts',
'allow-top-navigation'
];
export class Rule extends Lint.Rules.AbstractRule {
public static metadata: ExtendedMetadata = {
ruleName: 'react-iframe-missing-sandbox',
type: 'functionality',
description: 'React iframes must specify a sandbox attribute',
options: null, // tslint:disable-line:no-null-keyword
optionsDescription: '',
typescriptOnly: true,
issueClass: 'SDL',
issueType: 'Error',
severity: 'Critical',
level: 'Opportunity for Excellence',
group: 'Security',
commonWeaknessEnumeration: '915'
};
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithFunction(sourceFile, walk);
}
return [];
}
}
function walk(ctx: Lint.WalkContext<void>) {
function handleJsxOpeningElement(node: ts.JsxOpeningLikeElement): void {
if (node.tagName.getText() !== 'iframe') {
return;
}
let sandboxAttributeFound: boolean = false;
node.attributes.properties.forEach(
(attribute: ts.JsxAttribute | ts.JsxSpreadAttribute): void => {
if (attribute.kind === ts.SyntaxKind.JsxAttribute) {
const jsxAttribute: ts.JsxAttribute = <ts.JsxAttribute>attribute;
const attributeName = jsxAttribute.name.text;
if (attributeName === 'sandbox') {
sandboxAttributeFound = true;
if (jsxAttribute.initializer !== undefined && jsxAttribute.initializer.kind === ts.SyntaxKind.StringLiteral) {
validateSandboxValue(<ts.StringLiteral>jsxAttribute.initializer);
}
}
}
}
);
if (!sandboxAttributeFound) {
ctx.addFailureAt(node.getStart(), node.getWidth(), FAILURE_NOT_FOUND);
}
}
function validateSandboxValue(node: ts.StringLiteral): void {
const values: string[] = node.text.split(' ');
let allowScripts: boolean = false;
let allowSameOrigin: boolean = false;
values.forEach(
(attributeValue: string): void => {
if (ALLOWED_VALUES.indexOf(attributeValue) === -1) {
ctx.addFailureAt(node.getStart(), node.getWidth(), FAILURE_INVALID_ENTRY + attributeValue);
}
if (attributeValue === 'allow-scripts') {
allowScripts = true;
}
if (attributeValue === 'allow-same-origin') {
allowSameOrigin = true;
}
}
);
if (allowScripts && allowSameOrigin) {
ctx.addFailureAt(node.getStart(), node.getWidth(), FAILURE_INVALID_COMBINATION);
}
}
function cb(node: ts.Node): void {
if (tsutils.isJsxElement(node)) {
handleJsxOpeningElement(node.openingElement);
} else if (tsutils.isJsxSelfClosingElement(node)) {
handleJsxOpeningElement(node);
}
return ts.forEachChild(node, cb);
}
return ts.forEachChild(ctx.sourceFile, cb);
}