Skip to content

Commit cc3b18d

Browse files
Initial commit
0 parents  commit cc3b18d

16 files changed

+512
-0
lines changed

.gitignore

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# See http://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# compiled output
4+
# for this project we do need the compiled version of the rules
5+
# /dist
6+
/tmp
7+
/out-tsc
8+
9+
# dependencies
10+
/node_modules
11+
12+
# IDE - VSCode
13+
.vscode/*
14+
!.vscode/settings.json
15+
!.vscode/tasks.json
16+
!.vscode/launch.json
17+
!.vscode/extensions.json
18+
19+
# misc
20+
npm-debug.log
21+
22+
# e2e
23+
/e2e/*.js
24+
/e2e/*.map
25+
26+
# System Files
27+
.DS_Store
28+
Thumbs.db

.npmignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/src
2+
/node_modules
3+
.gitignore
4+
package.json
5+
tsconfig.json
6+
tslint.json

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Angular Security Rules for TSLint
2+
3+
These simple linting rules flag points of interest where a security problem may be present in TypeScript Angular code. These rules are to be used with [TSLint](https://palantir.github.io/tslint/).
4+
5+
## Getting Started
6+
7+
Use the compiled rules from `\dist\src` with your installation of TSLint. In the project that you plan to scan (target project), go to the `node_modules` folder and create a folder called `tslint-angular-security`.
8+
```
9+
git clone -q https://github.com/Synopsys-SIG/tslint-angular-security
10+
cd targetproject\node_modules && mkdir tslint-angular-security
11+
```
12+
13+
Copy files from `\dist\src` in this project to the `node_modules\tslint-angular-security` folder in the target project.
14+
```
15+
cp tslint-angular-security\dist\src\* targetproject\node_modules\tslint-angular-security
16+
```
17+
18+
## Configuration
19+
20+
Copy the `tslint_custom_rules.json` configuration file from the root of this project to the root of the target project.
21+
```
22+
cp tslint-angular-security\tslint_custom_rules.json targetproject\
23+
```
24+
25+
Alternatively, modify the `tslint.json` file in the project to include the rules you need from the tslint-angular-security package.
26+
27+
## Prerequisites
28+
29+
TSLint must be installed locally in the target project. And the project must have tsconfig.json file in the root folder.
30+
```
31+
cd targetproject
32+
npm init -y
33+
npm i tslint typescript
34+
npm i tslint-microsoft-contrib
35+
./node_modules/tslint/bin/tslint --init
36+
```
37+
38+
## Running
39+
40+
In the root of the target project run:
41+
42+
```
43+
./node_modules/.bin/tslint --project tsconfig.json --config tslint_custom_rules.json
44+
```
45+
*Warning: this repository is a work-in-progress. Things may break while we transition this project to open source. This is not an officially supported Synopsys product.*
46+
47+
## Rules
48+
49+
Rule Name | Description | Vulnerability | CWE
50+
:---------- | :------------ | -------------|---
51+
`no-bypass-security` | Flags all calls of Angular Sanitizer functions: bypassSecurityTrustHtml, bypassSecurityTrustStyle, bypassSecurityTrustScript, bypassSecurityTrustUrl, bypassSecurityTrustResourceUrl. Angular does not apply any sanitization on the passed through these functions. | Validate that the input to these functions is tainted and that the result it written into a template.| [CWE-79](https://cwe.mitre.org/data/definitions/79.html)
52+
`no-element-reference` | Flags all calls to `nativeElement.innerHTML`, `nativeElement.outerHTML`, and `nativeElement.querySelector`. The `nativeElement` property of the ElementRef class allows direct access to the DOM element. | Validate that tainted data is assigned in these calls or other element manipulations, which can lead to DOM XSS.| [CWE-79](https://cwe.mitre.org/data/definitions/79.html)
53+
`flag-local-storage-angular-plugin` | Flags all calls writing data to localStorage or webStorage for plugins @ngx-pwa/local-storage and angular-webstorage-service. | Validate that the data is actually written to localStorage and not sessionStorage, and that the data is sensitive.| [CWE-922](https://cwe.mitre.org/data/definitions/922.html)
54+
55+
## Developing
56+
57+
Feel free to update/add new rules in your local version. After you update the .ts files in `src`, compile them using the [TypeScript compiler](https://www.npmjs.com/package/typescript) from the root folder:
58+
59+
```
60+
tsc
61+
```
62+
The compiled JavaScript files will be in `dist\src`. Copy them to `node_modules\tslint-angular-security` in the target project and use them there.
63+
64+
## Authors
65+
66+
* **Ksenia Peguero**, Senior Research Lead @Synopsys
67+
68+
## License
69+
70+
This software is released by Synopsys under the MIT license.
71+
72+
## Acknowledgments
73+
74+
* Thanks to [Lewis Ardern](https://github.com/LewisArdern/) for inspiration with his security rules for AngularJS https://github.com/LewisArdern/eslint-config-angular-security
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as ts from "typescript";
2+
import * as Lint from "tslint";
3+
export declare class Rule extends Lint.Rules.AbstractRule {
4+
static FAILURE_STRING: string;
5+
static metadata: Lint.IRuleMetadata;
6+
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
7+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"use strict";
2+
// Copyright (c) 2018 Synopsys, Inc. All rights reserved worldwide.
3+
/* The rule flags access to localStorage or web storage when Angular2+ app is used
4+
* with plugins:
5+
* - @ngx-pwa/local-storage
6+
* - angular-webstorage-service
7+
* Note that angular-webstorage-service is configured at the constuctor to use either
8+
* LOCAL_STORAGE or SESSION_STORAGE. This rule does not take this into account and
9+
* may return false positives.
10+
*/
11+
var __extends = (this && this.__extends) || (function () {
12+
var extendStatics = Object.setPrototypeOf ||
13+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
14+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
15+
return function (d, b) {
16+
extendStatics(d, b);
17+
function __() { this.constructor = d; }
18+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
19+
};
20+
})();
21+
Object.defineProperty(exports, "__esModule", { value: true });
22+
var Lint = require("tslint");
23+
var Rule = /** @class */ (function (_super) {
24+
__extends(Rule, _super);
25+
function Rule() {
26+
return _super !== null && _super.apply(this, arguments) || this;
27+
}
28+
Rule.prototype.apply = function (sourceFile) {
29+
return this.applyWithWalker(new FlagLocalStoragePluginWalker(sourceFile, this.getOptions()));
30+
};
31+
Rule.FAILURE_STRING = "Validate that sensitive data is not written to localStorage via plugins ";
32+
Rule.metadata = {
33+
ruleName: 'flag-local-storage-angular-plugin',
34+
type: 'functionality',
35+
description: 'Sensitive data stored in localStorage may be leaked to an attacker',
36+
options: null,
37+
optionsDescription: '',
38+
typescriptOnly: true,
39+
};
40+
return Rule;
41+
}(Lint.Rules.AbstractRule));
42+
exports.Rule = Rule;
43+
var FlagLocalStoragePluginWalker = /** @class */ (function (_super) {
44+
__extends(FlagLocalStoragePluginWalker, _super);
45+
function FlagLocalStoragePluginWalker() {
46+
return _super !== null && _super.apply(this, arguments) || this;
47+
}
48+
FlagLocalStoragePluginWalker.prototype.visitPropertyAccessExpression = function (node) {
49+
//check for @ngx-pwa/local-storage plugn API
50+
if (node.expression.getText() === 'this.localStorage'
51+
&& node.name.text === 'setItem') {
52+
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING);
53+
}
54+
//check for angular-webstorage-service plugin API
55+
if (node.expression.getText() === 'this.storage'
56+
&& node.name.text === 'set') {
57+
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING);
58+
}
59+
// call the base version of this visitor to actually parse this node
60+
_super.prototype.visitPropertyAccessExpression.call(this, node);
61+
};
62+
return FlagLocalStoragePluginWalker;
63+
}(Lint.RuleWalker));

