eslint-plugin-node-security 5.4.0 → 5.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-node-security",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.2",
|
|
4
4
|
"description": "ESLint plugin for Node.js security — detects command injection, path traversal, SSRF, zip slip, and weak crypto (MD5/SHA-1, ECB, static IV) in fs, child_process, vm, and crypto.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"node": ">=18.0.0"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@interlace/eslint-devkit": "^1.19.
|
|
82
|
+
"@interlace/eslint-devkit": "^1.19.2"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
85
|
"eslint": "^8.40.0 || ^9.0.0 || ^10.0.0"
|
|
@@ -87,5 +87,21 @@
|
|
|
87
87
|
"funding": {
|
|
88
88
|
"type": "github",
|
|
89
89
|
"url": "git+https://github.com/ofri-peretz/eslint.git"
|
|
90
|
+
},
|
|
91
|
+
"interlace": {
|
|
92
|
+
"surface": {
|
|
93
|
+
"kind": "node-core",
|
|
94
|
+
"modules": [
|
|
95
|
+
"node:fs",
|
|
96
|
+
"node:fs/promises",
|
|
97
|
+
"node:child_process",
|
|
98
|
+
"node:crypto",
|
|
99
|
+
"node:vm",
|
|
100
|
+
"node:dns",
|
|
101
|
+
"node:http",
|
|
102
|
+
"node:https",
|
|
103
|
+
"node:tls"
|
|
104
|
+
]
|
|
105
|
+
}
|
|
90
106
|
}
|
|
91
107
|
}
|
package/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.rules={get"detect-child-process"(){return require("./rules/detect-child-process").detectChildProcess},get"detect-eval-with-expression"(){return require("./rules/detect-eval-with-expression").detectEvalWithExpression},get"detect-non-literal-fs-filename"(){return require("./rules/detect-non-literal-fs-filename").detectNonLiteralFsFilename},get"no-unsafe-dynamic-require"(){return require("./rules/no-unsafe-dynamic-require").noUnsafeDynamicRequire},get"no-buffer-overread"(){return require("./rules/no-buffer-overread").noBufferOverread},get"no-deprecated-buffer"(){return require("./rules/no-deprecated-buffer").noDeprecatedBuffer},get"no-unsafe-buffer-alloc"(){return require("./rules/no-unsafe-buffer-alloc").noUnsafeBufferAlloc},get"no-toctou-vulnerability"(){return require("./rules/no-toctou-vulnerability").noToctouVulnerability},get"no-zip-slip"(){return require("./rules/no-zip-slip").noZipSlip},get"no-arbitrary-file-access"(){return require("./rules/no-arbitrary-file-access").noArbitraryFileAccess},get"no-data-in-temp-storage"(){return require("./rules/no-data-in-temp-storage").noDataInTempStorage},get"no-ssrf"(){return require("./rules/no-ssrf").noSsrf},get"no-shell-injection"(){return require("./rules/no-shell-injection").noShellInjection},get"no-dynamic-command-string"(){return require("./rules/no-dynamic-command-string").noDynamicCommandString},get"no-env-injection"(){return require("./rules/no-env-injection").noEnvInjection},get"no-dynamic-algorithm-selection"(){return require("./rules/no-dynamic-algorithm-selection").noDynamicAlgorithmSelection},get"detect-suspicious-dependencies"(){return require("./rules/detect-suspicious-dependencies").detectSuspiciousDependencies},get"lock-file"(){return require("./rules/lock-file").lockFile},get"no-dynamic-dependency-loading"(){return require("./rules/no-dynamic-dependency-loading").noDynamicDependencyLoading},get"require-dependency-integrity"(){return require("./rules/require-dependency-integrity").requireDependencyIntegrity},get"require-secure-credential-storage"(){return require("./rules/require-secure-credential-storage").requireSecureCredentialStorage},get"require-secure-deletion"(){return require("./rules/require-secure-deletion").requireSecureDeletion},get"require-storage-encryption"(){return require("./rules/require-storage-encryption").requireStorageEncryption},get"no-dynamic-require"(){return require("./rules/no-dynamic-require").noDynamicRequire},get"no-cryptojs"(){return require("./rules/no-cryptojs").noCryptojs},get"no-cryptojs-weak-random"(){return require("./rules/no-cryptojs-weak-random").noCryptojsWeakRandom},get"no-deprecated-cipher-method"(){return require("./rules/no-deprecated-cipher-method").noDeprecatedCipherMethod},get"no-ecb-mode"(){return require("./rules/no-ecb-mode").noEcbMode},get"no-insecure-key-derivation"(){return require("./rules/no-insecure-key-derivation").noInsecureKeyDerivation},get"no-insecure-rsa-padding"(){return require("./rules/no-insecure-rsa-padding").noInsecureRsaPadding},get"no-math-random-crypto"(){return require("./rules/no-math-random-crypto").noMathRandomCrypto},get"no-self-signed-certs"(){return require("./rules/no-self-signed-certs").noSelfSignedCerts},get"no-sha1-hash"(){return require("./rules/no-sha1-hash").noSha1Hash},get"no-static-iv"(){return require("./rules/no-static-iv").noStaticIv},get"no-timing-unsafe-compare"(){return require("./rules/no-timing-unsafe-compare").noTimingUnsafeCompare},get"no-weak-cipher-algorithm"(){return require("./rules/no-weak-cipher-algorithm").noWeakCipherAlgorithm},get"no-weak-hash-algorithm"(){return require("./rules/no-weak-hash-algorithm").noWeakHashAlgorithm},get"prefer-native-crypto"(){return require("./rules/prefer-native-crypto").preferNativeCrypto},get"require-aead-tag-verification"(){return require("./rules/require-aead-tag-verification").requireAeadTagVerification},get"no-unbounded-decompression"(){return require("./rules/no-unbounded-decompression").noUnboundedDecompression},get"no-insecure-http-parser"(){return require("./rules/no-insecure-http-parser").noInsecureHttpParser},get"require-stream-error-handler"(){return require("./rules/require-stream-error-handler").requireStreamErrorHandler}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-node-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-node-security",version:"5.4.
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.rules={get"detect-child-process"(){return require("./rules/detect-child-process").detectChildProcess},get"detect-eval-with-expression"(){return require("./rules/detect-eval-with-expression").detectEvalWithExpression},get"detect-non-literal-fs-filename"(){return require("./rules/detect-non-literal-fs-filename").detectNonLiteralFsFilename},get"no-unsafe-dynamic-require"(){return require("./rules/no-unsafe-dynamic-require").noUnsafeDynamicRequire},get"no-buffer-overread"(){return require("./rules/no-buffer-overread").noBufferOverread},get"no-deprecated-buffer"(){return require("./rules/no-deprecated-buffer").noDeprecatedBuffer},get"no-unsafe-buffer-alloc"(){return require("./rules/no-unsafe-buffer-alloc").noUnsafeBufferAlloc},get"no-toctou-vulnerability"(){return require("./rules/no-toctou-vulnerability").noToctouVulnerability},get"no-zip-slip"(){return require("./rules/no-zip-slip").noZipSlip},get"no-arbitrary-file-access"(){return require("./rules/no-arbitrary-file-access").noArbitraryFileAccess},get"no-data-in-temp-storage"(){return require("./rules/no-data-in-temp-storage").noDataInTempStorage},get"no-ssrf"(){return require("./rules/no-ssrf").noSsrf},get"no-shell-injection"(){return require("./rules/no-shell-injection").noShellInjection},get"no-dynamic-command-string"(){return require("./rules/no-dynamic-command-string").noDynamicCommandString},get"no-env-injection"(){return require("./rules/no-env-injection").noEnvInjection},get"no-dynamic-algorithm-selection"(){return require("./rules/no-dynamic-algorithm-selection").noDynamicAlgorithmSelection},get"detect-suspicious-dependencies"(){return require("./rules/detect-suspicious-dependencies").detectSuspiciousDependencies},get"lock-file"(){return require("./rules/lock-file").lockFile},get"no-dynamic-dependency-loading"(){return require("./rules/no-dynamic-dependency-loading").noDynamicDependencyLoading},get"require-dependency-integrity"(){return require("./rules/require-dependency-integrity").requireDependencyIntegrity},get"require-secure-credential-storage"(){return require("./rules/require-secure-credential-storage").requireSecureCredentialStorage},get"require-secure-deletion"(){return require("./rules/require-secure-deletion").requireSecureDeletion},get"require-storage-encryption"(){return require("./rules/require-storage-encryption").requireStorageEncryption},get"no-dynamic-require"(){return require("./rules/no-dynamic-require").noDynamicRequire},get"no-cryptojs"(){return require("./rules/no-cryptojs").noCryptojs},get"no-cryptojs-weak-random"(){return require("./rules/no-cryptojs-weak-random").noCryptojsWeakRandom},get"no-deprecated-cipher-method"(){return require("./rules/no-deprecated-cipher-method").noDeprecatedCipherMethod},get"no-ecb-mode"(){return require("./rules/no-ecb-mode").noEcbMode},get"no-insecure-key-derivation"(){return require("./rules/no-insecure-key-derivation").noInsecureKeyDerivation},get"no-insecure-rsa-padding"(){return require("./rules/no-insecure-rsa-padding").noInsecureRsaPadding},get"no-math-random-crypto"(){return require("./rules/no-math-random-crypto").noMathRandomCrypto},get"no-self-signed-certs"(){return require("./rules/no-self-signed-certs").noSelfSignedCerts},get"no-sha1-hash"(){return require("./rules/no-sha1-hash").noSha1Hash},get"no-static-iv"(){return require("./rules/no-static-iv").noStaticIv},get"no-timing-unsafe-compare"(){return require("./rules/no-timing-unsafe-compare").noTimingUnsafeCompare},get"no-weak-cipher-algorithm"(){return require("./rules/no-weak-cipher-algorithm").noWeakCipherAlgorithm},get"no-weak-hash-algorithm"(){return require("./rules/no-weak-hash-algorithm").noWeakHashAlgorithm},get"prefer-native-crypto"(){return require("./rules/prefer-native-crypto").preferNativeCrypto},get"require-aead-tag-verification"(){return require("./rules/require-aead-tag-verification").requireAeadTagVerification},get"no-unbounded-decompression"(){return require("./rules/no-unbounded-decompression").noUnboundedDecompression},get"no-insecure-http-parser"(){return require("./rules/no-insecure-http-parser").noInsecureHttpParser},get"require-stream-error-handler"(){return require("./rules/require-stream-error-handler").requireStreamErrorHandler}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-node-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-node-security",version:"5.4.2"},rules:exports.rules};const recommendedRules={"node-security/detect-child-process":"error","node-security/detect-eval-with-expression":"error","node-security/detect-non-literal-fs-filename":"warn","node-security/no-unsafe-dynamic-require":"error","node-security/no-buffer-overread":"warn","node-security/no-deprecated-buffer":"error","node-security/no-unsafe-buffer-alloc":"warn","node-security/no-toctou-vulnerability":"error","node-security/no-zip-slip":"error","node-security/no-arbitrary-file-access":"error","node-security/no-data-in-temp-storage":"error","node-security/no-ssrf":"warn","node-security/no-shell-injection":"error","node-security/no-dynamic-command-string":"error","node-security/no-env-injection":"error","node-security/no-dynamic-algorithm-selection":"error","node-security/no-timing-unsafe-compare":"warn","node-security/detect-suspicious-dependencies":"warn","node-security/require-dependency-integrity":"error","node-security/no-weak-hash-algorithm":"error","node-security/no-weak-cipher-algorithm":"error","node-security/no-static-iv":"error","node-security/no-ecb-mode":"error","node-security/no-math-random-crypto":"error","node-security/no-cryptojs":"error","node-security/no-self-signed-certs":"error","node-security/require-aead-tag-verification":"error","node-security/no-unbounded-decompression":"error","node-security/no-insecure-http-parser":"error","node-security/require-stream-error-handler":"error"};exports.configs={recommended:{plugins:{"node-security":exports.plugin},rules:recommendedRules},strict:{plugins:{"node-security":exports.plugin},rules:Object.fromEntries(Object.keys(exports.rules).map(ruleName=>[`node-security/${ruleName}`,"error"]))}};exports.default=exports.plugin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noArbitraryFileAccess=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noArbitraryFileAccess=(0,eslint_devkit_1.createRule)({name:"no-arbitrary-file-access",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-arbitrary-file-access.md",description:"Prevent file access from user input",cwe:"CWE-22",cvss:7.5},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Arbitrary File Access",cwe:"CWE-22",description:"File path from user input - path traversal vulnerability",severity:"HIGH",fix:"Validate and sanitize file paths, use allowlists",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"})},schema:[]},defaultOptions:[],create(context){const sourceCode=context.sourceCode;function report(node){context.report({node,messageId:"violationDetected"})}const fsReadMethods=["readFile","readFileSync","readdir","readdirSync","stat","statSync"];const fsWriteMethods=["writeFile","writeFileSync","appendFile","appendFileSync"];const userInputSources=new Set(["req","request","params","query","body"]);const bindings=new Map;function readsUserInput(node,depth=0){if(depth>6)return false;switch(node.type){case"Identifier":{if(userInputSources.has(node.name.toLowerCase()))return true;const bound=bindings.get(node.name);return bound!==void 0&&readsUserInput(bound,depth+1)}case"MemberExpression":return readsUserInput(node.object,depth+1);case"TemplateLiteral":return node.expressions.some(e=>readsUserInput(e,depth+1));case"BinaryExpression":return readsUserInput(node.left,depth+1)||readsUserInput(node.right,depth+1);case"CallExpression":return node.arguments.some(arg=>arg.type!=="SpreadElement"&&readsUserInput(arg,depth+1));default:return false}}const allCalls=[];function functionName(fn){if(fn.type!=="ArrowFunctionExpression"&&fn.id!==null)return fn.id.name;const parent=fn.parent;if(parent.type==="VariableDeclarator"&&parent.id.type==="Identifier"){return parent.id.name}if(parent.type==="Property"&&!parent.computed&&parent.key.type==="Identifier"){return parent.key.name}if(parent.type==="AssignmentExpression"&&parent.left.type==="Identifier"){return parent.left.name}return void 0}function callerPassesUserInput(name,index){return allCalls.some(call=>{const callee=call.callee;const calleeName=callee.type==="Identifier"?callee.name:callee.type==="MemberExpression"?(0,eslint_devkit_1.propertyName)(callee)??void 0:void 0;if(calleeName!==name)return false;const arg=call.arguments[index];return arg!==void 0&&arg.type!=="SpreadElement"&&readsUserInput(arg)})}function variableTracesToUserInput(varName,from){if(userInputSources.has(varName.toLowerCase()))return true;let scope=from;while(scope!=null){if(scope.type==="FunctionDeclaration"||scope.type==="FunctionExpression"||scope.type==="ArrowFunctionExpression"){const index=scope.params.findIndex(param=>param.type==="Identifier"&¶m.name===varName);if(index!==-1){const name=functionName(scope);return name!==void 0&&callerPassesUserInput(name,index)}}const body=scope.type==="Program"||scope.type==="BlockStatement"?scope.body:void 0;if(body!==void 0){for(const stmt of body){if(stmt.type!=="VariableDeclaration")continue;for(const decl of stmt.declarations){if(decl.id.type!=="Identifier"||decl.id.name!==varName)continue;return decl.init!=null&&readsUserInput(decl.init)}}}scope=scope.parent}return false}const sanitizedVariables=new Set;const validatedVariables=new Set;function checkVariableDeclaration(node){if(node.id.type!=="Identifier"||!node.init){return}const varName=node.id.name;const init=node.init;bindings.set(varName,init);if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&init.callee.object.type==="Identifier"&&init.callee.object.name==="path"&&(0,eslint_devkit_1.propertyName)(init.callee)==="basename"){sanitizedVariables.add(varName)}if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&init.callee.object.type==="Identifier"&&init.callee.object.name==="path"&&(0,eslint_devkit_1.propertyName)(init.callee)==="join"){const hasSanitizedArg=init.arguments.some(arg=>arg.type==="Identifier"&&sanitizedVariables.has(arg.name));const firstArg=init.arguments[0];const hasSafeBase=firstArg&&(firstArg.type==="Literal"||firstArg.type==="Identifier"&&/^(SAFE|BASE|ROOT|UPLOAD|PUBLIC)/i.test(firstArg.name));if(hasSanitizedArg&&hasSafeBase){sanitizedVariables.add(varName)}}}function hasStartsWithGuard(node,varName){if(validatedVariables.has(varName)){return true}let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){const statements=current.body;for(const stmt of statements){if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){const testText=sourceCode.getText(stmt.test).toLowerCase();if(testText.includes("startswith")&&testText.includes(varName.toLowerCase())){const consequent=stmt.consequent;if(consequent.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement&&consequent.body.length>0){const firstStmt=consequent.body[0];if(firstStmt.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement||firstStmt.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){validatedVariables.add(varName);return true}}if(consequent.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement||consequent.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){validatedVariables.add(varName);return true}}}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){const testText=sourceCode.getText(current.test).toLowerCase();if(testText.includes("startswith")&&testText.includes(varName.toLowerCase())){validatedVariables.add(varName);return true}}current=current.parent}return false}function isVariableSafe(varName,node){if(sanitizedVariables.has(varName)){return true}if(hasStartsWithGuard(node,varName)){return true}if(/^(safe|sanitized|validated|clean)/i.test(varName)){return true}return false}return{VariableDeclarator(node){checkVariableDeclaration(node)},CallExpression(node){allCalls.push(node)},"Program:exit"(){for(const node of allCalls)checkFsCall(node)}};function checkFsCall(node){if(node.callee.type==="MemberExpression"&&node.callee.object.type==="Identifier"&&node.callee.object.name==="fs"&&[...fsReadMethods,...fsWriteMethods].includes((0,eslint_devkit_1.propertyName)(node.callee)??"")){const pathArg=node.arguments[0];if(pathArg&&pathArg.type==="Literal"){return}if(pathArg&&pathArg.type==="Identifier"){const varName=pathArg.name;if(isVariableSafe(varName,node)){return}if(!variableTracesToUserInput(varName,node)){return}report(node);return}if(pathArg!==void 0&&readsUserInput(pathArg)){report(node)}}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noArbitraryFileAccess=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noArbitraryFileAccess=(0,eslint_devkit_1.createRule)({name:"no-arbitrary-file-access",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-arbitrary-file-access.md",description:"Prevent file access from user input",cwe:"CWE-22",cvss:7.5},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Arbitrary File Access",cwe:"CWE-22",description:"File path from user input - path traversal vulnerability",severity:"HIGH",fix:"Validate and sanitize file paths, use allowlists",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"})},schema:[]},defaultOptions:[],create(context){const sourceCode=context.sourceCode;function report(node){context.report({node,messageId:"violationDetected"})}const fsReadMethods=["readFile","readFileSync","readdir","readdirSync","stat","statSync"];const fsWriteMethods=["writeFile","writeFileSync","appendFile","appendFileSync"];const userInputSources=new Set(["req","request","params","query","body"]);const bindings=new Map;function readsUserInput(node,depth=0){if(depth>6)return false;switch(node.type){case"Identifier":{if(userInputSources.has(node.name.toLowerCase()))return true;const bound=bindings.get(node.name);return bound!==void 0&&readsUserInput(bound,depth+1)}case"MemberExpression":return readsUserInput(node.object,depth+1);case"TemplateLiteral":return node.expressions.some(e=>readsUserInput(e,depth+1));case"BinaryExpression":return readsUserInput(node.left,depth+1)||readsUserInput(node.right,depth+1);case"CallExpression":return node.arguments.some(arg=>arg.type!=="SpreadElement"&&readsUserInput(arg,depth+1));default:return false}}const allCalls=[];function functionName(fn){if(fn.type!=="ArrowFunctionExpression"&&fn.id!==null)return fn.id.name;const parent=fn.parent;if(parent.type==="VariableDeclarator"&&parent.id.type==="Identifier"){return parent.id.name}if(parent.type==="Property"&&!parent.computed&&parent.key.type==="Identifier"){return parent.key.name}if(parent.type==="AssignmentExpression"&&parent.left.type==="Identifier"){return parent.left.name}return void 0}function callerPassesUserInput(name,index){return allCalls.some(call=>{const callee=call.callee;const calleeName=callee.type==="Identifier"?callee.name:callee.type==="MemberExpression"?(0,eslint_devkit_1.propertyName)(callee)??void 0:void 0;if(calleeName!==name)return false;const arg=call.arguments[index];return arg!==void 0&&arg.type!=="SpreadElement"&&readsUserInput(arg)})}function variableTracesToUserInput(varName,from){if(userInputSources.has(varName.toLowerCase()))return true;let scope=from;while(scope!=null){if(scope.type==="FunctionDeclaration"||scope.type==="FunctionExpression"||scope.type==="ArrowFunctionExpression"){const index=scope.params.findIndex(param=>param.type==="Identifier"&¶m.name===varName);if(index!==-1){const name=functionName(scope);return name!==void 0&&callerPassesUserInput(name,index)}}const body=scope.type==="Program"||scope.type==="BlockStatement"?scope.body:void 0;if(body!==void 0){for(const stmt of body){if(stmt.type!=="VariableDeclaration")continue;for(const decl of stmt.declarations){if(decl.id.type!=="Identifier"||decl.id.name!==varName)continue;return decl.init!=null&&readsUserInput(decl.init)}}}scope=scope.parent}return false}const sanitizedVariables=new Set;const validatedVariables=new Set;function checkVariableDeclaration(node){if(node.id.type!=="Identifier"||!node.init){return}const varName=node.id.name;const init=node.init;bindings.set(varName,init);if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&init.callee.object.type==="Identifier"&&init.callee.object.name==="path"&&(0,eslint_devkit_1.propertyName)(init.callee)==="basename"){sanitizedVariables.add(varName)}if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&init.callee.object.type==="Identifier"&&init.callee.object.name==="path"&&(0,eslint_devkit_1.propertyName)(init.callee)==="join"){const hasSanitizedArg=init.arguments.some(arg=>arg.type==="Identifier"&&sanitizedVariables.has(arg.name));const firstArg=init.arguments[0];const hasSafeBase=firstArg&&(firstArg.type==="Literal"||firstArg.type==="Identifier"&&/^(SAFE|BASE|ROOT|UPLOAD|PUBLIC)/i.test(firstArg.name));if(hasSanitizedArg&&hasSafeBase){sanitizedVariables.add(varName)}}}function hasStartsWithGuard(node,varName){if(validatedVariables.has(varName)){return true}let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){const statements=current.body;for(const stmt of statements){if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){const testText=sourceCode.getText(stmt.test).toLowerCase();if(testText.includes("startswith")&&testText.includes(varName.toLowerCase())){const consequent=stmt.consequent;if(consequent.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement&&consequent.body.length>0){const firstStmt=consequent.body[0];if(firstStmt.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement||firstStmt.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){validatedVariables.add(varName);return true}}if(consequent.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement||consequent.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){validatedVariables.add(varName);return true}}}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){const testText=sourceCode.getText(current.test).toLowerCase();if(testText.includes("startswith")&&testText.includes(varName.toLowerCase())){validatedVariables.add(varName);return true}}current=current.parent}return false}function isVariableSafe(varName,node){if(sanitizedVariables.has(varName)){return true}if(hasStartsWithGuard(node,varName)){return true}if(/^(safe|sanitized|validated|clean)/i.test(varName)){const bound=bindings.get(varName);if(bound===void 0)return true;if(bound.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression)return true;return!readsUserInput(bound)}return false}return{VariableDeclarator(node){checkVariableDeclaration(node)},CallExpression(node){allCalls.push(node)},"Program:exit"(){for(const node of allCalls)checkFsCall(node)}};function checkFsCall(node){if(node.callee.type==="MemberExpression"&&node.callee.object.type==="Identifier"&&node.callee.object.name==="fs"&&[...fsReadMethods,...fsWriteMethods].includes((0,eslint_devkit_1.propertyName)(node.callee)??"")){const pathArg=node.arguments[0];if(pathArg&&pathArg.type==="Literal"){return}if(pathArg&&pathArg.type==="Identifier"){const varName=pathArg.name;if(isVariableSafe(varName,node)){return}if(!variableTracesToUserInput(varName,node)){return}report(node);return}if(pathArg!==void 0&&readsUserInput(pathArg)){report(node)}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakHashAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const names_1=require("../../utils/names");const const_value_1=require("../../utils/const-value");const WEAK_HASH_PATTERNS=[{pattern:/\bmd5\b/i,name:"MD5",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bmd4\b/i,name:"MD4",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bsha1\b/i,name:"SHA-1",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bripemd\b/i,name:"RIPEMD",alternatives:["SHA-256","SHA-512"],replacement:"sha256"}];const DEFAULT_NON_CRYPTOGRAPHIC_NAMES=["sha","etag","cachekey","cachebuster","thumbprint","x5t"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate","otp","mfa","totp","passphrase","pincode","mnemonic","seedphrase","masterkey","securityanswer","recoverycode","backupcode"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!target.computed&&target.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return target.property.name}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return node.property.name}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&¤t.id){return current.id.name}const owner=current.parent;if(owner?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return owner.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.id.name:null}if((owner?.type===eslint_devkit_1.AST_NODE_TYPES.Property||owner?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition)&&!owner.computed){return expressionName(owner.key)}return null}current=current.parent}return null}function findWeakHash(value,additionalPatterns){for(const pattern of WEAK_HASH_PATTERNS){if(pattern.pattern.test(value)){return pattern}}for(const additionalPattern of additionalPatterns){const regex=new RegExp(`\\b${additionalPattern}\\b`,"i");if(regex.test(value)){return{pattern:regex,name:additionalPattern.toUpperCase(),alternatives:["SHA-256","SHA-512"],replacement:"sha256"}}}return null}exports.noWeakHashAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-hash-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-hash-algorithm.md",description:"Disallow weak hash algorithms (MD5, SHA1, MD4)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakHashAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak hash algorithm",cwe:"CWE-327",description:"Use of weak hash algorithm: {{algorithm}}. {{algorithm}} is cryptographically broken and unsuitable for security purposes.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createHash("{{replacement}}").update(data). If this hash is an identifier rather than a security control \u2014 an EVALSHA key, an ETag, a cache key \u2014 store it under one of the nonCryptographicNames instead.',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-256",description:"Replace with SHA-256 for secure hashing",severity:"LOW",fix:'crypto.createHash("sha256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-512",description:"Replace with SHA-512 for stronger hashing",severity:"LOW",fix:'crypto.createHash("sha512").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha3:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-3",description:"Replace with SHA-3 for latest standard",severity:"LOW",fix:'crypto.createHash("sha3-256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"})},schema:[{type:"object",properties:{additionalWeakAlgorithms:{type:"array",items:{type:"string"},default:[],description:"Additional weak algorithms to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak hashes in test files"},nonCryptographicNames:{type:"array",items:{type:"string"},default:DEFAULT_NON_CRYPTOGRAPHIC_NAMES,description:"Assignment target names that mark a hash as an identifier rather than a security control"},securityUseNames:{type:"array",items:{type:"string"},default:DEFAULT_SECURITY_USE_NAMES,description:"Names that mark a hash as a security control (whole-word matched)"},reportUnclassifiedHashes:{type:"boolean",default:false,description:"Report weak hashes whose purpose cannot be determined. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{additionalWeakAlgorithms:[],allowInTests:false,nonCryptographicNames:DEFAULT_NON_CRYPTOGRAPHIC_NAMES}],create(context,[options={}]){const{additionalWeakAlgorithms=[],allowInTests=false,nonCryptographicNames=DEFAULT_NON_CRYPTOGRAPHIC_NAMES,securityUseNames=DEFAULT_SECURITY_USE_NAMES,reportUnclassifiedHashes=false}=options;const isSecurityUse=(0,names_1.makeNameTest)(securityUseNames);const isNonCryptographicName=(0,names_1.makeNameTest)(nonCryptographicNames.map(normalizeName));const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function isNonCryptographicUse(node){const stored=assignedName(node);if(stored!==null&&isNonCryptographicName(stored))return true;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isNonCryptographicName(enclosing)}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}if(stored!==null)return false;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function isLocalHmacHelper(callee){const scope=context.sourceCode.getScope(callee);for(let current=scope;current;current=current.upper){const variable=current.variables.find(v=>v.name===callee.name);if(!variable)continue;return variable.defs.some(def=>def.type!=="ImportBinding"&&computesHmac(def.node))}return false}function computesHmac(root){const stack=[root];while(stack.length>0){const node=stack.pop();if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&isCreateHmacCall(node)){return true}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){stack.push(child)}}}}return false}function isCreateHmacCall(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.name==="createHmac"}return callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(callee)==="createHmac"}function checkCallExpression(node){if(isTestFile)return;if(isNonCryptographicUse(node))return;if(!reportUnclassifiedHashes&&!hasSecurityUse(node))return;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(node.callee)==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const funcName=node.callee.name.toLowerCase();if((funcName==="sha1"||funcName==="md5"||funcName==="md4")&&!isLocalHmacHelper(node.callee)){const weakPattern=findWeakHash(funcName,additionalWeakAlgorithms);context.report({node,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return fixer.replaceText(node.callee,"sha256")}return null}}]})}}}function checkHashArgument(node){for(const arg of node.arguments){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,arg);if(resolved===null)continue;const weakPattern=findWeakHash(resolved.value,additionalWeakAlgorithms);if(!weakPattern)continue;const target=resolved.source;context.report({node:arg,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>fixer.replaceText(target,`"sha256"`)},{messageId:"useSha512",fix:fixer=>fixer.replaceText(target,`"sha512"`)},{messageId:"useSha3",fix:fixer=>fixer.replaceText(target,`"sha3-256"`)}]})}}return{CallExpression:checkCallExpression}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakHashAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const names_1=require("../../utils/names");const const_value_1=require("../../utils/const-value");const WEAK_HASH_PATTERNS=[{pattern:/\bmd5\b/i,name:"MD5",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bmd4\b/i,name:"MD4",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bsha1\b/i,name:"SHA-1",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bripemd\b/i,name:"RIPEMD",alternatives:["SHA-256","SHA-512"],replacement:"sha256"}];const DEFAULT_NON_CRYPTOGRAPHIC_NAMES=["sha","etag","cachekey","cachebuster","thumbprint","x5t"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate","otp","mfa","totp","passphrase","pincode","mnemonic","seedphrase","masterkey","securityanswer","recoverycode","backupcode"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return(0,eslint_devkit_1.propertyName)(target)}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return(0,eslint_devkit_1.propertyName)(node)}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&¤t.id){return current.id.name}const owner=current.parent;if(owner?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return owner.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.id.name:null}if((owner?.type===eslint_devkit_1.AST_NODE_TYPES.Property||owner?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition)&&!owner.computed){return expressionName(owner.key)}return null}current=current.parent}return null}function findWeakHash(value,additionalPatterns){for(const pattern of WEAK_HASH_PATTERNS){if(pattern.pattern.test(value)){return pattern}}for(const additionalPattern of additionalPatterns){const regex=new RegExp(`\\b${additionalPattern}\\b`,"i");if(regex.test(value)){return{pattern:regex,name:additionalPattern.toUpperCase(),alternatives:["SHA-256","SHA-512"],replacement:"sha256"}}}return null}exports.noWeakHashAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-hash-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-hash-algorithm.md",description:"Disallow weak hash algorithms (MD5, SHA1, MD4)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakHashAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak hash algorithm",cwe:"CWE-327",description:"Use of weak hash algorithm: {{algorithm}}. {{algorithm}} is cryptographically broken and unsuitable for security purposes.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createHash("{{replacement}}").update(data). If this hash is an identifier rather than a security control \u2014 an EVALSHA key, an ETag, a cache key \u2014 store it under one of the nonCryptographicNames instead.',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-256",description:"Replace with SHA-256 for secure hashing",severity:"LOW",fix:'crypto.createHash("sha256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-512",description:"Replace with SHA-512 for stronger hashing",severity:"LOW",fix:'crypto.createHash("sha512").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha3:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-3",description:"Replace with SHA-3 for latest standard",severity:"LOW",fix:'crypto.createHash("sha3-256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"})},schema:[{type:"object",properties:{additionalWeakAlgorithms:{type:"array",items:{type:"string"},default:[],description:"Additional weak algorithms to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak hashes in test files"},nonCryptographicNames:{type:"array",items:{type:"string"},default:DEFAULT_NON_CRYPTOGRAPHIC_NAMES,description:"Assignment target names that mark a hash as an identifier rather than a security control"},securityUseNames:{type:"array",items:{type:"string"},default:DEFAULT_SECURITY_USE_NAMES,description:"Names that mark a hash as a security control (whole-word matched)"},reportUnclassifiedHashes:{type:"boolean",default:false,description:"Report weak hashes whose purpose cannot be determined. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{additionalWeakAlgorithms:[],allowInTests:false,nonCryptographicNames:DEFAULT_NON_CRYPTOGRAPHIC_NAMES}],create(context,[options={}]){const{additionalWeakAlgorithms=[],allowInTests=false,nonCryptographicNames=DEFAULT_NON_CRYPTOGRAPHIC_NAMES,securityUseNames=DEFAULT_SECURITY_USE_NAMES,reportUnclassifiedHashes=false}=options;const isSecurityUse=(0,names_1.makeNameTest)(securityUseNames);const isNonCryptographicName=(0,names_1.makeNameTest)(nonCryptographicNames.map(normalizeName));const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function isNonCryptographicUse(node){const stored=assignedName(node);if(stored!==null&&isNonCryptographicName(stored))return true;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isNonCryptographicName(enclosing)}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}if(stored!==null)return false;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function isLocalHmacHelper(callee){const scope=context.sourceCode.getScope(callee);for(let current=scope;current;current=current.upper){const variable=current.variables.find(v=>v.name===callee.name);if(!variable)continue;return variable.defs.some(def=>def.type!=="ImportBinding"&&computesHmac(def.node))}return false}function computesHmac(root){const stack=[root];while(stack.length>0){const node=stack.pop();if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&isCreateHmacCall(node)){return true}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){stack.push(child)}}}}return false}function isCreateHmacCall(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.name==="createHmac"}return callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(callee)==="createHmac"}function checkCallExpression(node){if(isTestFile)return;if(isNonCryptographicUse(node))return;if(!reportUnclassifiedHashes&&!hasSecurityUse(node))return;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(node.callee)==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const funcName=node.callee.name.toLowerCase();if((funcName==="sha1"||funcName==="md5"||funcName==="md4")&&!isLocalHmacHelper(node.callee)){const weakPattern=findWeakHash(funcName,additionalWeakAlgorithms);context.report({node,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return fixer.replaceText(node.callee,"sha256")}return null}}]})}}}function checkHashArgument(node){for(const arg of node.arguments){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,arg);if(resolved===null)continue;const weakPattern=findWeakHash(resolved.value,additionalWeakAlgorithms);if(!weakPattern)continue;const target=resolved.source;context.report({node:arg,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>fixer.replaceText(target,`"sha256"`)},{messageId:"useSha512",fix:fixer=>fixer.replaceText(target,`"sha512"`)},{messageId:"useSha3",fix:fixer=>fixer.replaceText(target,`"sha3-256"`)}]})}}return{CallExpression:checkCallExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireDependencyIntegrity=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const RESOURCE_TAG=/<(script|link)\b(?:"[^"]*"|'[^']*'|[^>"'])*>/gi;const TAG_ATTRIBUTE=/(?:^|[\s/])([a-z][a-z0-9-]*)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;const CDN_HOSTS=["cdn.","cdnjs.","unpkg.","jsdelivr."];const SRI_LINK_RELS=new Set(["stylesheet","modulepreload"]);const SRI_PRELOAD_DESTINATIONS=new Set(["style","script"]);function attributesOf(tag){const attributes=new Map;for(const match of tag.matchAll(TAG_ATTRIBUTE)){const raw=match[2];const quoted=raw.startsWith('"')||raw.startsWith("'");attributes.set(match[1].toLowerCase(),quoted?raw.slice(1,-1):raw)}return attributes}function hostOf(url){const match=/^\s*(?:[a-z][a-z0-9+.-]*:)?\/\/([^/?#]*)/i.exec(url);return match?.[1].toLowerCase()}function linkTakesIntegrity(attributes){const rel=(attributes.get("rel")??"").toLowerCase().trim();if(rel==="")return true;const relations=rel.split(/\s+/);if(relations.some(value=>SRI_LINK_RELS.has(value)))return true;if(!relations.includes("preload"))return false;return SRI_PRELOAD_DESTINATIONS.has((attributes.get("as")??"").toLowerCase())}function hasUnprotectedCdnTag(text){for(const match of text.matchAll(RESOURCE_TAG)){const isScript=match[1].toLowerCase()==="script";const attributes=attributesOf(match[0]);const url=attributes.get(isScript?"src":"href");if(url===void 0)continue;const host=hostOf(url);if(host===void 0)continue;if(!CDN_HOSTS.some(fragment=>host.includes(fragment)))continue;if(!isScript&&!linkTakesIntegrity(attributes))continue;if(attributes.has("integrity"))continue;return true}return false}exports.requireDependencyIntegrity=(0,eslint_devkit_1.createRule)({name:"require-dependency-integrity",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-dependency-integrity.md",description:"Require SRI (Subresource Integrity) for CDN resources",cwe:"CWE-494",cvss:8.1},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing SRI",cwe:"CWE-494",description:"External resource loaded without integrity hash - supply chain risk",severity:"HIGH",fix:'Add integrity="sha384-..." and crossorigin="anonymous" attributes',documentationLink:"https://cwe.mitre.org/data/definitions/494.html"})},schema:[]},defaultOptions:[],create(context){function report(node){context.report({node,messageId:"violationDetected"})}const renderTemplate=node=>{let text=node.quasis[0].value.cooked;for(const[index,expression]of node.expressions.entries()){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,expression);text+=resolved===null?"\0":resolved.value;text+=node.quasis[index+1].value.cooked}return text};return{Literal(node){if(typeof node.value!=="string")return;if(hasUnprotectedCdnTag(node.value))report(node)},TemplateLiteral(node){if(hasUnprotectedCdnTag(renderTemplate(node)))report(node)}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireDependencyIntegrity=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const RESOURCE_TAG=/<(script|link)\b(?:"[^"]*"|'[^']*'|[^>"'])*>/gi;const TAG_ATTRIBUTE=/(?:^|[\s/])([a-z][a-z0-9-]*)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;const CDN_HOSTS=["cdn.","cdnjs.","unpkg.","jsdelivr."];const SRI_LINK_RELS=new Set(["stylesheet","modulepreload"]);const SRI_PRELOAD_DESTINATIONS=new Set(["style","script"]);function attributesOf(tag){const attributes=new Map;for(const match of tag.matchAll(TAG_ATTRIBUTE)){const raw=match[2];const quoted=raw.startsWith('"')||raw.startsWith("'");attributes.set(match[1].toLowerCase(),quoted?raw.slice(1,-1):raw)}return attributes}function hostOf(url){const match=/^\s*(?:[a-z][a-z0-9+.-]*:)?\/\/([^/?#]*)/i.exec(url);return match?.[1].toLowerCase()}function linkTakesIntegrity(attributes){const rel=(attributes.get("rel")??"").toLowerCase().trim();if(rel==="")return true;const relations=rel.split(/\s+/);if(relations.some(value=>SRI_LINK_RELS.has(value)))return true;if(!relations.includes("preload"))return false;return SRI_PRELOAD_DESTINATIONS.has((attributes.get("as")??"").toLowerCase())}function hasUnprotectedCdnTag(text){for(const match of text.matchAll(RESOURCE_TAG)){const isScript=match[1].toLowerCase()==="script";const attributes=attributesOf(match[0]);const url=attributes.get(isScript?"src":"href");if(url===void 0)continue;const host=hostOf(url);if(host===void 0)continue;if(!CDN_HOSTS.some(fragment=>host.includes(fragment)))continue;if(!isScript&&!linkTakesIntegrity(attributes))continue;if(attributes.has("integrity"))continue;return true}return false}exports.requireDependencyIntegrity=(0,eslint_devkit_1.createRule)({name:"require-dependency-integrity",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-dependency-integrity.md",description:"Require SRI (Subresource Integrity) for CDN resources",cwe:"CWE-494",cvss:8.1},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing SRI",cwe:"CWE-494",description:"External resource loaded without integrity hash - supply chain risk",severity:"HIGH",fix:'Add integrity="sha384-..." and crossorigin="anonymous" attributes',documentationLink:"https://cwe.mitre.org/data/definitions/494.html"})},schema:[]},defaultOptions:[],create(context){function report(node){context.report({node,messageId:"violationDetected"})}const renderTemplate=node=>{let text=node.quasis[0].value.cooked??node.quasis[0].value.raw;for(const[index,expression]of node.expressions.entries()){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,expression);text+=resolved===null?"\0":resolved.value;text+=node.quasis[index+1].value.cooked??node.quasis[index+1].value.raw}return text};return{Literal(node){if(typeof node.value!=="string")return;if(hasUnprotectedCdnTag(node.value))report(node)},TemplateLiteral(node){if(hasUnprotectedCdnTag(renderTemplate(node)))report(node)}}}});
|