├── .npmignore ├── tslint.json ├── tslint_custom_rules.json ├── tsconfig.json ├── .gitignore ├── package.json ├── noBypassSecurityRule.ts ├── flagLocalStorageAngularPluginRule.ts ├── noElementReferenceRule.ts └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .gitignore 3 | package.json 4 | tsconfig.json 5 | tslint.json -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-imports": true 4 | }, 5 | "rulesDirectory": "./src/" 6 | } 7 | -------------------------------------------------------------------------------- /tslint_custom_rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/tslint-angular-security" 4 | ], 5 | 6 | "rules": { 7 | "flag-local-storage-angular-plugin": true, 8 | "no-bypass-security": true, 9 | "no-element-reference": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "target": "es5", 6 | "skipLibCheck": true, 7 | "lib": [ 8 | "es2016", 9 | "dom" 10 | ] 11 | }, 12 | "include": [ 13 | "." 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | # for this project we do not need the compiled version of the rules 5 | *.js 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # IDE - VSCode 11 | .vscode/* 12 | !.vscode/settings.json 13 | !.vscode/tasks.json 14 | !.vscode/launch.json 15 | !.vscode/extensions.json 16 | 17 | # misc 18 | npm-debug.log 19 | 20 | # e2e 21 | /e2e/*.js 22 | /e2e/*.map 23 | 24 | # System Files 25 | .DS_Store 26 | Thumbs.db 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tslint-angular-security", 3 | "description": "Angular security rules for TSLint", 4 | "version": "0.0.5", 5 | "dependencies": { 6 | "tslint": "^5.10.0" 7 | }, 8 | "devDependencies": { 9 | "typescript": "^2.9.2" 10 | }, 11 | "scripts": { 12 | "build": "tsc", 13 | "prepare": "npm run build", 14 | "version": "git add -A src", 15 | "postversion": "git push && git push --tags" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/synopsys-SIG/tslint-angular-security.git" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "main": "index.js", 25 | "license": "MIT", 26 | "keywords": [ 27 | "TSLint", 28 | "Angular", 29 | "Security", 30 | "TypeScript" 31 | ], 32 | "author": "Ksenia Peguero" 33 | } 34 | -------------------------------------------------------------------------------- /noBypassSecurityRule.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Synopsys, Inc. All rights reserved worldwide. 2 | /* The rule flags any call to Angular APIs bypassSecurityTrust*, which 3 | * when called on tainted data may result in untrusted data written into the DOM 4 | * which may lead to XSS. 5 | */ 6 | 7 | import * as ts from "typescript"; 8 | import * as Lint from "tslint"; 9 | 10 | export class Rule extends Lint.Rules.AbstractRule { 11 | 12 | public static FAILURE_STRING = "Untrusted data sent to bypassSecurityTrust* methods may result in XSS"; 13 | 14 | public static metadata: Lint.IRuleMetadata = { 15 | ruleName: 'no-bypass-security', 16 | type: 'functionality', 17 | description: 'Angular bypassSecurityTrust* methods may lead to XSS and other attacks', 18 | options: null, 19 | optionsDescription: '', 20 | typescriptOnly: true, 21 | }; 22 | 23 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { 24 | return this.applyWithWalker(new NoBypassSecurityWalker(sourceFile, this.getOptions())); 25 | } 26 | } 27 | 28 | class NoBypassSecurityWalker extends Lint.RuleWalker { 29 | 30 | public visitPropertyAccessExpression(node: ts.PropertyAccessExpression): void { 31 | 32 | if (node.name.text === 'bypassSecurityTrustHtml' 33 | || node.name.text === 'bypassSecurityTrustStyle' 34 | || node.name.text === 'bypassSecurityTrustScript' 35 | || node.name.text === 'bypassSecurityTrustUrl' 36 | || node.name.text === 'bypassSecurityTrustResourceUrl' ) 37 | // create a failure at the current position 38 | this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING); 39 | 40 | // call the base version of this visitor to actually parse this node 41 | super.visitPropertyAccessExpression(node); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /flagLocalStorageAngularPluginRule.ts: -------------------------------------------------------------------------------- 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 | } 52 | -------------------------------------------------------------------------------- /noElementReferenceRule.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Synopsys, Inc. All rights reserved worldwide. 2 | /* The rule flags any references to nativeElement, when DOM-modifying attributes or functions, 3 | * such as innerHTML, outerHTML, querySelector are called on it. The nativeElement property 4 | * allows access to the underlying DOM element. 5 | */ 6 | 7 | import * as ts from "typescript"; 8 | import * as Lint from "tslint"; 9 | 10 | export class Rule extends Lint.Rules.AbstractRule { 11 | 12 | public static FAILURE_STRING_INNER = "Forbid writing innerHTML directly through element reference"; 13 | public static FAILURE_STRING_OUTER = "Forbid writing outerHTML directly through element reference"; 14 | public static FAILURE_STRING_QUERY = "Validate no tainted data is written to the element accessed directly through querySelector"; 15 | 16 | public static metadata: Lint.IRuleMetadata = { 17 | ruleName: 'no-element-reference', 18 | type: 'functionality', 19 | description: 'Directly manipulating innerHTML or outerHTML of the DOM element may lead to XSS', 20 | options: null, 21 | optionsDescription: '', 22 | typescriptOnly: true, 23 | }; 24 | 25 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { 26 | return this.applyWithWalker(new NoElementReferenceWalker(sourceFile, this.getOptions())); 27 | } 28 | } 29 | 30 | // The walker takes care of all the work. 31 | class NoElementReferenceWalker extends Lint.RuleWalker { 32 | 33 | public visitPropertyAccessExpression(node: ts.PropertyAccessExpression): void { 34 | 35 | if (node.getText().includes('nativeElement')) { 36 | if (node.name.text === 'innerHTML') { 37 | this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_INNER); 38 | } else if (node.name.text === 'outerHTML') { 39 | this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_OUTER); 40 | } else if (node.name.text === 'querySelector') { 41 | this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_QUERY); 42 | } 43 | } 44 | // call the base version of this visitor to actually parse this node 45 | super.visitPropertyAccessExpression(node); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 | TSLint must be installed locally in the target project. And the project must have tsconfig.json file in the root folder. Install `tslint-angular-security` from npm. 8 | 9 | ``` 10 | cd targetproject 11 | npm init -y 12 | npm i tslint typescript 13 | npm i tslint-angular-security 14 | ./node_modules/tslint/bin/tslint --init 15 | ``` 16 | 17 | ## Configuration 18 | 19 | Configure the target project `tslint.json` file to include the needed rules from the `tslint-angular-security` package. 20 | 21 | ``` 22 | { 23 | "rulesDirectory": [ 24 | "node_modules/tslint-angular-security" 25 | ], 26 | 27 | "rules": { 28 | "flag-local-storage-angular-plugin": true, 29 | "no-bypass-security": true, 30 | "no-element-reference": true 31 | } 32 | } 33 | ``` 34 | 35 | See example configuration in `tslint_custom_rules.json`. 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.json 44 | ``` 45 | 46 | *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.* 47 | 48 | ## Rules 49 | 50 | Rule Name | Description | Vulnerability | CWE 51 | :---------- | :------------ | -------------|--- 52 | `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) 53 | `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) 54 | `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) 55 | 56 | ## Developing 57 | 58 | Feel free to update/add new rules in your local version. After you add/update the .ts, compile them using the [TypeScript compiler](https://www.npmjs.com/package/typescript) from the root folder: 59 | 60 | ``` 61 | tsc 62 | ``` 63 | The compiled JavaScript files will be in the root directory. Copy them to `node_modules\tslint-angular-security` in the target project and use them. 64 | 65 | ## Authors 66 | 67 | * **Ksenia Peguero**, Senior Research Lead @Synopsys 68 | 69 | ## License 70 | 71 | This software is released by Synopsys under the MIT license. 72 | 73 | ## Acknowledgments 74 | 75 | * Thanks to [Lewis Ardern](https://github.com/LewisArdern/) for inspiration with his security rules for AngularJS https://github.com/LewisArdern/eslint-config-angular-security 76 | --------------------------------------------------------------------------------