dist/src/noBypassSecurityRule.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as ts from "typescript";
2+
import * as Lint from "tslint";
3+
export declare class Rule extends Lint.Rules.AbstractRule {
4+
static FAILURE_STRING: string;
5+
static metadata: Lint.IRuleMetadata;
6+
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
7+
}

dist/src/noBypassSecurityRule.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use strict";
2+
// Copyright (c) 2018 Synopsys, Inc. All rights reserved worldwide.
3+
/* The rule flags any call to Angular APIs bypassSecurityTrust*, which
4+
* when called on tainted data may result in untrusted data written into the DOM
5+
* which may lead to XSS.
6+
*/
7+
var __extends = (this && this.__extends) || (function () {
8+
var extendStatics = Object.setPrototypeOf ||
9+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
10+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
11+
return function (d, b) {
12+
extendStatics(d, b);
13+
function __() { this.constructor = d; }
14+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15+
};
16+
})();
17+
Object.defineProperty(exports, "__esModule", { value: true });
18+
var Lint = require("tslint");
19+
var Rule = /** @class */ (function (_super) {
20+
__extends(Rule, _super);
21+
function Rule() {
22+
return _super !== null && _super.apply(this, arguments) || this;
23+
}
24+
Rule.prototype.apply = function (sourceFile) {
25+
return this.applyWithWalker(new NoBypassSecurityWalker(sourceFile, this.getOptions()));
26+
};
27+
Rule.FAILURE_STRING = "Untrusted data sent to bypassSecurityTrust* methods may result in XSS";
28+
Rule.metadata = {
29+
ruleName: 'no-bypass-security',
30+
type: 'functionality',
31+
description: 'Angular bypassSecurityTrust* methods may lead to XSS and other attacks',
32+
options: null,
33+
optionsDescription: '',
34+
typescriptOnly: true,
35+
};
36+
return Rule;
37+
}(Lint.Rules.AbstractRule));
38+
exports.Rule = Rule;
39+
var NoBypassSecurityWalker = /** @class */ (function (_super) {
40+
__extends(NoBypassSecurityWalker, _super);
41+
function NoBypassSecurityWalker() {
42+
return _super !== null && _super.apply(this, arguments) || this;
43+
}
44+
NoBypassSecurityWalker.prototype.visitPropertyAccessExpression = function (node) {
45+
if (node.name.text === 'bypassSecurityTrustHtml'
46+
|| node.name.text === 'bypassSecurityTrustStyle'
47+
|| node.name.text === 'bypassSecurityTrustScript'
48+
|| node.name.text === 'bypassSecurityTrustUrl'
49+
|| node.name.text === 'bypassSecurityTrustResourceUrl')
50+
// create a failure at the current position
51+
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING);
52+
// call the base version of this visitor to actually parse this node
53+
_super.prototype.visitPropertyAccessExpression.call(this, node);
54+
};
55+
return NoBypassSecurityWalker;
56+
}(Lint.RuleWalker));

