diff --git a/.github/workflows/E2E integration.yml b/.github/workflows/E2E integration.yml index 79cda1b..87a5726 100644 --- a/.github/workflows/E2E integration.yml +++ b/.github/workflows/E2E integration.yml @@ -81,7 +81,7 @@ jobs: continue-on-error: true - name: Upload eslint results as artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: eslint-result path: ${{env.PROJECT}}/eslint-result-${{ matrix.os }}-${{github.run_id}}.sarif diff --git a/.github/workflows/node-version-integration.yml b/.github/workflows/node-version-integration.yml index e66afb6..7809490 100644 --- a/.github/workflows/node-version-integration.yml +++ b/.github/workflows/node-version-integration.yml @@ -16,8 +16,8 @@ jobs: strategy: matrix: - os: [ubuntu-latest, windows-latest] - node-version: [12.x, 14.x, 16.x] + os: [windows-latest] + node-version: [20.x] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 466cbd7..08ed18e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14 + node-version: 20 - run: npm i - run: npm test @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2.1.4 with: - node-version: 14 + node-version: 20 registry-url: https://registry.npmjs.org/ - run: npm i - run: npm publish diff --git a/lib/ast-utils.js b/lib/ast-utils.js index b0eaf22..4e944b9 100644 --- a/lib/ast-utils.js +++ b/lib/ast-utils.js @@ -20,17 +20,17 @@ module.exports = { hasFullTypeInformation(context) { var hasFullTypeInformation = ( context && - this.isTypeScriptParserServices(context.parserServices) && - context.parserServices.hasFullTypeInformation === true + this.isTypeScriptParserServices(context.sourceCode.parserServices) && + context.sourceCode.parserServices.program ); return hasFullTypeInformation; }, getFullTypeChecker(context) { - return this.hasFullTypeInformation(context) ? context.parserServices.program.getTypeChecker() : null; + return this.hasFullTypeInformation(context) ? context.sourceCode.parserServices.program.getTypeChecker() : null; }, getNodeTypeAsString(fullTypeChecker, node, context) { if (fullTypeChecker && node) { - const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = context.sourceCode.parserServices.esTreeNodeToTSNodeMap.get(node); const tsType = fullTypeChecker.getTypeAtLocation(tsNode); const type = fullTypeChecker.typeToString(tsType); return type; diff --git a/lib/rules/no-cookies.js b/lib/rules/no-cookies.js index 19fc215..e48cf34 100644 --- a/lib/rules/no-cookies.js +++ b/lib/rules/no-cookies.js @@ -9,6 +9,7 @@ "use strict"; const astUtils = require("../ast-utils"); +const ESLintUtils = require("@typescript-eslint/utils").ESLintUtils; //------------------------------------------------------------------------------ // Rule Definition @@ -29,8 +30,10 @@ module.exports = { }, create: function (context) { const fullTypeChecker = astUtils.getFullTypeChecker(context); + fullTypeChecker && console.log("config1: " + JSON.stringify(ESLintUtils.getParserServices(context).program.getCompilerOptions())); return { "MemberExpression[property.name='cookie']"(node) { + fullTypeChecker && console.log("config2: " + JSON.stringify(ESLintUtils.getParserServices(context).program.getCompilerOptions())); if (astUtils.isDocumentObject(node.object, context, fullTypeChecker)) { context.report({ node: node, diff --git a/lib/rules/no-postmessage-star-origin.js b/lib/rules/no-postmessage-star-origin.js index 294343c..d8c5682 100644 --- a/lib/rules/no-postmessage-star-origin.js +++ b/lib/rules/no-postmessage-star-origin.js @@ -37,7 +37,7 @@ module.exports = { // Check that object type is Window when full type information is available if (fullTypeChecker) { - const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node.callee.object); + const tsNode = context.sourceCode.parserServices.esTreeNodeToTSNodeMap.get(node.callee.object); const tsType = fullTypeChecker.getTypeAtLocation(tsNode); const type = fullTypeChecker.typeToString(tsType); if (type !== "any" && type !== "Window") { diff --git a/out.log b/out.log new file mode 100644 index 0000000..25e9310 --- /dev/null +++ b/out.log @@ -0,0 +1,568 @@ + +> @microsoft/eslint-plugin-sdl@0.2.2 test +> mocha tests --recursive + + + + no-angular-bypass-sanitizer + valid + √ bypassSecurityTrustHtml('XSS') (59ms) + √ x.bypassSecurityTrustHtml() + √ x.BypassSecurityTrustHtml('XSS') + invalid + √ $('p').bypassSecurityTrustHtml('XSS'); + √ $('p').bypassSecurityTrustResourceUrl('XSS') + √ $('p').bypassSecurityTrustScript('XSS') + √ $('p').bypassSecurityTrustStyle('XSS') + √ $('p').bypassSecurityTrustUrl('XSS') + + no-angular-sanitization-trusted-urls + valid + √ aHrefSanitizationTrustedUrlList ('.*') + √ x.aHrefSanitizationTrustedUrlList ('.*') + √ $compileProvider.aHrefSanitizationTrustedUrlList () + √ $compileProvider.AHrefSanitizationTrustedUrlList ('.*') + invalid + √ $compileProvider.aHrefSanitizationTrustedUrlList ('.*'); + √ $compileProvider.imgSrcSanitizationTrustedUrlList('.*'); + + no-angularjs-bypass-sce + valid + √ trustAsHtml() + √ $sce.trustAsHtml() + √ $sce.trustAsHtml('') + √ $sce.TrustAsHtml('XSS') + √ x.trustAsHtml('XSS') + √ $sceProvider.enabled() + √ $sceProvider.enabled(true) + √ $sceProvider.enabled(1) + invalid + √ $sceDelegate.trustAs($sce.HTML, 'XSS') + √ $sce.trustAs($sce.HTML, 'XSS') + √ $sce.trustAsCss('XSS') + √ $sce.trustAsHtml('XSS') + √ $sce.trustAsJs('XSS') + √ $sce.trustAsResourceUrl('XSS') + √ $sce.trustAsUrl('XSS') + √ $sceProvider.enabled(false) + √ $sceProvider.enabled(0) + √ $sceProvider.enabled(true != true) + + no-angularjs-enable-svg + valid + √ enableSvg() + √ enableSvg(true) + √ $sanitizeProvider.enableSvg() + √ $sanitizeProvider.enableSvg(false) + √ $sanitizeProvider.enableSvg(0) + √ $sanitizeProvider.EnableSvg(0) + invalid + √ $sanitizeProvider.enableSvg(true) + √ $sanitizeProvider.enableSvg(1) + + no-angularjs-sanitization-whitelist + valid + √ aHrefSanitizationWhitelist('.*') + √ x.aHrefSanitizationWhitelist('.*') + √ $compileProvider.aHrefSanitizationWhitelist() + √ $compileProvider.AHrefSanitizationWhitelist('.*') + invalid + √ $compileProvider.aHrefSanitizationWhitelist('.*'); + √ $compileProvider.imgSrcSanitizationWhitelist('.*'); + + no-cookies + valid + √ +function documentLikeAPIFunction(){ + return { + cookie:'fake.cookie' + } +} +var document2 = documentLikeAPIFunction(); +document2.cookie = '...'; +document2.cookie = '...'; +documentLikeAPIFunction().cookie = '...' + +config: {"jsx":1,"noEmit":true,"noUnusedLocals":true,"noUnusedParameters":true,"allowNonTsExtensions":true,"allowJs":true,"checkJs":true,"configFilePath":"C:/Enlistments/eslint-plugin-sdl/tests/fixtures/tsconfig.json"} +config: {"jsx":1,"noEmit":true,"noUnusedLocals":true,"noUnusedParameters":true,"allowNonTsExtensions":true,"allowJs":true,"checkJs":true,"configFilePath":"C:/Enlistments/eslint-plugin-sdl/tests/fixtures/tsconfig.json"} +config: {"jsx":1,"noEmit":true,"noUnusedLocals":true,"noUnusedParameters":true,"allowNonTsExtensions":true,"allowJs":true,"checkJs":true,"configFilePath":"C:/Enlistments/eslint-plugin-sdl/tests/fixtures/tsconfig.json"} +config: {"jsx":1,"noEmit":true,"noUnusedLocals":true,"noUnusedParameters":true,"allowNonTsExtensions":true,"allowJs":true,"checkJs":true,"configFilePath":"C:/Enlistments/eslint-plugin-sdl/tests/fixtures/tsconfig.json"} + √ +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 = '...'; + (567ms) + invalid + √ document.cookie = '...' + √ window.document.cookie = '...' + √ this.window.document.cookie = '...' + √ globalThis.window.document.cookie = '...' +config: {"jsx":1,"noEmit":true,"noUnusedLocals":true,"noUnusedParameters":true,"allowNonTsExtensions":true,"allowJs":true,"checkJs":true,"configFilePath":"C:/Enlistments/eslint-plugin-sdl/tests/fixtures/tsconfig.json"} +config: {"jsx":1,"noEmit":true,"noUnusedLocals":true,"noUnusedParameters":true,"allowNonTsExtensions":true,"allowJs":true,"checkJs":true,"configFilePath":"C:/Enlistments/eslint-plugin-sdl/tests/fixtures/tsconfig.json"} + √ +function documentFunction(): Document { + return window.document; +} +documentFunction().cookie = '...'; + +config: {"jsx":1,"noEmit":true,"noUnusedLocals":true,"noUnusedParameters":true,"allowNonTsExtensions":true,"allowJs":true,"checkJs":true,"configFilePath":"C:/Enlistments/eslint-plugin-sdl/tests/fixtures/tsconfig.json"} +config: {"jsx":1,"noEmit":true,"noUnusedLocals":true,"noUnusedParameters":true,"allowNonTsExtensions":true,"allowJs":true,"checkJs":true,"configFilePath":"C:/Enlistments/eslint-plugin-sdl/tests/fixtures/tsconfig.json"} + √ +namespace Sample { + function method() { + return document.cookie; + } +} + + + no-document-domain + valid + √ +interface DocumentLikeAPI { + domain: string; +} +function documentLikeAPIFunction(): DocumentLikeAPI { + return null; +} +function main() { + var document: DocumentLikeAPI = documentLikeAPIFunction(); + document.domain = 'somevalue'; +} + + invalid + √ var doc = window.document; doc.domain = 'somevalue'; + √ document.domain = 'somevalue' + √ window.document.domain = 'somevalue' + √ +var somevalue = 'somevalue'; +document.domain = somevalue; +window.document.domain = somevalue; +newWindow.document.domain = somevalue; + + + no-document-write + valid + √ + interface DocumentLikeAPI { + write: ((arg : string) => void); + writeln: ((arg : string) => void); + } + function documentLikeAPIFunction() : DocumentLikeAPI { + return { + write: () => {}, + writeln: () => {}, + }; + } + + √ + 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 + √ + var doc = document; + doc.write('...'); + doc.writeln('...'); + function documentFunction() : Document { + return window.document; + } + documentFunction().write('...'); + documentFunction().writeln('...'); + + √ + document.write('...'); + document.writeln('...'); + window.document.write('...'); + window.document.writeln('...'); + newWindow.document.write('...'); + newWindow.document.writeln('...'); + + + no-electron-node-integration + valid + √ + var mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: false, + nodeIntegrationInWorker: false, + nodeIntegrationInSubFrames: false + } + }); + var view = new BrowserView({ + webPreferences: { + nodeIntegration: false + } + }); + + invalid + √ + var mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: true, + nodeIntegrationInWorker: true, + nodeIntegrationInSubFrames: true + } + }); + + √ + var view = new BrowserView({ + webPreferences: { + nodeIntegration: true, + nodeIntegrationInWorker: true, + nodeIntegrationInSubFrames: true + } + }); + + + no-html-method + valid + √ test.html = 'test' + √ test.html() + √ test.html('','') + √ element.html(''); + √ element.html(null); + invalid + √ $('p').html('XSS') + √ $(selector).html(sample_function()) + √ + import $ from "jquery"; + test.html('XSS'); + + + no-inner-html + valid + √ var test = element.innerHTML + √ var test = element.outerHTML + √ document.body.innerHTML = '' + √ document.test + √ element.insertAdjacentHTML() + √ + 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 + √ + var element = document.getElementById(id); + element.innerHTML = 'test'; + element.outerHTML = 'test'; + element.insertAdjacentHTML('beforebegin', 'foo'); + + √ + element.innerHTML = 'test'; + parent.child.innerHTML += 'test'; + + √ + element.outerHTML = 'test'; + parent.child.outerHTML += 'test'; + + √ element.insertAdjacentHTML('beforebegin', 'foo') + + no-insecure-random + valid + √ Math.Random; + √ Math.random; + √ math.random(); + √ random(); + √ + Math.Random; + Math.random; + math.random(); + random(); + + √ + require('./node_modules/not-unsafe-random'); + require('eslint'); + require('test'); + require('random-package'); + require('random-float2'); + require('random2-seed'); + + √ + import './node_modules/untest'; + import 'random'; + import 'random-3'; + import 'eslint'; + import 'eslint-plugin-sdl'; + import 'testing'; + + √ + cryptos.pseudoRandomBytes(); + pseudoRandomBytes(); + pseudoRandomByte(); + cryptos.pseudoRondomBytes(); + + √ + function random(){} + + random(); + + Math.Random; + Math.random; + + √ + function pseudoRandomBytes(){} + function pseudoRandomByte(){} + + pseudoRandomBytes(); + pseudoRandomByte(); + cryptos.pseudoRondomBytes(); + cryptos.pseudoRondomBytes(); + + invalid + √ + Math.random(); + crypto.pseudoRandomBytes(); + + √ + Math.random(); + this.Math.random(); + + √ + function notMath() : Math{ + return Math; + } + + notMath().random(); + + √ + crypto.pseudoRandomBytes(); + + √ + function notCrypto() : Crypto{ + return crypto; + } + + notCrypto().pseudoRandomBytes(); + + √ + import './node_modules/unique-random'; + import 'chance'; + import 'random-number'; + import 'random-int'; + import 'random-float'; + import 'random-seed'; + + √ + import * as chance1 from 'chance'; + import defaultExport from 'chance'; + import { chance } from 'chance'; + import { chance as chance2 } from 'chance'; + import { chance3, chance4 } from 'chance'; + + √ + require('./node_modules/unique-random'); + require('**/chance.js'); + require('random-number'); + require('random-int'); + require('random-float'); + require('random-seed'); + + + no-insecure-url + valid + √ + var x = 'https://www.example.com' + var y = 'ftps://www.example.com' + + √ + var x = `https://www.template-examples.com` + var y = `ftps://www.template-file-examples.com` + + √ + var x = `https://www.${multipartExample}.com` + var y = `ftps://www.${multipartExample}.com` + + √ var x = 'The protocol may be http://, https://, ftp:// or ftps://' + √ + function f(x : string = 'https://www.example.com') {} + function f(y : string = 'ftps://www.example.com') {} + + √ + 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' + + √ + var insecureURL = 'http://www.allow-example.com' + var InSeCuReURL = 'ftp://www.allow-example.com/path' + + √ + const someSvg: React.FC = () => { + return ( + + + ); + }; + (413ms) + √ + var x = "http://localhost/test"; + var y = "http://localhost"; + + √ + var x = "http://www.w3.org/1999/xhtml"; + var y = "http://www.w3.org/2000/svg"; + + invalid + √ + 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' + + √ + 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` + + √ + var x1 = `http://www.${multipartExample}.com`; + var y1 = `ftp://www.${multipartExample}.com`; + + √ + function f(x : string = 'http://www.example.com') {} + function f(y : string = 'ftp://www.example.com') {} + + √ + 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' + + √ + const someSvg: React.FC = () => { + return ( + + + ); + }; + + √ var a1 = "http://moz\u0009i\u0009lla.org"; + √ var x1 = `http://foo${multipartExample} http://${multipartExample}.com`; + √ var a1 = `http://moz\u0009i\u0009lla.org`; + + no-msapp-exec-unsafe + valid + √ test.execUnsafeLocalFunction = 'test' + √ MSApp.execUnsafeLocalFunction() + invalid + √ MSApp.execUnsafeLocalFunction(testfunc) + + no-postmessage-star-origin + 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') + √ +class WindowLike { + postMessage(): void { + }; +} +function main() { + var w: WindowLike = new WindowLike(); + w.postMessage('test', '*'); +} + + invalid + √ + any.postMessage(message, "*"); + any.postMessage(message, "*", "menubar=yes"); + + √ + window.frames[0].postMessage(message, "*"); + var w1 = window.open(url); + w1.postMessage(message, "*"); + + + no-unsafe-alloc + valid + √ foo.allocUnsafe + √ Buffer.allocUnsafe(0) + √ Buffer.allocUnsafeSlow(0) + invalid + √ + var buf1 = Buffer.allocUnsafe(10); + var buf2 = Buffer.allocUnsafeSlow(10) + + + no-winjs-html-unsafe + valid + √ element.insertAdjacentHTMLUnsafe = "test"; + invalid + √ + WinJS.Utilities.insertAdjacentHTMLUnsafe(element, position, text); + WinJS.Utilities.setInnerHTMLUnsafe(element, text); + WinJS.Utilities.setOuterHTMLUnsafe(element, text); + + + react-iframe-missing-sandbox + valid + √
; + √ + √ + √ + √ + √ + √ + √ + √ + √ + √ + √ + √ + √ + √ + √ + invalid + √ ; + √ + √ ; + √