diff --git a/.github/workflows/E2E integration.yml b/.github/workflows/E2E integration.yml new file mode 100644 index 0000000..6181b60 --- /dev/null +++ b/.github/workflows/E2E integration.yml @@ -0,0 +1,79 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: E2E integration + +on: + push: + branches: [main] + pull_request_target: + branches: [main, release/vNext] + +env: + PROJECT: discordjs # The name of the project you want to clone. It must be on github + REPOSITORY: discord.js # The repository name + FOLDER_TO_SCAN: src # The folder under which the source code you have is contained. Relative to the repository + TS_CONFIG_PATH: tsconfig.json # The tsconfig.json path relative to the repository + +jobs: + build: + name: E2E run with sarif + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-18.04, windows-2019] + + steps: + - name: Setup Node.js environment + uses: actions/setup-node@v1.4.3 + + - uses: actions/checkout@v2 + with: + repository: ${{env.PROJECT}}/${{env.REPOSITORY}} + path: ${{env.PROJECT}} + clean: true + - uses: actions/checkout@v2 + with: + path: eslint-plugin-sdl + clean: true + + # store them inside a seperate folder for performance in case of multiple projects. + # Trying to install eslint on a project with an existing package.json will install all of the package.json packages as well + - name: Create eslint directory + run: | + mkdir eslint + continue-on-error: true + + - name: Install eslint for discord library and eslint-plugin-dependencies + run: | + npm i eslint + npm i eslint-plugin-security + npm i eslint-plugin-react + npm i eslint-plugin-node + npm i typescript + npm i @microsoft/eslint-formatter-sarif + working-directory: eslint + + - name: Link the eslint-plugin-sdl + run: npm link ../eslint-plugin-sdl + working-directory: eslint + + - name: Run eslint + run: npx eslint + -c node_modules/@microsoft/eslint-plugin-sdl/config/recommended.js + ../${{env.PROJECT}}/${{env.FOLDER_TO_SCAN}}/ + --ext .js + --parser-options=project:../${{env.PROJECT}}/${{env.TS_CONFIG_PATH}} + --no-eslintrc + -f @microsoft/eslint-formatter-sarif + -o ../${{env.PROJECT}}/eslint-result-${{ matrix.os }}-${{github.run_id}}.sarif + working-directory: eslint + continue-on-error: true + + - name: Upload eslint results as artifact + uses: actions/upload-artifact@v2 + with: + name: eslint-result + path: ${{env.PROJECT}}/eslint-result-${{ matrix.os }}-${{github.run_id}}.sarif + if-no-files-found: error diff --git a/.github/workflows/node-version-integration.yml b/.github/workflows/node-version-integration.yml new file mode 100644 index 0000000..ef8659d --- /dev/null +++ b/.github/workflows/node-version-integration.yml @@ -0,0 +1,30 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: Node CI + +on: + push: + branches: [main] + pull_request_target: + branches: [main, release/vNext] + +jobs: + build: + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04, windows-2019, macos-10.15] + node-version: [12.x, 14.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run build --if-present + - run: npm test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..7b31b73 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,34 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: Publish Package + +on: + release: + types: [created] + +jobs: + # Run one last check + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + - run: npm ci + - run: npm test + + publish-npm: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcd3dc0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +node_modules/ +.vscode/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..28c5d8d --- /dev/null +++ b/.npmignore @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +tests/ +.github/ \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f9ba8cf --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/README.md b/README.md new file mode 100644 index 0000000..615c0e8 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# eslint-plugin-sdl + +[ESLint Plugin](https://eslint.org/docs/developer-guide/working-with-plugins) focused on common security issues and misconfigurations. + +Plugin is intended as a baseline for projects that follow [Microsoft Security Development Lifecycle (SDL)](https://www.microsoft.com/en-us/securityengineering/sdl) and use ESLint to perform [Static Analysis Security Testing (SAST)](https://www.microsoft.com/en-us/securityengineering/sdl/practices#practice9). + +## Configs + +Plugin is shipped with following [Shareable Configs](http://eslint.org/docs/developer-guide/shareable-configs): + +- [angular](config/angular.js) - Set of rules for [Angular](https://angular.io) applications +- [angularjs](config/angularjs.js) - Set of rules for [AngularJS](https://docs.angularjs.org) applications +- [common](config/common.js) - Set of rules for common JavaScript applications +- [electron](config/electron.js) - Set of rules for Electron applications +- [node](config/node.js) - Set of rules for Node applications +- [react](config/react.js) - Set of rules for [ReactJS](https://reactjs.org) applications +- [**recommended**](config/recommended.js) - SDL Recommended rules for all applications +- [**required**](config/required.js) - SDL Required rules for all applications +- [typescript](config/typescript.js) - Set of rules for TypeScript applications + +## Rules + +Where possible, we leverage existing rules from [ESLint](https://eslint.org/docs/rules/) and community plugins such as [react](https://github.com/yannickcr/eslint-plugin-react), [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules) or [security](https://github.com/nodesecurity/eslint-plugin-security#rules). + +We also implemented several [custom rules](./lib/rules) where we did not find sufficient alternative in the community. + +| Name | Description | +| --- | --- | +| [no-caller](https://eslint.org/docs/rules/no-caller) | Bans usage of deprecated functions `arguments.caller()` and `arguments.callee` that could potentially allow access to call stack. | +| [no-delete-var](https://eslint.org/docs/rules/no-delete-var) | Bans usage of operator `delete` on variables as it can lead to unexpected behavior. | +| [no-eval](https://eslint.org/docs/rules/no-eval) | Bans usage of [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) that allows code execution from string argument. | +| [no-implied-eval](https://eslint.org/docs/rules/no-implied-eval) | Bans usage of `setTimeout()`, `setInterval()` and `execScript()`. These functions are similar to `eval()` and prone to code execution. | +| [no-new-func](https://eslint.org/docs/rules/no-new-func) | Bans calling `new Function()` as it's similar to `eval()` and prone to code execution. | +| [node/no-deprecated-api](https://github.com/mysticatea/eslint-plugin-node/blob/master/docs/rules/no-deprecated-api.md) | Bans usage of deprecated APIs in Node. | +| [@microsoft/sdl/no-angular-bypass-sanitizer](./docs/rules/no-angular-bypass-sanitizer.md) | Calls to bypassSecurityTrustHtml, bypassSecurityTrustScript and similar methods bypass [DomSanitizer](https://angular.io/api/platform-browser/DomSanitizer#security-risk) in Angular and need to be reviewed. | +| [@microsoft/sdl/no-angularjs-bypass-sce](./docs/rules/no-angularjs-bypass-sce.md) | Calls to `$sceProvider.enabled(false)`, `$sceDelegate.trustAs()`, `$sce.trustAs()` and relevant shorthand methods (e.g. `trustAsHtml` or `trustAsJs`) bypass [Strict Contextual Escaping (SCE)](https://docs.angularjs.org/api/ng/service/$sce#strict-contextual-escaping) in AngularJS and need to be reviewed. | +| [@microsoft/sdl/no-angularjs-enable-svg](./docs/rules/no-angularjs-enable-svg.md) | Calls to [`$sanitizeProvider.enableSvg(true)`](https://docs.angularjs.org/api/ngSanitize/provider/$sanitizeProvider#enableSvg) increase attack surface of the application by enabling SVG support in AngularJS sanitizer and need to be reviewed. | +| [@microsoft/sdl/no-angularjs-sanitization-whitelist](./docs/rules/no-angularjs-sanitization-whitelist.md) | Calls to [`$compileProvider.aHrefSanitizationWhitelist`](https://docs.angularjs.org/api/ng/provider/$compileProvider#aHrefSanitizationWhitelist) or [`$compileProvider.imgSrcSanitizationWhitelist`](https://docs.angularjs.org/api/ng/provider/$compileProvider#imgSrcSanitizationWhitelist) configure whitelists in AngularJS sanitizer and need to be reviewed. | +| [@microsoft/sdl/no-cookies](./docs/rules/no-cookies.md) | HTTP cookies are an old client-side storage mechanism with inherent risks and limitations. Use Web Storage, IndexedDB or other modern methods instead. | +| [@microsoft/sdl/no-document-domain](./docs/rules/no-document-domain.md) | Writes to [`document.domain`](https://developer.mozilla.org/en-US/docs/Web/API/Document/domain) property must be reviewed to avoid bypass of [same-origin checks](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin). Usage of top level domains such as `azurewebsites.net` is strictly prohibited. | +| [@microsoft/sdl/no-document-write](./docs/rules/no-document-write.md) | Calls to document.write or document.writeln manipulate DOM directly without any sanitization and should be avoided. Use document.createElement() or similar methods instead. | +| [@microsoft/sdl/no-electron-node-integration](./docs/rules/no-electron-node-integration.md) | [Node.js Integration](https://www.electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content) must not be enabled in any renderer that loads remote content to avoid remote code execution attacks. | +| [@microsoft/sdl/no-html-method](./docs/rules/no-html-method.md) | Direct calls to method `html()` often (e.g. in jQuery framework) manipulate DOM without any sanitization and should be avoided. Use document.createElement() or similar methods instead. | +| [@microsoft/sdl/no-inner-html](./docs/rules/no-inner-html.md) | Assignments to innerHTML or outerHTML properties manipulate DOM directly without any sanitization and should be avoided. Use document.createElement() or similar methods instead. | +| [@microsoft/sdl/no-insecure-url](./docs/rules/no-insecure-url.md) | Insecure protocols such as [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol) or [FTP](https://en.wikipedia.org/wiki/File_Transfer_Protocol) should be replaced by their encrypted counterparts ([HTTPS](https://en.wikipedia.org/wiki/HTTPS), [FTPS](https://en.wikipedia.org/wiki/FTPS)) to avoid sending potentially sensitive data over untrusted networks in plaintext. | +| [@microsoft/sdl/no-msapp-exec-unsafe](./docs/rules/no-msapp-exec-unsafe.md) | Calls to [`MSApp.execUnsafeLocalFunction()`](https://docs.microsoft.com/en-us/previous-versions/hh772324(v=vs.85)) bypass script injection validation and should be avoided. | +| [@microsoft/sdl/no-postmessage-star-origin](./docs/rules/no-postmessage-star-origin.md) | Always provide specific target origin, not * when sending data to other windows using [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Security_concerns) to avoid data leakage outside of trust boundary. | +| [@microsoft/sdl/no-unsafe-alloc](./docs/rules/no-unsafe-alloc.md) | When calling [`Buffer.allocUnsafe`](https://nodejs.org/api/buffer.html#buffer_static_method_buffer_allocunsafe_size) and [`Buffer.allocUnsafeSlow`](https://nodejs.org/api/buffer.html#buffer_static_method_buffer_allocunsafeslow_size), the allocated memory is not wiped-out and can contain old, potentially sensitive data. | +| [@microsoft/sdl/no-winjs-html-unsafe](./docs/rules/no-winjs-html-unsafe.md) | Calls to [`WinJS.Utilities.setInnerHTMLUnsafe()`](https://docs.microsoft.com/en-us/previous-versions/windows/apps/br211696(v=win.10)) and similar methods do not perform any input validation and should be avoided. Use [`WinJS.Utilities.setInnerHTML()`](https://docs.microsoft.com/en-us/previous-versions/windows/apps/br211697(v=win.10)) instead. | +| [@microsoft/sdl/react-iframe-missing-sandbox](./docs/rules/react-iframe-missing-sandbox.md) | The [sandbox](https://www.w3schools.com/tags/att_iframe_sandbox.asp) attribute enables an extra set of restrictions for the content in the iframe and should always be specified. | +| [react/no-danger](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md) | Bans usage of `dangerouslySetInnerHTML` property in React as it allows passing unsanitized HTML in DOM. | +| [@typescript-eslint/no-implied-eval](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-implied-eval.md) | Similar to built-in ESLint rule `no-implied-eval`. Bans usage of `setTimeout()`, `setInterval()`, `setImmediate()`, `execScript()` or `new Function()` as they are similar to `eval()` and allow code execution from string arguments. | + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f7b8998 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + \ No newline at end of file diff --git a/config/angular.js b/config/angular.js new file mode 100644 index 0000000..4213670 --- /dev/null +++ b/config/angular.js @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Shareable config for Angular apps. + */ + +"use strict"; + +module.exports = { + plugins: [ + "@microsoft/sdl" + ], + rules: { + "@microsoft/sdl/no-angular-bypass-sanitizer": "error" + } +} \ No newline at end of file diff --git a/config/angularjs.js b/config/angularjs.js new file mode 100644 index 0000000..1e7ab4c --- /dev/null +++ b/config/angularjs.js @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Shareable config for AngularJS apps. + */ + +"use strict"; + +module.exports = { + plugins: [ + "@microsoft/sdl" + ], + rules: { + "@microsoft/sdl/no-angularjs-enable-svg": "error", + "@microsoft/sdl/no-angularjs-sanitization-whitelist": "error", + "@microsoft/sdl/no-angularjs-bypass-sce": "error" + } +} \ No newline at end of file diff --git a/config/common.js b/config/common.js new file mode 100644 index 0000000..d1a52b6 --- /dev/null +++ b/config/common.js @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Shareable config for common JavaScript apps. + */ + +"use strict"; + +module.exports = { + plugins: [ + "@microsoft/sdl" + ], + rules: { + "no-caller": "error", + "no-delete-var": "error", + "no-eval": "error", + "no-implied-eval": "error", + "no-new-func": "error", + "@microsoft/sdl/no-cookies": "error", + "@microsoft/sdl/no-document-domain": "error", + "@microsoft/sdl/no-document-write": "error", + "@microsoft/sdl/no-html-method": "error", + "@microsoft/sdl/no-inner-html": "error", + "@microsoft/sdl/no-insecure-url": "error", + "@microsoft/sdl/no-msapp-exec-unsafe": "error", + "@microsoft/sdl/no-postmessage-star-origin": "error", + "@microsoft/sdl/no-winjs-html-unsafe": "error" + } +} \ No newline at end of file diff --git a/config/electron.js b/config/electron.js new file mode 100644 index 0000000..26d398f --- /dev/null +++ b/config/electron.js @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Shareable config for Electron apps. + */ + +"use strict"; + +module.exports = { + plugins: [ + "@microsoft/sdl" + ], + rules: { + "@microsoft/sdl/no-electron-node-integration": "error" + } +} \ No newline at end of file diff --git a/config/node.js b/config/node.js new file mode 100644 index 0000000..a2e6d9b --- /dev/null +++ b/config/node.js @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Shareable config for Node apps. + */ + +"use strict"; + +module.exports = { + plugins: [ + "@microsoft/sdl", + "node" + ], + rules: { + "@microsoft/sdl/no-unsafe-alloc": "error", + "node/no-deprecated-api": "error" + } +} \ No newline at end of file diff --git a/config/react.js b/config/react.js new file mode 100644 index 0000000..b8971ef --- /dev/null +++ b/config/react.js @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Shareable config for React apps. + */ + +"use strict"; + +module.exports = { + plugins: [ + "react", + "@microsoft/sdl" + ], + rules: { + "react/no-danger": "error", + "@microsoft/sdl/react-iframe-missing-sandbox": "error" + } +} \ No newline at end of file diff --git a/config/recommended.js b/config/recommended.js new file mode 100644 index 0000000..37d0af5 --- /dev/null +++ b/config/recommended.js @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Default SDL recommended config for all applications. + */ + +"use strict"; + +module.exports = { + plugins: [ + "@microsoft/sdl", + "security" + ], + extends: [ + "plugin:@microsoft/sdl/required", + "plugin:@microsoft/sdl/typescript" + ], + // TODO: + // - Consider using SDL Recommended for identifying places in code that are often misused and can be potentially risky. + // - The action should be to review the code, not to remove it. + // - Good lists of such APIs + // - https://github.com/mozfreddyb/eslint-plugin-scanjs-rules/tree/master/lib/rules + // - https://github.com/ajinabraham/njsscan/tree/master/njsscan/rules/semantic_grep + // - Eventually we might remove detect-* rules from security plugin as they have high FP-rate. +} \ No newline at end of file diff --git a/config/required.js b/config/required.js new file mode 100644 index 0000000..46d5659 --- /dev/null +++ b/config/required.js @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Default SDL required config for all applications. + */ + +"use strict"; + +module.exports = { + plugins: [ + "@microsoft/sdl" + ], + extends: [ + "plugin:@microsoft/sdl/angular", + "plugin:@microsoft/sdl/angularjs", + "plugin:@microsoft/sdl/common", + "plugin:@microsoft/sdl/electron", + "plugin:@microsoft/sdl/node", + "plugin:@microsoft/sdl/react" + ] +} \ No newline at end of file diff --git a/config/typescript.js b/config/typescript.js new file mode 100644 index 0000000..d37cb7a --- /dev/null +++ b/config/typescript.js @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Shareable config for TypeScript applications. + */ + +"use strict"; + +module.exports = { + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 6, + sourceType: "module", + ecmaFeatures: { + jsx: true + } + }, + plugins: [ + "@typescript-eslint" + ], + overrides: [ + { + files: [ + "**/*.{ts,tsx}" + ], + rules: { + "@typescript-eslint/no-implied-eval": "error", + // @typescript-eslint/no-implied-eval offers more accurate results for typescript. + // thus we turn the more generic rule off for ts and tsx files. + // This also avoids duplicate hits. + "no-implied-eval": "off" + } + } + ] +} \ No newline at end of file diff --git a/docs/rules/no-angular-bypass-sanitizer.md b/docs/rules/no-angular-bypass-sanitizer.md new file mode 100644 index 0000000..35d9a95 --- /dev/null +++ b/docs/rules/no-angular-bypass-sanitizer.md @@ -0,0 +1,7 @@ +# Do not bypass Angular's built-in sanitization (no-angular-bypass-sanitizer) + +Calls to bypassSecurityTrustHtml, bypassSecurityTrustScript and similar methods bypass [DomSanitizer](https://angular.io/api/platform-browser/DomSanitizer#security-risk) in Angular and need to be reviewed. + +Sanitization should be disabled only in very rare and justifiable cases after careful review so that the risk of introducing Cross-Site-Scripting (XSS) vulnerability is minimized. + +The issue is well described in official [DomSanitizer](https://angular.io/api/platform-browser/DomSanitizer#security-risk) documentation. Also see [Angular Security Guide](https://angular.io/guide/security) for more details. diff --git a/docs/rules/no-angularjs-bypass-sce.md b/docs/rules/no-angularjs-bypass-sce.md new file mode 100644 index 0000000..0e73fdc --- /dev/null +++ b/docs/rules/no-angularjs-bypass-sce.md @@ -0,0 +1,7 @@ +# Do not bypass Strict Contextual Escaping (SCE) in AngularJS (no-angularjs-bypass-sce) + +Calls to `$sceProvider.enabled(false)`, `$sceDelegate.trustAs()`, `$sce.trustAs()` and relevant shorthand methods (e.g. `trustAsHtml` or `trustAsJs`) bypass [Strict Contextual Escaping (SCE)](https://docs.angularjs.org/api/ng/service/$sce#strict-contextual-escaping) in AngularJS and need to be reviewed. + +SCE should be bypassed only in very rare and justifiable cases after careful review so that the risk of introducing Cross-Site-Scripting (XSS) vulnerability is minimized. + +See [official documentation](https://docs.angularjs.org/api/ng/service/$sce#strict-contextual-escaping) for more details. \ No newline at end of file diff --git a/docs/rules/no-angularjs-enable-svg.md b/docs/rules/no-angularjs-enable-svg.md new file mode 100644 index 0000000..a5f2ad9 --- /dev/null +++ b/docs/rules/no-angularjs-enable-svg.md @@ -0,0 +1,7 @@ +# Do not enable SVG support in AngularJS (no-angularjs-enable-svg) + +Calls to [`$sanitizeProvider.enableSvg(true)`](https://docs.angularjs.org/api/ngSanitize/provider/$sanitizeProvider#enableSvg) increase attack surface of the application by enabling SVG support in AngularJS sanitizer and need to be reviewed. + +SVG support should be enabled only in very rare and justifiable cases after careful review so that the risk of introducing Clickjacking vulnerability is minimized. + +See [official documentation](https://docs.angularjs.org/api/ngSanitize/provider/$sanitizeProvider#enableSvg) for more details about the issue. diff --git a/docs/rules/no-angularjs-sanitization-whitelist.md b/docs/rules/no-angularjs-sanitization-whitelist.md new file mode 100644 index 0000000..ccf151a --- /dev/null +++ b/docs/rules/no-angularjs-sanitization-whitelist.md @@ -0,0 +1,7 @@ +# Do not bypass Angular's built-in sanitization (no-angularjs-sanitization-whitelist) + +Calls to [$compileProvider.aHrefSanitizationWhitelist](https://docs.angularjs.org/api/ng/provider/$compileProvider#aHrefSanitizationWhitelist) or [$compileProvider.imgSrcSanitizationWhitelist](https://docs.angularjs.org/api/ng/provider/$compileProvider#imgSrcSanitizationWhitelist) configure whitelists in AngularJS sanitizer and need to be reviewed. + +Sanitization should be disabled only in very rare and justifiable cases after careful review so that the risk of introducing Cross-Site-Scripting (XSS) vulnerability is minimized. + +See [official documentation](https://docs.angularjs.org/api/ng/provider/$compileProvider#aHrefSanitizationWhitelist) for more details about the issue. diff --git a/docs/rules/no-cookies.md b/docs/rules/no-cookies.md new file mode 100644 index 0000000..5949476 --- /dev/null +++ b/docs/rules/no-cookies.md @@ -0,0 +1,13 @@ +# Do not use HTTP cookies in modern applications (no-cookies) + +HTTP cookies are an old client-side storage mechanism with inherent risks and limitations. Use Web Storage, IndexedDB or other modern methods instead. + +Cookies should be used only in rare and justifiable cases after thorough security review. + +## Further Reading + +* [Using HTTP cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) + +## Related Rules + +* [tslint-microsoft-contrib/no-cookies](https://github.com/microsoft/tslint-microsoft-contrib/blob/master/src/noCookiesRule.ts) \ No newline at end of file diff --git a/docs/rules/no-document-domain.md b/docs/rules/no-document-domain.md new file mode 100644 index 0000000..e16c2c1 --- /dev/null +++ b/docs/rules/no-document-domain.md @@ -0,0 +1,7 @@ +# Do not write to document.domain property (no-document-domain) + +Writes to [`document.domain`](https://developer.mozilla.org/en-US/docs/Web/API/Document/domain) property must be reviewed to avoid bypass of [same-origin checks](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin). Usage of top level domains such as `azurewebsites.net` is strictly prohibited. + +## Related Rules + +* [tslint-microsoft-contrib/no-document-domain](https://github.com/microsoft/tslint-microsoft-contrib/blob/master/src/noDocumentDomainRule.ts) diff --git a/docs/rules/no-document-write.md b/docs/rules/no-document-write.md new file mode 100644 index 0000000..3726090 --- /dev/null +++ b/docs/rules/no-document-write.md @@ -0,0 +1,7 @@ +# Do not write to DOM directly using document.write or document.writeln methods (no-document-write) + +Calls to document.write or document.writeln manipulate DOM directly without any sanitization and should be avoided. Use document.createElement() or similar methods instead. + +## Related Rules + +* [tslint-microsoft-contrib/no-document-write](https://github.com/microsoft/tslint-microsoft-contrib/blob/master/src/noDocumentWriteRule.ts) diff --git a/docs/rules/no-electron-node-integration.md b/docs/rules/no-electron-node-integration.md new file mode 100644 index 0000000..91b776e --- /dev/null +++ b/docs/rules/no-electron-node-integration.md @@ -0,0 +1,9 @@ +# Do not enable Node.js Integration for Remote Content (no-electron-node-integration) + +[Node.js Integration](https://www.electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content) must not be enabled in any renderer that loads remote content to avoid remote code execution attacks. + +[Rule Source Code](../../lib/rules/no-electron-node-integration.js) + +## Related Rules + +* [codeql/js/enabling-electron-renderer-node-integration](https://help.semmle.com/wiki/display/JS/Enabling+Node.js+integration+for+Electron+web+content+renderers) diff --git a/docs/rules/no-html-method.md b/docs/rules/no-html-method.md new file mode 100644 index 0000000..4ee59a9 --- /dev/null +++ b/docs/rules/no-html-method.md @@ -0,0 +1,7 @@ +# Do not write to DOM directly using jQuery html() method (no-html-method) + +Direct calls to method `html()` often (e.g. in jQuery framework) manipulate DOM without any sanitization and should be avoided. Use document.createElement() or similar methods instead. + +## Related Rules + +* [tslint-microsoft-contrib/no-inner-html](https://github.com/microsoft/tslint-microsoft-contrib/blob/master/src/noInnerHtml.ts) diff --git a/docs/rules/no-inner-html.md b/docs/rules/no-inner-html.md new file mode 100644 index 0000000..15facd9 --- /dev/null +++ b/docs/rules/no-inner-html.md @@ -0,0 +1,11 @@ +# Do not write to DOM directly using innerHTML/outerHTML property (no-inner-html) + +Assignments to [innerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)/[outerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML) properties or calls to [insertAdjacentHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML) method manipulate DOM directly without any sanitization and should be avoided. Use document.createElement() or similar methods instead. + +* [Rule Source](../../lib/rules/no-inner-html.js) +* [Rule Test](../../tests/lib/rules/no-inner-html.js) + +## Related Rules + +* [tslint-microsoft-contrib/no-inner-html](https://github.com/microsoft/tslint-microsoft-contrib/blob/master/src/noInnerHtml.ts) +* [eslint-plugin-no-unsanitized](https://github.com/mozilla/eslint-plugin-no-unsanitized/blob/master/docs/rules/method.md) \ No newline at end of file diff --git a/docs/rules/no-insecure-random.md b/docs/rules/no-insecure-random.md new file mode 100644 index 0000000..04bb94f --- /dev/null +++ b/docs/rules/no-insecure-random.md @@ -0,0 +1,17 @@ +# Do not use insecure random functions + +Methods such as Math.random or crypto.pseudoRandomBytes do not produce cryptographically-secure random numbers and must not be used for security purposes such as generating tokens, passwords or keys. + +Use crypto.randomBytes() or window.crypto.getRandomValues() instead. + +## Related Rules + +* [tslint-microsoft-contrib/no-insecure-random](https://github.com/microsoft/tslint-microsoft-contrib/blob/master/src/insecureRandomRule.ts) +- https://help.semmle.com/wiki/display/JS/Insecure+randomness + - [source](https://github.com/github/codeql/blob/master/javascript/ql/src/semmle/javascript/security/dataflow/InsecureRandomnessCustomizations.qll) +- https://vulncat.fortify.com/en/detail?id=desc.semantic.abap.insecure_randomness#JavaScript +- https://rules.sonarsource.com/javascript/RSPEC-2245 + - [source](https://github.com/SonarSource/SonarJS/blob/master/eslint-bridge/src/rules/pseudo-random.ts) +- https://github.com/nodesecurity/eslint-plugin-security/blob/master/rules/detect-pseudoRandomBytes.js +- https://github.com/gkouziik/eslint-plugin-security-node/blob/master/lib/rules/detect-insecure-randomness.js + diff --git a/docs/rules/no-insecure-url.md b/docs/rules/no-insecure-url.md new file mode 100644 index 0000000..6322dd7 --- /dev/null +++ b/docs/rules/no-insecure-url.md @@ -0,0 +1,55 @@ +# Do not use insecure URLs (no-insecure-url) + +Insecure protocols such as [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol) or [FTP](https://en.wikipedia.org/wiki/File_Transfer_Protocol) should be replaced by their encrypted counterparts ([HTTPS](https://en.wikipedia.org/wiki/HTTPS), [FTPS](https://en.wikipedia.org/wiki/FTPS)) to avoid sending potentially sensitive data over untrusted networks in plaintext. + +- [Rule Source](../../lib/rules/no-insecure-url.js) +- [Rule Test](../../tests/lib/rules/no-insecure-url.js) + +## Options +This rule comes with two [default lists](../../lib/rules/no-insecure-url.js#L13): +- **blacklist** - a RegEx list of insecure URL patterns. +- **exceptions** - a RegEx list of common false positive patterns. For example, HTTP URLs to XML schemas are usually allowed as they are used as identifiers, not for establishing actual network connections. + +These lists can be overrided by providing options. + +--- +For example, providing these options... : +```javascript +"@microsoft/sdl/no-insecure-url": ["error", { + "blacklist": ["^(http|ftp):\\/\\/", "^https:\\/\\/www\\.disallow-example\\.com"], + "exceptions": ["^http:\\/\\/schemas\\.microsoft\\.com\\/\\/?.*"] + }] +``` + +... overrides the internal blacklist, blocking the following URL patterns... : +- `http://`... +- `ftp://`... +- `https://www.disallow-example.com` + +... and also overrides the internal exceptions list, allowing the following URL patterns as exceptions.: +- `http://schemas.microsoft.com` + - `http://schemas.microsoft.com/sharepoint` + - `http://schemas.microsoft.com/path/subpath` + - ... + +URLs in neither the blacklist nor the exceptions list, are allowed: +- `telnet://`... +- `ws://`... +- ... + +--- + +**Note**: The RegEx for the lists is provided within a string in a JSON. It is without delimiting slashes `/ /` and thus users cannot pass RegEx parameters. We make it case-insensitive after user input. Do not forget to escape characters: +```javascript +let pureRegex = /^https:\/\/www\.disallow-example\.com/; +let regexInString = "^https:\\/\\/www\\.disallow-example\\.com"; +``` + +## Related Rules +* [tslint-microsoft-contrib/no-http-string](https://github.com/microsoft/tslint-microsoft-contrib/blob/master/src/noHttpStringRule.ts) +* [CodeQL/InsecureDownloadCustomizations.qll](https://github.com/github/codeql/blob/master/javascript/ql/src/semmle/javascript/security/dataflow/InsecureDownloadCustomizations.qll#L62) +* [DevSkim/DS137138](https://github.com/microsoft/DevSkim/blob/main/guidance/DS137138.md) +* [Fortify/insecure_transport](https://vulncat.fortify.com/en/detail?id=desc.config.java.insecure_transport#JavaScript%2fTypeScript) + +## Further Reading +* [HTTPS Everywhere](https://en.wikipedia.org/wiki/HTTPS_Everywhere) diff --git a/docs/rules/no-msapp-exec-unsafe.md b/docs/rules/no-msapp-exec-unsafe.md new file mode 100644 index 0000000..4863d51 --- /dev/null +++ b/docs/rules/no-msapp-exec-unsafe.md @@ -0,0 +1,3 @@ +# Do not bypass script injection validation (no-msapp-exec-unsafe) + +Calls to `MSApp.execUnsafeLocalFunction()` bypass script injection validation and should be avoided. diff --git a/docs/rules/no-postmessage-star-origin.md b/docs/rules/no-postmessage-star-origin.md new file mode 100644 index 0000000..f756bd9 --- /dev/null +++ b/docs/rules/no-postmessage-star-origin.md @@ -0,0 +1,3 @@ +# Do not use * as target origin when sending data to other windows (no-postmessage-star-origin) + +Always provide specific target origin, not * when sending data to other windows using [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Security_concerns) to avoid data leakage outside of trust boundary. diff --git a/docs/rules/no-unsafe-alloc.md b/docs/rules/no-unsafe-alloc.md new file mode 100644 index 0000000..21892f6 --- /dev/null +++ b/docs/rules/no-unsafe-alloc.md @@ -0,0 +1,12 @@ +# Do not allocate uninitialized buffers in Node.js (no-unsafe-alloc) + +When calling [`Buffer.allocUnsafe`](https://nodejs.org/api/buffer.html#buffer_static_method_buffer_allocunsafe_size) and [`Buffer.allocUnsafeSlow`](https://nodejs.org/api/buffer.html#buffer_static_method_buffer_allocunsafeslow_size), the allocated memory is not wiped-out and can contain old, potentially sensitive data. + +These methods should be used only in justifiable cases (e.g. due to performance reasons) after thorough security review. + +* [Rule Source](../../lib/rules/no-unsafe-alloc.js) +* [Rule Test](../../tests/lib/rules/no-unsafe-alloc.js) + +## Resources + +* [Node.js - What makes Buffer.allocUnsafe() and Buffer.allocUnsafeSlow() "unsafe"?](https://nodejs.org/api/buffer.html#buffer_what_makes_buffer_allocunsafe_and_buffer_allocunsafeslow_unsafe) diff --git a/docs/rules/no-winjs-html-unsafe.md b/docs/rules/no-winjs-html-unsafe.md new file mode 100644 index 0000000..3e9af12 --- /dev/null +++ b/docs/rules/no-winjs-html-unsafe.md @@ -0,0 +1,3 @@ +# Do not set HTML using unsafe methods from WinJS.Utilities (no-winjs-html-unsafe) + +Calls to [`setInnerHTMLUnsafe`](https://docs.microsoft.com/en-us/previous-versions/windows/apps/br211696(v=win.10)), [`setOuterHTMLUnsafe`](https://docs.microsoft.com/en-us/previous-versions/windows/apps/br211698(v=win.10)) or [`insertAdjacentHTMLUnsafe`](https://docs.microsoft.com/en-us/previous-versions/windows/apps/br229832(v=win.10)) methods from [Windows Library for JavaScript](https://docs.microsoft.com/en-us/previous-versions/windows/apps/mt502392(v=win.10)) do not perform input validation and should be avoided. Use alternate methods such as [`setInnerHTML`](https://docs.microsoft.com/en-us/previous-versions/windows/apps/br211697(v=win.10)) instead. \ No newline at end of file diff --git a/docs/rules/react-iframe-missing-sandbox.md b/docs/rules/react-iframe-missing-sandbox.md new file mode 100644 index 0000000..24dd407 --- /dev/null +++ b/docs/rules/react-iframe-missing-sandbox.md @@ -0,0 +1,17 @@ +# An iframe element is missing a sandbox attribute (react-iframe-missing-sandbox) + +The [sandbox](https://www.w3schools.com/tags/att_iframe_sandbox.asp) attribute enables an extra set of restrictions for the content in the iframe and should always be specified. + +Additional functionality such as the ability to run scripts should be enabled only in justifiable cases after thorough security review. + +* [Rule Source](../../lib/rules/react-iframe-missing-sandbox.js) +* [Rule Test](../../tests/lib/rules/react-iframe-missing-sandbox.js) + +## Related Rules + +* [tslint-microsoft-contrib/react-iframe-missing-sandbox](https://github.com/microsoft/tslint-microsoft-contrib/blob/master/src/reactIframeMissingSandboxRule.ts) + +## More Reading + +* [How to safely inject HTML in React using an iframe](https://medium.com/the-thinkmill/how-to-safely-inject-html-in-react-using-an-iframe-adc775d458bc) +* [Play safely in sandboxed IFrames](https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/) \ No newline at end of file diff --git a/lib/ast-utils.js b/lib/ast-utils.js new file mode 100644 index 0000000..df2e073 --- /dev/null +++ b/lib/ast-utils.js @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Common utils for AST. + */ + +"use strict"; + +module.exports = { + isTypeScriptParserServices(parserServices) { + // Check properties specific to @typescript-eslint/parser + return ( + parserServices && + parserServices.program && + parserServices.esTreeNodeToTSNodeMap && + parserServices.tsNodeToESTreeNodeMap + ); + }, + hasFullTypeInformation(context) { + var hasFullTypeInformation = ( + context && + this.isTypeScriptParserServices(context.parserServices) && + context.parserServices.hasFullTypeInformation === true + ); + return hasFullTypeInformation; + }, + getFullTypeChecker(context) { + return this.hasFullTypeInformation(context) ? context.parserServices.program.getTypeChecker() : null; + }, + getNodeType(node, context) { + const typeChecker = context.parserServices.program.getTypeChecker(); + const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node); + const tsType = typeChecker.getTypeAtLocation(tsNode); + return typeChecker.typeToString(tsType); + }, + getCallerType(fullTypeChecker, object, context){ + const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(object); + const tsType = fullTypeChecker.getTypeAtLocation(tsNode); + const type = fullTypeChecker.typeToString(tsType); + return type; + }, + isDocumentObject(node, context, fullTypeChecker) { + if (fullTypeChecker) { + const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node); + const tsType = fullTypeChecker.getTypeAtLocation(tsNode); + const type = fullTypeChecker.typeToString(tsType); + return (type === "Document"); + } + + // Best-effort checking without Type information + switch (node.type) { + case "Identifier": + return node != undefined && node.name == "document"; + case "MemberExpression": + return ( + node != undefined && + node.property != undefined && + node.property.name == "document" && ( + (node.object != undefined && + node.object.name == "window") || + ( + node.object != undefined && + node.object.property != undefined && + node.object.property.name == "window" && + ( + + (node.object.object != undefined && node.object.object.type == "ThisExpression") || + (node.object.object != undefined && node.object.object.name == "globalThis") + ) + ) + ) + ); + }; + return false; + } +}; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..efb7740 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview ESLint plugin that implements rules intended for static testing during SDL + * @author Antonios Katopodis + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Plugin Definition +//------------------------------------------------------------------------------ +const path = require("path"); +const fs = require('fs'); + +function readFilesAsMap(relativeDir) { + var absoluteDir = path.resolve(__dirname, relativeDir); + var files = fs.readdirSync(absoluteDir); + var output = {}; + files.forEach(filename => { + var file = path.parse(filename); + var obj = require(path.join(absoluteDir, file.base)); + output[file.name] = obj; + }); + return output; +} + +module.exports = { + rules: readFilesAsMap("./rules"), + configs: readFilesAsMap("../config") +} diff --git a/lib/rules/no-angular-bypass-sanitizer.js b/lib/rules/no-angular-bypass-sanitizer.js new file mode 100644 index 0000000..3493ee9 --- /dev/null +++ b/lib/rules/no-angular-bypass-sanitizer.js @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow bypassing Angular's built-in sanitizer + * @author Antonios Katopodis + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "Calls to bypassSecurityTrustHtml, bypassSecurityTrustScript and similar methods bypass DomSanitizer in Angular and need to be reviewed.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-angular-bypass-sanitizer.md" + }, + messages: { + noBypass: "Do not bypass Angular's built-in sanitizer" + } + }, + create: function(context) { + return { + "CallExpression[arguments!=''][callee.property.name=/bypassSecurityTrust(Html|ResourceUrl|Script|Style|Url)/]"(node) { + context.report( + { + node: node, + messageId: "noBypass" + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-angularjs-bypass-sce.js b/lib/rules/no-angularjs-bypass-sce.js new file mode 100644 index 0000000..a4e7019 --- /dev/null +++ b/lib/rules/no-angularjs-bypass-sce.js @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow bypassing Strict Contextual Escaping (SCE) in AngularJS + * @author Antonios Katopodis + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "Calls to $sceProvider.enabled(false), $sceDelegate.trustAs(), $sce.trustAs() and relevant shorthand methods (e.g. trustAsHtml or trustAsJs) bypass Strict Contextual Escaping (SCE) in AngularJS and need to be reviewed.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-angularjs-bypass-sce.md" + }, + messages: { + doNotBypass: "Do not bypass Strict Contextual Escaping (SCE) in AngularJS" + } + }, + create: function (context) { + + function reportIt(node) { + context.report({ + node: node, + messageId: "doNotBypass" + }); + } + + return { + "CallExpression[arguments!=''][callee.object.name='$sceProvider'][callee.property.name='enabled']"(node) { + // Known false positives + if (node.arguments == undefined || node.arguments.length != 1 || (node.arguments[0].type == "Literal" && /true|1/.test(node.arguments[0].value))){ + return; + } + return reportIt(node) + }, + "CallExpression[arguments!=''][callee.object.name='$sceDelegate'][callee.property.name='trustAs']": reportIt, + "CallExpression[arguments!=''][callee.object.name='$sce'][callee.property.name=/trustAs(Css|Html|Js|ResourceUrl|Url)?/]"(node) { + // Known false positives + if ( + node.arguments + && node.arguments.length === 1 + && node.arguments[0].type === "Literal" + && node.arguments[0].value === "" + ) { + return; + } + + return reportIt(node); + } + }; + } +}; + +// TODO: Review https://docs.angularjs.org/api/ng/provider/$sceDelegateProvider#resourceUrlWhitelist and https://docs.angularjs.org/api/ng/provider/$sceDelegateProvider#resourceUrlBlacklist diff --git a/lib/rules/no-angularjs-enable-svg.js b/lib/rules/no-angularjs-enable-svg.js new file mode 100644 index 0000000..d2a680d --- /dev/null +++ b/lib/rules/no-angularjs-enable-svg.js @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow enabling SVG in AngularJS apps + * @author Antonios Katopodis + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "Calls to $sanitizeProvider.enableSvg(true) increase attack surface of the application by enabling SVG support in AngularJS sanitizer and need to be reviewed.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-angularjs-enable-svg.md" + }, + messages: { + doNotEnableSVG: "Do not enable SVG support in AngularJS" + } + }, + create: function (context) { + return { + "CallExpression[callee.object.name='$sanitizeProvider'][callee.property.name='enableSvg']"(node) { + // Known false positives + if ( + (node.arguments != undefined && + node.arguments.length != 1) || + ( + node.arguments[0].type == "Literal" && ( + node.arguments[0].value == "false" || node.arguments[0].value == "0" + ) + )) + { + return; + } + context.report( + { + node: node, + messageId: "doNotEnableSVG" + }); + } + }; + } +}; + +// TODO: Add rules for $sanitizeProvider.addValidElements() and $sanitizeProvider.addValidAttrs() \ No newline at end of file diff --git a/lib/rules/no-angularjs-sanitization-whitelist.js b/lib/rules/no-angularjs-sanitization-whitelist.js new file mode 100644 index 0000000..dcbfa98 --- /dev/null +++ b/lib/rules/no-angularjs-sanitization-whitelist.js @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow modifying sanitization whitelist in AngularJS + * @author Antonios Katopodis + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "Calls to [`$compileProvider.aHrefSanitizationWhitelist`](https://docs.angularjs.org/api/ng/provider/$compileProvider#aHrefSanitizationWhitelist) or [`$compileProvider.imgSrcSanitizationWhitelist`](https://docs.angularjs.org/api/ng/provider/$compileProvider#imgSrcSanitizationWhitelist) configure whitelists in AngularJS sanitizer and need to be reviewed.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-angularjs-sanitization-whitelist.md" + }, + messages: { + noSanitizationWhitelist: "Do not modify sanitization whitelist in AngularJS" + } + }, + create: function(context) { + return { + "CallExpression[arguments!=''][callee.object.name='$compileProvider'][callee.property.name=/(aHref|imgSrc)SanitizationWhitelist/]"(node) { + context.report( + { + node: node, + messageId: "noSanitizationWhitelist" + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-cookies.js b/lib/rules/no-cookies.js new file mode 100644 index 0000000..19fc215 --- /dev/null +++ b/lib/rules/no-cookies.js @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow usage of HTTP cookies + * @author Antonios Katopodis + */ + +"use strict"; + +const astUtils = require("../ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "HTTP cookies are an old client-side storage mechanism with inherent risks and limitations. Use Web Storage, IndexedDB or other more modern methods instead.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-cookies.md" + }, + messages: { + doNotUseCookies: "Do not use HTTP cookies in modern applications" + } + }, + create: function (context) { + const fullTypeChecker = astUtils.getFullTypeChecker(context); + return { + "MemberExpression[property.name='cookie']"(node) { + if (astUtils.isDocumentObject(node.object, context, fullTypeChecker)) { + context.report({ + node: node, + messageId: "doNotUseCookies" + }); + } + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-document-domain.js b/lib/rules/no-document-domain.js new file mode 100644 index 0000000..32af7ea --- /dev/null +++ b/lib/rules/no-document-domain.js @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow document.domain property + * @author Antonios Katopodis + */ + +"use strict"; + +const astUtils = require("../ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "Writes to [`document.domain`](https://developer.mozilla.org/en-US/docs/Web/API/Document/domain) property must be reviewed to avoid bypass of [same-origin checks](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin). Usage of top level domains such as `azurewebsites.net` is strictly prohibited.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-document-domain.md" + }, + messages: { + default: 'Do not write to document.domain property' + } + }, + create: function(context) { + const fullTypeChecker = astUtils.getFullTypeChecker(context); + return { + "AssignmentExpression[operator='='][left.property.name='domain']"(node) { + if (astUtils.isDocumentObject(node.left.object, context, fullTypeChecker)) { + context.report( + { + node: node, + messageId: "default" + }); + } + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-document-write.js b/lib/rules/no-document-write.js new file mode 100644 index 0000000..20b36b1 --- /dev/null +++ b/lib/rules/no-document-write.js @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow document.write or document.writeln method call + * @author Antonios Katopodis + */ + +"use strict"; + +const astUtils = require("../ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "Calls to document.write or document.writeln manipulate DOM directly without any sanitization and should be avoided. Use document.createElement() or similar methods instead.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-document-write.md" + }, + messages: { + default: 'Do not write to DOM directly using document.write or document.writeln methods' + } + }, + create: function(context) { + const fullTypeChecker = astUtils.getFullTypeChecker(context); + return { + "CallExpression[arguments.length=1][callee.property.name=/write|writeln/]"(node) { + if (astUtils.isDocumentObject(node.callee.object, context, fullTypeChecker)) { + context.report( + { + node: node, + messageId: "default" + }); + } + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-electron-node-integration.js b/lib/rules/no-electron-node-integration.js new file mode 100644 index 0000000..5a3d1cb --- /dev/null +++ b/lib/rules/no-electron-node-integration.js @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow enabling Node.js integration in Electron apps + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "[Node.js Integration](https://www.electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content) must not be enabled in any renderer that loads remote content to avoid remote code execution attacks.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-electron-node-integration.md" + }, + messages: { + default: "Do not enable Node.js Integration for Remote Content" + } + }, + create: function(context) { + return { + "NewExpression[callee.name=/BrowserWindow|BrowserView/] > ObjectExpression.arguments > Property.properties[key.name=webPreferences] > ObjectExpression.value > Property.properties[key.name=/nodeIntegration|nodeIntegrationInWorker|nodeIntegrationInSubFrames/][value.value='true']"(node) { + context.report( + { + node: node, + messageId: "default" + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-html-method.js b/lib/rules/no-html-method.js new file mode 100644 index 0000000..ef5e939 --- /dev/null +++ b/lib/rules/no-html-method.js @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow call to html() method + * @author Antonios Katopodis + */ + +"use strict"; + +const astUtils = require("../ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs:{ + description: "Direct calls to method `html()` often (e.g. in jQuery framework) manipulate DOM without any sanitization and should be avoided. Use document.createElement() or similar methods instead.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-html-method.md" + }, + messages: { + default: 'Do not write to DOM directly using jQuery html() method' + } + }, + create: function(context) { + const fullTypeChecker = astUtils.getFullTypeChecker(context); + return { + // TODO: + // - Cover similar methods that can manipulate DOM such as append(string), jQuery(string) + // - Improve rule with type information from TypeScript parser + // - Consider ignoring all Literals? + "CallExpression[arguments.length=1] > MemberExpression.callee[property.name='html']"(node) { + // Known false positives + if ( + // element.html("") + node.parent.arguments[0].type === "Literal" + && ( + node.parent.arguments[0].value === "" + || node.parent.arguments[0].value === null + ) + ) { + return; + } + context.report( + { + node: node, + messageId: "default" + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-inner-html.js b/lib/rules/no-inner-html.js new file mode 100644 index 0000000..40f4a9d --- /dev/null +++ b/lib/rules/no-inner-html.js @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow assignment to innerHTML or outerHTML properties + * @author Antonios Katopodis + */ + +"use strict"; + +const astUtils = require("../ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + description: "Assignments to [innerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)/[outerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML) properties or calls to [insertAdjacentHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML) method manipulate DOM directly without any sanitization and should be avoided. Use document.createElement() or similar methods instead.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-inner-html.md" + }, + messages: { + noInnerHtml: 'Do not write to DOM directly using innerHTML/outerHTML property', + noInsertAdjacentHTML: 'Do not write to DOM using insertAdjacentHTML method' + } + }, + create: function (context) { + const fullTypeChecker = astUtils.getFullTypeChecker(context); + + function getNodeTypeAsString(node) { + if (fullTypeChecker && node) { + const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node); + const tsType = fullTypeChecker.getTypeAtLocation(tsNode); + const type = fullTypeChecker.typeToString(tsType); + return type; + } + return "any"; + } + + function mightBeHTMLElement(node) { + const type = getNodeTypeAsString(node); + return type === "HTMLElement" || type === "any"; + } + + return { + "CallExpression[arguments.length=2] > MemberExpression.callee[property.name='insertAdjacentHTML']"(node) { + // Ignore known false positives + if ( + node.parent != undefined + && node.parent.arguments != undefined + && node.parent.arguments.length >= 1 + && node.parent.arguments[1] != undefined + // element.insertAdjacentHTML('') + && node.parent.arguments[1].type === 'Literal' && node.parent.arguments[1].value === '' + ) { + return; + } + + if (mightBeHTMLElement(node.object)) { + context.report({ + node: node, + messageId: "noInsertAdjacentHTML" + }); + } + }, + "AssignmentExpression[left.type='MemberExpression'][left.property.name=/innerHTML|outerHTML/]"(node) { + // Ignore known false positives + if ( + node.right != undefined + // element.innerHTML = '' + && node.right.type === 'Literal' && node.right.value === '' + ) { + return; + } + + if (mightBeHTMLElement(node.left.object)) { + context.report({ + node: node, + messageId: "noInnerHtml" + }); + } + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-insecure-random.js b/lib/rules/no-insecure-random.js new file mode 100644 index 0000000..b1a86e8 --- /dev/null +++ b/lib/rules/no-insecure-random.js @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow call to Math.random and crypto.pseudoRandomBytes functions + * @author Antonios Katopodis + */ + +"use strict"; + +const astUtils = require("../ast-utils"); +const eslint = require("eslint/lib/eslint"); +const path = require('path'); + +const bannedRandomLibraries = [ + 'chance', + 'random-number', + 'random-int', + 'random-float', + 'random-seed', + 'unique-random' +] + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs:{ + description: `Methods such as Math.random or crypto.pseudoRandomBytes do not produce cryptographically-secure random numbers and must not be used for security purposes such as generating tokens, passwords or keys. + + Use crypto.randomBytes() or window.crypto.getRandomValues() instead. + + `, + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-insecure-random.md" + }, + messages: { + default: 'Do not use pseudo-random number generators for generating secret values such as tokens, passwords or keys.' + } + }, + create: function(context) { + const fullTypeChecker = astUtils.getFullTypeChecker(context); + return { + "CallExpression > MemberExpression[property.name='pseudoRandomBytes']"(node) { + var notFalsePositive = false; + + if (fullTypeChecker) { + const type = astUtils.getCallerType(fullTypeChecker, node.object, context); + notFalsePositive = type === "any" || type === "Crypto"; + }else{ + notFalsePositive = node.object.name === 'crypto'; + } + + if(notFalsePositive){ + context.report({ + node: node, + messageId: "default" + }); + } + }, + "CallExpression > MemberExpression[property.name='random']"(node) { + var notFalsePositive = false; + if (fullTypeChecker) { + const type = astUtils.getCallerType(fullTypeChecker, node.object, context); + notFalsePositive = type === "any" || type === "Math"; + }else{ + notFalsePositive = node.object.name === 'Math'; + } + + if(notFalsePositive){ + context.report({ + node: node, + messageId: "default" + }); + } + }, + ImportDeclaration(node){ + if(bannedRandomLibraries.includes(path.basename(node.source.value))){ + context.report({ + node: node, + messageId: "default" + }); + } + }, + "CallExpression[callee.name='require'][arguments.length=1]"(node){ + var requireName = path.parse(path.basename(node.arguments[0].value)).name; + if(bannedRandomLibraries.includes(requireName)){ + context.report({ + node: node, + messageId: "default" + }); + } + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-insecure-url.js b/lib/rules/no-insecure-url.js new file mode 100644 index 0000000..b15ea25 --- /dev/null +++ b/lib/rules/no-insecure-url.js @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Disallows usage of insecure protocols in URL strings + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +const DEFAULT_BLACKLIST = [ + /^(ftp|http|telnet|ws):\/\//i +]; + +const DEFAULT_EXCEPTIONS = [ // TODO: add more typical false positives such as XML schemas after more testing + /^http:(\/\/|\\u002f\\u002f)schemas\.microsoft\.com(\/\/|\\u002f\\u002f)?.*/i, + /^http:(\/\/|\\u002f\\u002f)schemas\.openxmlformats\.org(\/\/|\\u002f\\u002f)?.*/i +]; + +module.exports = { + defaultBlacklist: DEFAULT_BLACKLIST, + defaultExceptions: DEFAULT_EXCEPTIONS, + meta: { + type: "suggestion", + fixable: "code", + schema: [ + { + type: "object", + properties: { + blacklist: { + type: "array", + items: { + type: "string" + } + }, + exceptions: { + type: "array", + items: { + type: "string" + } + }, + }, + additionalProperties: false + } + ], + docs: { + category: "Security", + description: "Insecure protocols such as [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol) or [FTP](https://en.wikipedia.org/wiki/File_Transfer_Protocol) should be replaced by their encrypted counterparts ([HTTPS](https://en.wikipedia.org/wiki/HTTPS), [FTPS](https://en.wikipedia.org/wiki/FTPS)) to avoid sending (potentially sensitive) data over untrusted network in plaintext.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-insecure-url.md" + }, + messages: { + doNotUseInsecureUrl: "Do not use insecure URLs" + } + }, + create: function (context) { + const options = context.options[0] || {}; + const blacklist = (options.blacklist || DEFAULT_BLACKLIST).map((pattern) => { return new RegExp(pattern, "i"); }); + const exceptions = (options.exceptions || DEFAULT_EXCEPTIONS).map((pattern) => { return new RegExp(pattern, "i"); }); + + function matches(patterns, value) { + return patterns.find((re) => { return re.test(value) }) !== undefined; + } + + return { + "Literal"(node) { + if (typeof node.value === "string") { + if (matches(blacklist, node.value) && !matches(exceptions, node.value)) { + context.report({ + node: node, + messageId: "doNotUseInsecureUrl" + }); + } + } + }, + "TemplateElement"(node) { + if (typeof node.value.raw === "string" && typeof node.value.cooked === "string") { + const rawStringText = node.value.raw; + const cookedStringText = node.value.cooked; + + if ((matches(blacklist, rawStringText) && !matches(exceptions, rawStringText)) || + (matches(blacklist, cookedStringText) && !matches(exceptions, cookedStringText))) { + context.report({ + node: node, + messageId: "doNotUseInsecureUrl" + }); + } + } + } + }; + }, +}; \ No newline at end of file diff --git a/lib/rules/no-msapp-exec-unsafe.js b/lib/rules/no-msapp-exec-unsafe.js new file mode 100644 index 0000000..a6d2189 --- /dev/null +++ b/lib/rules/no-msapp-exec-unsafe.js @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow MSApp.execUnsafeLocalFunction method call + * @author Antonios Katopodis + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs:{ + description: "Calls to [`MSApp.execUnsafeLocalFunction()`](https://docs.microsoft.com/en-us/previous-versions/hh772324(v=vs.85)) bypass script injection validation and should be avoided.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-msapp-exec-unsafe.md" + }, + messages: { + default: 'Do not bypass script injection validation' + } + }, + create: function(context) { + return { + "CallExpression[arguments.length=1][callee.object.name='MSApp'][callee.property.name='execUnsafeLocalFunction']"(node) { + context.report( + { + node: node, + messageId: "default" + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-postmessage-star-origin.js b/lib/rules/no-postmessage-star-origin.js new file mode 100644 index 0000000..294343c --- /dev/null +++ b/lib/rules/no-postmessage-star-origin.js @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow * as target origin in window.postMessage + */ + +"use strict"; + +const astUtils = require("../ast-utils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + description: "Always provide specific target origin, not * when sending data to other windows using [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Security_concerns) to avoid data leakage outside of trust boundary.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-postmessage-star-origin.md" + }, + messages: { + default: 'Do not use * as target origin when sending data to other windows' + } + }, + create: function (context) { + const fullTypeChecker = astUtils.getFullTypeChecker(context); + return { + "CallExpression[arguments.length>=2][arguments.length<=3][callee.property.name=postMessage]"(node) { + + // Check that second argument (target origin) is Literal "*" + if (!(node.arguments[1].type === 'Literal' && node.arguments[1].value == '*')) { + return; + } + + // Check that object type is Window when full type information is available + if (fullTypeChecker) { + const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node.callee.object); + const tsType = fullTypeChecker.getTypeAtLocation(tsNode); + const type = fullTypeChecker.typeToString(tsType); + if (type !== "any" && type !== "Window") { + return; + } + } + + context.report({ + node: node, + messageId: "default" + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-unsafe-alloc.js b/lib/rules/no-unsafe-alloc.js new file mode 100644 index 0000000..51e8a7c --- /dev/null +++ b/lib/rules/no-unsafe-alloc.js @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + description: "When calling [`Buffer.allocUnsafe`](https://nodejs.org/api/buffer.html#buffer_static_method_buffer_allocunsafe_size) and [`Buffer.allocUnsafeSlow`](https://nodejs.org/api/buffer.html#buffer_static_method_buffer_allocunsafeslow_size), the allocated memory is not wiped-out and can contain old, potentially sensitive data.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-unsafe-alloc.md" + }, + messages: { + default: 'Do not allocate uninitialized buffers in Node.js' + } + }, + create: function (context) { + return { + "MemberExpression[object.name='Buffer'][property.name=/allocUnsafe|allocUnsafeSlow/]"(node) { + // Known false positives + if ( + node.parent != undefined && + node.parent.arguments != undefined && + node.parent.arguments.length != undefined && + // Buffer.allocUnsafe(0); + node.parent.type === 'CallExpression' && + node.parent.arguments.length == 1 && + node.parent.arguments[0] != undefined && + node.parent.arguments[0].type === 'Literal' && + node.parent.arguments[0].value == '0' + ) { + return; + } + context.report({ + node: node, + messageId: "default" + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/no-winjs-html-unsafe.js b/lib/rules/no-winjs-html-unsafe.js new file mode 100644 index 0000000..4884469 --- /dev/null +++ b/lib/rules/no-winjs-html-unsafe.js @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to disallow WinJS.Utilities.setInnerHTMLUnsafe or WinJS.Utilities.setOuterHTMLUnsafe method call + * @author Antonios Katopodis + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs:{ + description: "Calls to [`WinJS.Utilities.setInnerHTMLUnsafe()`](https://docs.microsoft.com/en-us/previous-versions/windows/apps/br211696(v=win.10)) and similar methods do not perform any input validation and should be avoided. Use [`WinJS.Utilities.setInnerHTML()`](https://docs.microsoft.com/en-us/previous-versions/windows/apps/br211697(v=win.10)) instead.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-winjs-html-unsafe.md" + }, + messages: { + default: 'Do not set HTML using unsafe methods from WinJS.Utilities' + } + }, + create: function(context) { + return { + "CallExpression[callee.object.object.name='WinJS'][callee.object.property.name='Utilities'][callee.property.name=/(insertAdjacent|setInner|setOuter)HTMLUnsafe/]"(node) { + context.report( + { + node: node, + messageId: "default" + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/rules/react-iframe-missing-sandbox.js b/lib/rules/react-iframe-missing-sandbox.js new file mode 100644 index 0000000..a62655b --- /dev/null +++ b/lib/rules/react-iframe-missing-sandbox.js @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Rule to enforce sandbox attribute on iframe elements + */ + +"use strict"; + +// TODO: Follow-up on https://github.com/yannickcr/eslint-plugin-react/issues/2754 and try to merge rule into eslint-plugin-react + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ +module.exports = { + meta: { + type: "suggestion", + fixable: "code", + schema: [], + docs: { + category: "Security", + description: "The [sandbox](https://www.w3schools.com/tags/att_iframe_sandbox.asp) attribute enables an extra set of restrictions for the content in the iframe and should always be specified.", + url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/react-iframe-missing-sandbox.md" + }, + 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' + }); + } + } + }; + } +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1aac546 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1638 @@ +{ + "name": "@microsoft/eslint-plugin-sdl", + "version": "0.1.5", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.7.0.tgz", + "integrity": "sha512-4OEcPON3QIx0ntsuiuFP/TkldmBGXf0uKxPQlGtS/W2F3ndYm8Vgdpj/woPJkzUc65gd3iR+qi3K8SDQP/obFg==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "3.7.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.7.0.tgz", + "integrity": "sha512-xpfXXAfZqhhqs5RPQBfAFrWDHoNxD5+sVB5A46TF58Bq1hRfVROrWHcQHHUM9aCBdy9+cwATcvCbRg8aIRbaHQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.7.0", + "@typescript-eslint/typescript-estree": "3.7.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.7.0.tgz", + "integrity": "sha512-2LZauVUt7jAWkcIW7djUc3kyW+fSarNEuM3RF2JdLHR9BfX/nDEnyA4/uWz0wseoWVZbDXDF7iF9Jc342flNqQ==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "3.7.0", + "@typescript-eslint/types": "3.7.0", + "@typescript-eslint/typescript-estree": "3.7.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/types": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.7.0.tgz", + "integrity": "sha512-reCaK+hyKkKF+itoylAnLzFeNYAEktB0XVfSQvf0gcVgpz1l49Lt6Vo9x4MVCCxiDydA0iLAjTF/ODH0pbfnpg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.7.0.tgz", + "integrity": "sha512-xr5oobkYRebejlACGr1TJ0Z/r0a2/HUf0SXqPvlgUMwiMqOCu/J+/Dr9U3T0IxpE5oLFSkqMx1FE/dKaZ8KsOQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "3.7.0", + "@typescript-eslint/visitor-keys": "3.7.0", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.7.0.tgz", + "integrity": "sha512-k5PiZdB4vklUpUX4NBncn5RBKty8G3ihTY+hqJsCdMuD0v4jofI5xuqwnVcWxfv6iTm2P/dfEa2wMUnsUY8ODw==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "acorn": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.3.1.tgz", + "integrity": "sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-scope": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", + "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", + "dev": true, + "requires": { + "acorn": "^7.2.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.2.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..62ba9b6 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "@microsoft/eslint-plugin-sdl", + "version": "0.1.5", + "description": "ESLint plugin focused on common security issues and misconfigurations discoverable during static testing as part of Microsoft Security Development Lifecycle (SDL)", + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin", + "sdl" + ], + "author": "Microsoft", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/eslint-plugin-sdl" + }, + "homepage": "https://github.com/microsoft/eslint-plugin-sdl", + "bugs": "https://github.com/microsoft/eslint-plugin-sdl/issues", + "main": "lib/index.js", + "scripts": { + "test": "mocha tests --recursive" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^3.7.0", + "@typescript-eslint/parser": "^3.7.0", + "eslint": "^7.1.0", + "mocha": "^7.2.0", + "typescript": "^3.9.7" + }, + "engines": { + "node": ">=0.10.0" + }, + "license": "ISC" +} diff --git a/tests/fixtures/estree.ts b/tests/fixtures/estree.ts new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/tsconfig.json b/tests/fixtures/tsconfig.json new file mode 100644 index 0000000..5ffdb29 --- /dev/null +++ b/tests/fixtures/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "jsx": "preserve" + }, + "include": [ + "estree.ts" + ] +} \ No newline at end of file diff --git a/tests/lib/rules/no-angular-bypass-sanitizer.js b/tests/lib/rules/no-angular-bypass-sanitizer.js new file mode 100644 index 0000000..58bfb53 --- /dev/null +++ b/tests/lib/rules/no-angular-bypass-sanitizer.js @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + "bypassSecurityTrustHtml('XSS')", + "x.bypassSecurityTrustHtml()", + "x.BypassSecurityTrustHtml('XSS')" + ], + invalid: [ + { + code: "$('p').bypassSecurityTrustHtml('XSS');", + errors: [ + { + messageId: "noBypass", + line: 1, + endLine: 1, + column: 1, + endColumn: 38 + } + ] + }, + { + code: "$('p').bypassSecurityTrustResourceUrl('XSS')", + errors: [{ messageId: "noBypass"}] + }, + { + code: "$('p').bypassSecurityTrustScript('XSS')", + errors: [{ messageId: "noBypass"}] + }, + { + code: "$('p').bypassSecurityTrustStyle('XSS')", + errors: [{ messageId: "noBypass"}] + }, + { + code: "$('p').bypassSecurityTrustUrl('XSS')", + errors: [{ messageId: "noBypass"}] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-angularjs-bypass-sce.js b/tests/lib/rules/no-angularjs-bypass-sce.js new file mode 100644 index 0000000..dbb1eab --- /dev/null +++ b/tests/lib/rules/no-angularjs-bypass-sce.js @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + "trustAsHtml()", + "$sce.trustAsHtml()", + "$sce.trustAsHtml('')", + "$sce.TrustAsHtml('XSS')", + "x.trustAsHtml('XSS')", + "$sceProvider.enabled()", + "$sceProvider.enabled(true)", + "$sceProvider.enabled(1)", + ], + invalid: [ + { + code: "$sceDelegate.trustAs($sce.HTML, 'XSS')", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sce.trustAs($sce.HTML, 'XSS')", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sce.trustAsCss('XSS')", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sce.trustAsHtml('XSS')", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sce.trustAsJs('XSS')", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sce.trustAsResourceUrl('XSS')", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sce.trustAsUrl('XSS')", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sceProvider.enabled(false)", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sceProvider.enabled(0)", + errors: [{ messageId: "doNotBypass" }] + }, + { + code: "$sceProvider.enabled(true != true)", + errors: [{ messageId: "doNotBypass" }] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-angularjs-enable-svg.js b/tests/lib/rules/no-angularjs-enable-svg.js new file mode 100644 index 0000000..e41d3ac --- /dev/null +++ b/tests/lib/rules/no-angularjs-enable-svg.js @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + "enableSvg()", + "enableSvg(true)", + "$sanitizeProvider.enableSvg()", + "$sanitizeProvider.enableSvg(false)", + "$sanitizeProvider.enableSvg(0)", + "$sanitizeProvider.EnableSvg(0)" + ], + invalid: [ + { + code: "$sanitizeProvider.enableSvg(true)", + errors: [{ messageId: "doNotEnableSVG" }] + }, + { + code: "$sanitizeProvider.enableSvg(1)", + errors: [{ messageId: "doNotEnableSVG" }] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-angularjs-sanitization-whitelist.js b/tests/lib/rules/no-angularjs-sanitization-whitelist.js new file mode 100644 index 0000000..b391eeb --- /dev/null +++ b/tests/lib/rules/no-angularjs-sanitization-whitelist.js @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + "aHrefSanitizationWhitelist('.*')", + "x.aHrefSanitizationWhitelist('.*')", + "$compileProvider.aHrefSanitizationWhitelist()", + "$compileProvider.AHrefSanitizationWhitelist('.*')" + ], + invalid: [ + { + code: "$compileProvider.aHrefSanitizationWhitelist('.*');", + errors: [ + { + messageId: "noSanitizationWhitelist", + line: 1, + endLine: 1, + column: 1, + endColumn: 50 + } + ] + }, + { + code: "$compileProvider.imgSrcSanitizationWhitelist('.*');", + errors: [ + { + messageId: "noSanitizationWhitelist", + line: 1, + endLine: 1, + column: 1, + endColumn: 51 + } + ] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-cookies.js b/tests/lib/rules/no-cookies.js new file mode 100644 index 0000000..f2e1c8e --- /dev/null +++ b/tests/lib/rules/no-cookies.js @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +const typescriptParserPath = require.resolve("@typescript-eslint/parser"); +const testUtils = require("../test-utils"); + +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + ` +function documentLikeAPIFunction(){ + return { + cookie:'fake.cookie' + } +} +var document2 = documentLikeAPIFunction(); +document2.cookie = '...'; +document2.cookie = '...'; +documentLikeAPIFunction().cookie = '...' + `, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` +interface DocumentLikeAPI { + cookie: string; +} +function documentLikeAPIFunction(): DocumentLikeAPI { + return null; +} +function X() { + // These usages are OK because they are not on the DOM document + var document: DocumentLikeAPI = documentLikeAPIFunction(); + document.cookie = '...'; + document.cookie = '...'; +} + +documentLikeAPIFunction().cookie = '...'; +` + } + ], + invalid: [ + { + code: "document.cookie = '...'", + errors: [{ messageId: "doNotUseCookies" }] + }, + { + code: "window.document.cookie = '...'", + errors: [{ messageId: "doNotUseCookies" }] + }, + { + code: "this.window.document.cookie = '...'", + errors: [{ messageId: "doNotUseCookies" }] + }, + { + code: "globalThis.window.document.cookie = '...'", + errors: [{ messageId: "doNotUseCookies" }] + }, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` +function documentFunction(): Document { + return window.document; +} +documentFunction().cookie = '...'; + `, + errors: [{ messageId: "doNotUseCookies" }] + }, + { + parser: typescriptParserPath, + code: ` +namespace Sample { + function method() { + return document.cookie; + } +} + `, + errors: [{ messageId: "doNotUseCookies" }] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-document-domain.js b/tests/lib/rules/no-document-domain.js new file mode 100644 index 0000000..c832429 --- /dev/null +++ b/tests/lib/rules/no-document-domain.js @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const testUtils = require("../test-utils"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` +interface DocumentLikeAPI { + domain: string; +} +function documentLikeAPIFunction(): DocumentLikeAPI { + return null; +} +function main() { + var document: DocumentLikeAPI = documentLikeAPIFunction(); + document.domain = 'somevalue'; +} + ` + } + ], + invalid: [ + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: "var doc = window.document; doc.domain = 'somevalue';", + errors: [{ messageId: "default" }] + }, + { + code: "document.domain = 'somevalue'", + errors: [{ messageId: "default" }] + }, + { + code: "window.document.domain = 'somevalue'", + errors: [{ messageId: "default" }] + }, + { + code: ` +var somevalue = 'somevalue'; +document.domain = somevalue; +window.document.domain = somevalue; + `, + errors: [ + { + line: 3, + messageId: "default" + }, + { + line: 4, + messageId: "default" + } + ] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-document-write.js b/tests/lib/rules/no-document-write.js new file mode 100644 index 0000000..4be2092 --- /dev/null +++ b/tests/lib/rules/no-document-write.js @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const testUtils = require("../test-utils"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + interface DocumentLikeAPI { + write: ((arg : string) => void); + writeln: ((arg : string) => void); + } + function documentLikeAPIFunction() : DocumentLikeAPI { + return { + write: () => {}, + writeln: () => {}, + }; + } + ` + }, + { + code: ` + function documentLikeAPIFunction() { + return { + write: function(){}, + writeln: function(){} + }; + } + var documentAPI = documentLikeAPIFunction(); + documentAPI.write('...'); + documentAPI.writeln('...'); + documentLikeAPIFunction().write('...'); + documentLikeAPIFunction().writeln('...'); + // wrong # of args + document.write(); + document.write('', ''); + document.writeln(); + document.writeln('', ''); + ` + } + ], + invalid: [ + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + var doc = document; + doc.write('...'); + doc.writeln('...'); + function documentFunction() : Document { + return window.document; + } + documentFunction().write('...'); + documentFunction().writeln('...'); + `, + errors: [ + { messageId: "default", line: 3 }, + { messageId: "default", line: 4 }, + { messageId: "default", line: 8 }, + { messageId: "default", line: 9 } + ] + }, + { + code: ` + document.write('...'); + document.writeln('...'); + window.document.write('...'); + window.document.writeln('...'); + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 3 }, + { messageId: "default", line: 4 }, + { messageId: "default", line: 5 } + ] + } + ] +}); diff --git a/tests/lib/rules/no-electron-node-integration.js b/tests/lib/rules/no-electron-node-integration.js new file mode 100644 index 0000000..a17fd01 --- /dev/null +++ b/tests/lib/rules/no-electron-node-integration.js @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + { + code: ` + var mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: false, + nodeIntegrationInWorker: false, + nodeIntegrationInSubFrames: false + } + }); + var view = new BrowserView({ + webPreferences: { + nodeIntegration: false + } + }); + ` + } + ], + invalid: [ + { + code: ` + var mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: true, + nodeIntegrationInWorker: true, + nodeIntegrationInSubFrames: true + } + }); + `, + errors: [ + { messageId: "default", line: 4}, + { messageId: "default", line: 5}, + { messageId: "default", line: 6} + ] + }, + { + code: ` + var view = new BrowserView({ + webPreferences: { + nodeIntegration: true, + nodeIntegrationInWorker: true, + nodeIntegrationInSubFrames: true + } + }); + `, + errors: [ + { messageId: "default", line: 4}, + { messageId: "default", line: 5}, + { messageId: "default", line: 6} + ] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-html-method.js b/tests/lib/rules/no-html-method.js new file mode 100644 index 0000000..47ba7d2 --- /dev/null +++ b/tests/lib/rules/no-html-method.js @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); +const testUtils = require("../test-utils"); + +ruleTester.run(ruleId, rule, { + valid: [ + "test.html = 'test'", + "test.html()", + "test.html('','')", + "element.html('');", + "element.html(null);" + ], + invalid: [ + { + code: "$('p').html('XSS')", + errors: [{ messageId: "default", line: 1 }] + }, + { + code: "$(selector).html(sample_function())", + errors: [{ messageId: "default", line: 1 }] + }, + { + parser: testUtils.tsParser, + parserOptions: { + ecmaVersion: 6, + sourceType: "module" + }, + env: { + }, + code: ` + import $ from "jquery"; + test.html('XSS'); + `, + errors: [ + { messageId: "default", line: 3 } + ] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-inner-html.js b/tests/lib/rules/no-inner-html.js new file mode 100644 index 0000000..5ad1758 --- /dev/null +++ b/tests/lib/rules/no-inner-html.js @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); +const testUtils = require("../test-utils"); + +ruleTester.run(ruleId, rule, { + valid: [ + "var test = element.innerHTML", + "var test = element.outerHTML", + "document.body.innerHTML = ''", + "document.test", + "element.insertAdjacentHTML()", + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + class Test { + innerHTML: string; + outerHTML: string; + constructor(test: string) { + this.innerHTML = test; + this.outerHTML = test; + } + }; + let test = new Test("test"); + test.innerHTML = test; + test.outerHTML = test; + ` + } + ], + invalid: [ + // TypeScript with full type information + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + var element = document.getElementById(id); + element.innerHTML = 'test'; + element.outerHTML = 'test'; + element.insertAdjacentHTML('beforebegin', 'foo'); + `, + errors: [ + { messageId: "noInnerHtml", line: 3 }, + { messageId: "noInnerHtml", line: 4 }, + { messageId: "noInsertAdjacentHTML", line: 5 } + ] + }, + { + code: ` + element.innerHTML = 'test'; + parent.child.innerHTML += 'test'; + `, + errors: [ + { messageId: "noInnerHtml", line: 2 }, + { messageId: "noInnerHtml", line: 3 } + ] + }, + { + code: ` + element.outerHTML = 'test'; + parent.child.outerHTML += 'test'; + `, + errors: [ + { messageId: "noInnerHtml", line: 2 }, + { messageId: "noInnerHtml", line: 3 } + ] + }, + { + code: "element.insertAdjacentHTML('beforebegin', 'foo')", + errors: [{ messageId: "noInsertAdjacentHTML", line: 1 }] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-insecure-random.js b/tests/lib/rules/no-insecure-random.js new file mode 100644 index 0000000..59a401c --- /dev/null +++ b/tests/lib/rules/no-insecure-random.js @@ -0,0 +1,198 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); +const testUtils = require("../test-utils"); + +ruleTester.run(ruleId, rule, { + valid: [ + "Math.Random;", + "Math.random;", + "math.random();", + "random();", + { + code: ` + Math.Random; + Math.random; + math.random(); + random(); + ` + }, + { + + code:` + require('./node_modules/not-unsafe-random'); + require('eslint'); + require('test'); + require('random-package'); + require('random-float2'); + require('random2-seed'); + ` + }, + { + parserOptions: testUtils.moduleParserOptions, + code:` + import './node_modules/untest'; + import 'random'; + import 'random-3'; + import 'eslint'; + import 'eslint-plugin-sdl'; + import 'testing'; + ` + }, + { + + code:` + cryptos.pseudoRandomBytes(); + pseudoRandomBytes(); + pseudoRandomByte(); + cryptos.pseudoRondomBytes(); + ` + }, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + function random(){} + + random(); + + Math.Random; + Math.random; + ` + }, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code:` + function pseudoRandomBytes(){} + function pseudoRandomByte(){} + + pseudoRandomBytes(); + pseudoRandomByte(); + cryptos.pseudoRondomBytes(); + cryptos.pseudoRondomBytes(); + ` + } + ], + invalid: [ + { + code: ` + Math.random(); + crypto.pseudoRandomBytes(); + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 3 } + ] + }, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + Math.random(); + this.Math.random(); + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 3 } + ] + }, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + function notMath() : Math{ + return Math; + } + + notMath().random(); + `, + errors: [ + { messageId: "default", line: 6 } + ] + }, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + crypto.pseudoRandomBytes(); + `, + errors: [ + { messageId: "default", line: 2 } + ] + }, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + function notCrypto() : Crypto{ + return crypto; + } + + notCrypto().pseudoRandomBytes(); + `, + errors: [ + { messageId: "default", line: 6 } + ] + }, + { + parserOptions: testUtils.moduleParserOptions, + code:` + import './node_modules/unique-random'; + import 'chance'; + import 'random-number'; + import 'random-int'; + import 'random-float'; + import 'random-seed'; + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 3 }, + { messageId: "default", line: 4 }, + { messageId: "default", line: 5 }, + { messageId: "default", line: 6 }, + { messageId: "default", line: 7 } + ] + }, + { + parserOptions: testUtils.moduleParserOptions, + code:` + import * as chance1 from 'chance'; + import defaultExport from 'chance'; + import { chance } from 'chance'; + import { chance as chance2 } from 'chance'; + import { chance3, chance4 } from 'chance'; + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 3 }, + { messageId: "default", line: 4 }, + { messageId: "default", line: 5 }, + { messageId: "default", line: 6 } + ] + }, + { + code:` + require('./node_modules/unique-random'); + require('**/chance.js'); + require('random-number'); + require('random-int'); + require('random-float'); + require('random-seed'); + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 3 }, + { messageId: "default", line: 4 }, + { messageId: "default", line: 5 }, + { messageId: "default", line: 6 }, + { messageId: "default", line: 7 } + ] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-insecure-url.js b/tests/lib/rules/no-insecure-url.js new file mode 100644 index 0000000..bc52f98 --- /dev/null +++ b/tests/lib/rules/no-insecure-url.js @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join("../../../lib/rules/", ruleId)); +const RuleTester = require("eslint").RuleTester; +const testUtils = require("../test-utils"); + +/** + * Notes: + * - ES2015/ES6 introduced template literals (``). This is considered in parserOptions for relevant tests. + */ + +let ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + { // should allow https,ftps strings in variables + code: ` + var x = 'https://www.example.com' + var y = 'ftps://www.example.com' + ` + }, + { // should allow https,ftps template strings in variables + code: ` + var x = \`https://www.template-examples.com\` + var y = \`ftps://www.template-file-examples.com\` + `, + parserOptions: testUtils.moduleParserOptions + }, + { // should allow https,ftps multipart template strings in variables + code: ` + var x = \`https://www.\${multipartExample}.com\` + var y = \`ftps://www.\${multipartExample}.com\` + `, + parserOptions: testUtils.moduleParserOptions + }, + { // should allow http,ftp in middle of string + code: "var x = 'The protocol may be http://, https://, ftp:// or ftps://'" + }, + { // should allow https,ftps strings in default values + code: ` + function f(x : string = 'https://www.example.com') {} + function f(y : string = 'ftps://www.example.com') {} + `, + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + }, + { // should allow user-provided exceptions matches, regardless of upper/lower-case + code: ` + var a1 = 'http://www.allow-example.com' + var a2 = 'HtTp://www.allow-example.com/path' + var b1 = 'FTP://www.allow-file-example.com' + var c1 = 'LDaP://www.allow-ldap-example.com' + `, + options: [{ + exceptions: ["HTTP:\/\/www\.allow-example\.com\/?.*", "FtP:\/\/www\.allow-file-example\.com", "LdaP:\/\/www\.allow-ldap-example\.com"] + }] + }, + ], + invalid: [ + { // should ban http,ftp strings in variables + code: ` + var x1 = 'http://www.examples.com' + var x2 = 'HTTP://www.examples.com' + var y1 = 'ftp://www.file-examples.com' + var y2 = 'FTP://www.file-examples.com' + `, + errors: [ + { messageId: "doNotUseInsecureUrl", line: 2}, + { messageId: "doNotUseInsecureUrl", line: 3}, + { messageId: "doNotUseInsecureUrl", line: 4}, + { messageId: "doNotUseInsecureUrl", line: 5} + ], + }, + { // should ban http,ftp template strings in variables + code: ` + var x1 = \`http://www.template-examples.com\` + var x2 = \`HTTP://www.template-examples.com\` + var y1 = \`ftp://www.file-examples.com\` + var y2 = \`FTP://www.file-examples.com\` + `, + errors: [ + { messageId: "doNotUseInsecureUrl", line: 2}, + { messageId: "doNotUseInsecureUrl", line: 3}, + { messageId: "doNotUseInsecureUrl", line: 4}, + { messageId: "doNotUseInsecureUrl", line: 5} + ], + parserOptions: testUtils.moduleParserOptions + }, + { // should ban http,ftp multipart template strings in variables + code: ` + var x1 = \`http://www.\${multipartExample}.com\`; + var y1 = \`ftp://www.\${multipartExample}.com\`; + `, + errors: [ + { messageId: "doNotUseInsecureUrl", line: 2}, + { messageId: "doNotUseInsecureUrl", line: 3}, + ], + parserOptions: testUtils.moduleParserOptions + }, + { // should ban http,ftp strings in default values + code: ` + function f(x : string = 'http://www.example.com') {} + function f(y : string = 'ftp://www.example.com') {} + `, + errors: [ + { messageId: "doNotUseInsecureUrl", line: 2}, + { messageId: "doNotUseInsecureUrl", line: 3}, + ], + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + }, + { // should ban user-provided blacklist matches, regardless of upper/lower-case + code: ` + var a1 = 'http://www.ban-example.com' + var a2 = 'HTTP://www.ban-example.com/path' + var b1 = 'FtP://www.ban-file-example.com' + var c1 = 'LDAp://www.ban-ldap-example.com' + `, + errors: [ + { messageId: "doNotUseInsecureUrl", line: 2}, + { messageId: "doNotUseInsecureUrl", line: 3}, + { messageId: "doNotUseInsecureUrl", line: 4}, + { messageId: "doNotUseInsecureUrl", line: 5} + ], + options: [{ + blacklist: ["htTp:\/\/www\.ban-example\.com\/?.*", "fTp:\/\/www\.ban-file-example\.com\/?.*", "lDAp:\/\/www\.ban-ldap-example\.com\/?.*"] + }] + }, + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-msapp-exec-unsafe.js b/tests/lib/rules/no-msapp-exec-unsafe.js new file mode 100644 index 0000000..4aa4d90 --- /dev/null +++ b/tests/lib/rules/no-msapp-exec-unsafe.js @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); +const testUtils = require("../test-utils"); + +ruleTester.run(ruleId, rule, { + valid: [ + "test.execUnsafeLocalFunction = 'test'", + "MSApp.execUnsafeLocalFunction()" + ], + invalid: [ + { + code: "MSApp.execUnsafeLocalFunction(testfunc)", + errors: [ + { messageId: "default", line: 1, type: 'CallExpression' } + ] + } + ] +}); diff --git a/tests/lib/rules/no-postmessage-star-origin.js b/tests/lib/rules/no-postmessage-star-origin.js new file mode 100644 index 0000000..b40c5ec --- /dev/null +++ b/tests/lib/rules/no-postmessage-star-origin.js @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const testUtils = require("../test-utils"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + "window.postMessage()", + "window.postMessage = ''", + "window.postMessage(1)", + "window.postMessage(1, 2, 3, 4)", + "window.postMessage('data', 'https://target.domain')", + "window.postMessage('data', 'https://target.domain', 'menubar=yes')", + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` +class WindowLike { + postMessage(): void { + }; +} +function main() { + var w: WindowLike = new WindowLike(); + w.postMessage('test', '*'); +} + ` + } + ], + invalid: [ + { + code: ` + any.postMessage(message, "*"); + any.postMessage(message, "*", "menubar=yes"); + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 3 } + ] + }, + { + parser: testUtils.tsParser, + parserOptions: testUtils.tsParserOptions, + code: ` + window.frames[0].postMessage(message, "*"); + var w1 = window.open(url); + w1.postMessage(message, "*"); + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 4 } + ] + }, + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-unsafe-alloc.js b/tests/lib/rules/no-unsafe-alloc.js new file mode 100644 index 0000000..bd99f37 --- /dev/null +++ b/tests/lib/rules/no-unsafe-alloc.js @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +"use strict"; +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); + +ruleTester.run(ruleId, rule, { + valid: [ + "foo.allocUnsafe", + "Buffer.allocUnsafe(0)", + "Buffer.allocUnsafeSlow(0)" + ], + invalid: [ + { + code: ` + var buf1 = Buffer.allocUnsafe(10); + var buf2 = Buffer.allocUnsafeSlow(10) + `, + errors: [ + { messageId: "default", line: 2 }, + { messageId: "default", line: 3 } + ] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/rules/no-winjs-html-unsafe.js b/tests/lib/rules/no-winjs-html-unsafe.js new file mode 100644 index 0000000..bbcf3dc --- /dev/null +++ b/tests/lib/rules/no-winjs-html-unsafe.js @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; +var ruleTester = new RuleTester(); +const testUtils = require("../test-utils"); + +ruleTester.run(ruleId, rule, { + valid: [ + 'element.insertAdjacentHTMLUnsafe = "test";' + ], + invalid: [ + { + code: ` + WinJS.Utilities.insertAdjacentHTMLUnsafe(element, position, text); + WinJS.Utilities.setInnerHTMLUnsafe(element, text); + WinJS.Utilities.setOuterHTMLUnsafe(element, text); + `, + errors: [ + { messageId: "default", line: 2, type: 'CallExpression' }, + { messageId: "default", line: 3, type: 'CallExpression' }, + { messageId: "default", line: 4, type: 'CallExpression' } + ] + } + ] +}); diff --git a/tests/lib/rules/react-iframe-missing-sandbox.js b/tests/lib/rules/react-iframe-missing-sandbox.js new file mode 100644 index 0000000..377de5b --- /dev/null +++ b/tests/lib/rules/react-iframe-missing-sandbox.js @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +'use strict'; + +const path = require("path"); +const ruleId = path.parse(__filename).name; +const rule = require(path.join('../../../lib/rules/', ruleId)); +const RuleTester = require("eslint").RuleTester; + +var ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } + } +}); + +ruleTester.run(ruleId, rule, { + valid: [ + { code: '
;' }, + { code: ';' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' }, + { code: '' } + ], + invalid: [ + { + code: ';', + errors: [{ messageId: 'attributeMissing' }] + }, + { + code: ';', + errors: [{ messageId: 'attributeMissing' }] + }, + { + code: '', + errors: [{ messageId: 'invalidValue', data: { value: '__unknown__' } }] + }, + { + code: '', + errors: [{ messageId: 'invalidValue', data: { value: '__unknown__' } }] + }, + { + code: '', + errors: [{ messageId: 'invalidValue', data: { value: '__unknown__' } }] + }, + { + code: '', + errors: [ + { messageId: 'invalidValue', data: { value: '__unknown__' } }, + { messageId: 'invalidValue', data: { value: '__unknown__' } } + ] + }, + { + code: ';', + errors: [{ messageId: 'invalidCombination' }] + }, + { + code: ';', + errors: [{ messageId: 'invalidCombination' }] + } + ] +}); \ No newline at end of file diff --git a/tests/lib/test-utils.js b/tests/lib/test-utils.js new file mode 100644 index 0000000..f798258 --- /dev/null +++ b/tests/lib/test-utils.js @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * @fileoverview Common utils for tests. + */ + +"use strict"; + +const path = require("path"); + +module.exports = { + tsParser: require.resolve("@typescript-eslint/parser"), + tsParserOptions: { + tsconfigRootDir: path.join(__dirname, '../fixtures'), + project: 'tsconfig.json', + sourceType: "module" + }, + moduleParserOptions: { + ecmaVersion: 6, + sourceType: "module" + } +}; \ No newline at end of file