dist/src/noElementReferenceRule.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as ts from "typescript";
2+
import * as Lint from "tslint";
3+
export declare class Rule extends Lint.Rules.AbstractRule {
4+
static FAILURE_STRING_INNER: string;
5+
static FAILURE_STRING_OUTER: string;
6+
static FAILURE_STRING_QUERY: string;
7+
static metadata: Lint.IRuleMetadata;
8+
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
9+
}

dist/src/noElementReferenceRule.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"use strict";
2+
// Copyright (c) 2018 Synopsys, Inc. All rights reserved worldwide.
3+
/* The rule flags any references to nativeElement, when DOM-modifying attributes or functions,
4+
* such as innerHTML, outerHTML, querySelector are called on it. The nativeElement property
5+
* allows access to the underlying DOM element.
6+
*/
7+
var __extends = (this && this.__extends) || (function () {
8+
var extendStatics = Object.setPrototypeOf ||
9+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
10+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
11+
return function (d, b) {
12+
extendStatics(d, b);
13+
function __() { this.constructor = d; }
14+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15+
};
16+
})();
17+
Object.defineProperty(exports, "__esModule", { value: true });
18+
var Lint = require("tslint");
19+
var Rule = /** @class */ (function (_super) {
20+
__extends(Rule, _super);
21+
function Rule() {
22+
return _super !== null && _super.apply(this, arguments) || this;
23+
}
24+
Rule.prototype.apply = function (sourceFile) {
25+
return this.applyWithWalker(new NoElementReferenceWalker(sourceFile, this.getOptions()));
26+
};
27+
Rule.FAILURE_STRING_INNER = "Forbid writing innerHTML directly through element reference";
28+
Rule.FAILURE_STRING_OUTER = "Forbid writing outerHTML directly through element reference";
29+
Rule.FAILURE_STRING_QUERY = "Validate no tainted data is written to the element accessed directly through querySelector";
30+
Rule.metadata = {
31+
ruleName: 'no-element-reference',
32+
type: 'functionality',
33+
description: 'Directly manipulating innerHTML or outerHTML of the DOM element may lead to XSS',
34+
options: null,
35+
optionsDescription: '',
36+
typescriptOnly: true,
37+
};
38+
return Rule;
39+
}(Lint.Rules.AbstractRule));
40+
exports.Rule = Rule;
41+
// The walker takes care of all the work.
42+
var NoElementReferenceWalker = /** @class */ (function (_super) {
43+
__extends(NoElementReferenceWalker, _super);
44+
function NoElementReferenceWalker() {
45+
return _super !== null && _super.apply(this, arguments) || this;
46+
}
47+
NoElementReferenceWalker.prototype.visitPropertyAccessExpression = function (node) {
48+
if (node.getText().includes('nativeElement')) {
49+
if (node.name.text === 'innerHTML') {
50+
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_INNER);
51+
}
52+
else if (node.name.text === 'outerHTML') {
53+
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_OUTER);
54+
}
55+
else if (node.name.text === 'querySelector') {
56+
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_QUERY);
57+
}
58+
}
59+
// call the base version of this visitor to actually parse this node
60+
_super.prototype.visitPropertyAccessExpression.call(this, node);
61+
};
62+
return NoElementReferenceWalker;
63+
}(Lint.RuleWalker));

