diff --git a/.github/workflows/E2E integration.yml b/.github/workflows/E2E integration.yml index 72b608e..79cda1b 100644 --- a/.github/workflows/E2E integration.yml +++ b/.github/workflows/E2E integration.yml @@ -10,9 +10,9 @@ on: 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 + PROJECT: nodejs # The name of the project you want to clone. It must be on github + REPOSITORY: node # The repository name + FOLDER_TO_SCAN: lib # 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: @@ -22,7 +22,7 @@ jobs: strategy: matrix: - os: [ubuntu-18.04, windows-2019] + os: [ubuntu-latest, windows-latest] steps: - name: Setup Node.js environment @@ -70,7 +70,7 @@ jobs: - name: Run eslint run: npx eslint - -c node_modules/@microsoft/eslint-plugin-sdl/config/recommended.js + -c node_modules/@microsoft/eslint-plugin-sdl/config/required.js ../${{env.PROJECT}}/${{env.FOLDER_TO_SCAN}}/ --ext .js --parser-options=project:../${{env.PROJECT}}/${{env.TS_CONFIG_PATH}} diff --git a/.github/workflows/node-version-integration.yml b/.github/workflows/node-version-integration.yml index eeb2569..0960d37 100644 --- a/.github/workflows/node-version-integration.yml +++ b/.github/workflows/node-version-integration.yml @@ -16,8 +16,8 @@ jobs: strategy: matrix: - os: [ubuntu-20.04, ubuntu-18.04, windows-2019, macos-10.15] - node-version: [12.x, 14.x] + os: [ubuntu-latest, windows-latest] + node-version: [12.x, 14.x, 16.x] steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index f5b7bad..b281583 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ # Licensed under the MIT License. node_modules/ .vscode/ -.package-lock.json \ No newline at end of file +.package-lock.json diff --git a/config/react.js b/config/react.js index b8971ef..510a60c 100644 --- a/config/react.js +++ b/config/react.js @@ -14,6 +14,15 @@ module.exports = { ], rules: { "react/no-danger": "error", - "@microsoft/sdl/react-iframe-missing-sandbox": "error" + "@microsoft/sdl/react-iframe-missing-sandbox": "error", + "react/jsx-no-target-blank": ["error", + { + allowReferrer: false, + enforceDynamicLinks: 'always', + warnOnSpreadAttributes: true, + links: true, + forms: true + } + ] } } \ No newline at end of file diff --git a/lib/ast-utils.js b/lib/ast-utils.js index df2e073..b0eaf22 100644 --- a/lib/ast-utils.js +++ b/lib/ast-utils.js @@ -28,23 +28,18 @@ module.exports = { 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; + getNodeTypeAsString(fullTypeChecker, node, context) { + if (fullTypeChecker && node) { + const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node); + const tsType = fullTypeChecker.getTypeAtLocation(tsNode); + const type = fullTypeChecker.typeToString(tsType); + return type; + } + return "any"; }, isDocumentObject(node, context, fullTypeChecker) { if (fullTypeChecker) { - const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node); - const tsType = fullTypeChecker.getTypeAtLocation(tsNode); - const type = fullTypeChecker.typeToString(tsType); + const type = this.getNodeTypeAsString(fullTypeChecker, node, context); return (type === "Document"); } @@ -58,7 +53,8 @@ module.exports = { node.property != undefined && node.property.name == "document" && ( (node.object != undefined && - node.object.name == "window") || + typeof node.object.name === "string" && + node.object.name.toLowerCase().endsWith('window')) || ( node.object != undefined && node.object.property != undefined && diff --git a/lib/rules/no-inner-html.js b/lib/rules/no-inner-html.js index 40f4a9d..24d6547 100644 --- a/lib/rules/no-inner-html.js +++ b/lib/rules/no-inner-html.js @@ -30,19 +30,9 @@ module.exports = { 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"; + const type = astUtils.getNodeTypeAsString(fullTypeChecker, node, context); + return type.match(/HTML.*Element/) || type === "any"; } return { @@ -85,4 +75,4 @@ module.exports = { } }; } -}; \ No newline at end of file +}; diff --git a/lib/rules/no-insecure-random.js b/lib/rules/no-insecure-random.js index 88b775f..644b5ff 100644 --- a/lib/rules/no-insecure-random.js +++ b/lib/rules/no-insecure-random.js @@ -47,7 +47,7 @@ module.exports = { var notFalsePositive = false; if (fullTypeChecker) { - const type = astUtils.getCallerType(fullTypeChecker, node.object, context); + const type = astUtils.getNodeTypeAsString(fullTypeChecker, node.object, context); notFalsePositive = type === "any" || type === "Crypto"; }else{ notFalsePositive = node.object.name === 'crypto'; @@ -63,7 +63,7 @@ module.exports = { "CallExpression > MemberExpression[property.name='random']"(node) { var notFalsePositive = false; if (fullTypeChecker) { - const type = astUtils.getCallerType(fullTypeChecker, node.object, context); + const type = astUtils.getNodeTypeAsString(fullTypeChecker, node.object, context); notFalsePositive = type === "any" || type === "Math"; }else{ notFalsePositive = node.object.name === 'Math'; diff --git a/package.json b/package.json index 5f8f4e9..0d4da6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/eslint-plugin-sdl", - "version": "0.1.8", + "version": "0.1.9", "description": "ESLint plugin focused on common security issues and misconfigurations discoverable during static testing as part of Microsoft Security Development Lifecycle (SDL)", "keywords": [ "eslint", @@ -19,6 +19,11 @@ "scripts": { "test": "mocha tests --recursive" }, + "dependencies": { + "eslint-plugin-node": "11.1.0", + "eslint-plugin-security": "1.4.0", + "eslint-plugin-react": "7.24.0" + }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/parser": "^3.7.0", diff --git a/tests/lib/rules/no-document-domain.js b/tests/lib/rules/no-document-domain.js index c832429..30a997d 100644 --- a/tests/lib/rules/no-document-domain.js +++ b/tests/lib/rules/no-document-domain.js @@ -47,6 +47,7 @@ function main() { var somevalue = 'somevalue'; document.domain = somevalue; window.document.domain = somevalue; +newWindow.document.domain = somevalue; `, errors: [ { @@ -56,6 +57,10 @@ window.document.domain = somevalue; { line: 4, messageId: "default" + }, + { + line: 5, + messageId: "default" } ] } diff --git a/tests/lib/rules/no-document-write.js b/tests/lib/rules/no-document-write.js index 4be2092..47f13c8 100644 --- a/tests/lib/rules/no-document-write.js +++ b/tests/lib/rules/no-document-write.js @@ -74,12 +74,16 @@ ruleTester.run(ruleId, rule, { document.writeln('...'); window.document.write('...'); window.document.writeln('...'); + newWindow.document.write('...'); + newWindow.document.writeln('...'); `, errors: [ { messageId: "default", line: 2 }, { messageId: "default", line: 3 }, { messageId: "default", line: 4 }, - { messageId: "default", line: 5 } + { messageId: "default", line: 5 }, + { messageId: "default", line: 6 }, + { messageId: "default", line: 7 } ] } ]