package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "tslint-angular-security",
3+
"description": "Angular security rules for TSLint",
4+
"version": "0.0.1",
5+
"dependencies": {
6+
"tslint": "^5.10.0"
7+
},
8+
"devDependencies": {
9+
"typescript": "^2.9.2"
10+
},
11+
"scripts" : {
12+
"build" : "tsc",
13+
14+
"prepare" : "npm run build",
15+
"version" : "git add -A src",
16+
"postversion" : "git push && git push --tags"
17+
} ,
18+
"repository": {
19+
"type": "git",
20+
"url": "git+https://github.com/synopsys-SIG/tslint-angular-security.git"
21+
},
22+
"keywords": ["TSLint", "Angular", "Security", "TypeScript"],
23+
"author": "Ksenia Peguero"
24+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) 2018 Synopsys, Inc. All rights reserved worldwide.
2+
/* The rule flags access to localStorage or web storage when Angular2+ app is used
3+
* with plugins:
4+
* - @ngx-pwa/local-storage
5+
* - angular-webstorage-service
6+
* Note that angular-webstorage-service is configured at the constuctor to use either
7+
* LOCAL_STORAGE or SESSION_STORAGE. This rule does not take this into account and
8+
* may return false positives.
9+
*/
10+
11+
12+
import * as ts from "typescript";
13+
import * as Lint from "tslint";
14+
15+
export class Rule extends Lint.Rules.AbstractRule {
16+
17+
public static FAILURE_STRING = "Validate that sensitive data is not written to localStorage via plugins ";
18+
19+
public static metadata: Lint.IRuleMetadata = {
20+
ruleName: 'flag-local-storage-angular-plugin',
21+
type: 'functionality',
22+
description: 'Sensitive data stored in localStorage may be leaked to an attacker',
23+
options: null,
24+
optionsDescription: '',
25+
typescriptOnly: true,
26+
};
27+
28+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
29+
return this.applyWithWalker(new FlagLocalStoragePluginWalker(sourceFile, this.getOptions()));
30+
}
31+
}
32+
33+
class FlagLocalStoragePluginWalker extends Lint.RuleWalker {
34+
35+
public visitPropertyAccessExpression(node: ts.PropertyAccessExpression): void {
36+
37+
//check for @ngx-pwa/local-storage plugn API
38+
if (node.expression.getText() === 'this.localStorage'
39+
&& node.name.text === 'setItem') {
40+
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING);
41+
}
42+
43+
//check for angular-webstorage-service plugin API
44+
if (node.expression.getText() === 'this.storage'
45+
&& node.name.text === 'set') {
46+
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING);
47+
}
48+
// call the base version of this visitor to actually parse this node
49+
super.visitPropertyAccessExpression(node);
50+
}
51+
}

0 commit comments

Comments
 (0)