eslint-plugin-node-security 5.1.0 → 5.1.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.1.
|
|
3
|
+
"version": "5.1.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.
|
|
82
|
+
"@interlace/eslint-devkit": "^1.17.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
85
|
"eslint": "^8.40.0 || ^9.0.0 || ^10.0.0"
|
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.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.1.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.detectEvalWithExpression=exports.generateRefactoringSteps=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const EVAL_PATTERNS=[{pattern:"JSON\\.parse|parse\\(.*\\)",category:"json",safeAlternative:"JSON.parse()",example:{bad:`eval('{"key": "' + value + '"}"')`,good:`JSON.parse('{"key": "' + value + '"}"')`},effort:"2 minutes"},{pattern:"Math\\.|parseInt|parseFloat",category:"math",safeAlternative:"Math functions or parseInt/parseFloat",example:{bad:"eval('Math.' + method + '(' + arg + ')')",good:"const mathMethods = {sin: Math.sin, cos: Math.cos}; mathMethods[method](arg)"},effort:"5 minutes"},{pattern:"\\$\\{|template|interpolat",category:"template",safeAlternative:"Template literals or template engine",example:{bad:"eval('Hello ' + userName + '!')",good:"const template = `Hello ${userName}!`;"},effort:"3 minutes"},{pattern:"\\[.*\\]|object\\[|obj\\.|\\.",category:"object",safeAlternative:"Direct property access or Map",example:{bad:"eval('obj.' + property)",good:"const allowedProps = {name: true, age: true}; if (allowedProps[property]) obj[property]"},effort:"8 minutes"}];const VM_CODE_SINK_METHODS=new Set(["runInNewContext","runInThisContext","runInContext","compileFunction"]);const VM_CODE_CONSTRUCTORS=new Set(["Script","SourceTextModule"]);const VM2_SANDBOX_CONSTRUCTORS=new Set(["VM","NodeVM"]);const VM2_CODE_CONSTRUCTORS=new Set(["VMScript"]);const VM_MODULES=new Set(["vm","node:vm"]);const VM2_MODULES=new Set(["vm2"]);const NAMESPACE="*";function requiredModule(node){if(!node||node.type!=="CallExpression")return null;if(node.callee.type!=="Identifier"||node.callee.name!=="require"){return null}const[source]=node.arguments;if(!source||source.type!=="Literal")return null;return typeof source.value==="string"?source.value:null}function resolveModuleMember(callee,bindings,modules,name){if(name===null)return null;if(callee.type==="Identifier"){const bound=bindings.get(name);return bound!==void 0&&bound!==NAMESPACE?bound:null}const{object}=callee;if(object.type==="Identifier"){return bindings.get(object.name)===NAMESPACE?name:null}const inline=requiredModule(object);return inline!==null&&modules.has(inline)?name:null}function calleeTrailingName(callee){if(callee.type==="Identifier")return callee.name;if(callee.type!=="MemberExpression"||callee.computed)return null;return callee.property.type==="Identifier"?callee.property.name:null}function isStaticStringNode(node){if(node.type==="Literal")return typeof node.value==="string";if(node.type==="TemplateLiteral")return node.expressions.length===0;return false}const generateRefactoringSteps=pattern=>{if(!pattern){return[" 1. Remove eval() usage entirely"," 2. Identify what the code is trying to achieve"," 3. Use appropriate safe alternative (JSON.parse, Map, etc.)"," 4. Add input validation if dynamic behavior needed"," 5. Test thoroughly for edge cases"].join("\n")}switch(pattern.category){case"json":return[" 1. Replace eval() with JSON.parse()"," 2. Ensure input is valid JSON string"," 3. Add try/catch for JSON parsing errors"," 4. Consider using a JSON schema validator"].join("\n");case"math":return[" 1. Create whitelist of allowed Math functions"," 2. Use direct function calls: Math.sin(x)"," 3. Validate inputs are numbers"," 4. Consider using a math expression parser library"].join("\n");case"template":return[" 1. Use template literals: `Hello ${name}`"," 2. Sanitize variables before interpolation"," 3. Use a template engine like Handlebars if complex"," 4. Validate template structure"].join("\n");case"object":return[" 1. Use Map or plain object for key-value access"," 2. Whitelist allowed property names"," 3. Use hasOwnProperty() check"," 4. Consider Object.create(null) for clean objects"].join("\n");default:return[" 1. Identify the specific use case"," 2. Find a safer alternative approach"," 3. Add comprehensive input validation"," 4. Use static analysis if possible"].join("\n")}};exports.generateRefactoringSteps=generateRefactoringSteps;exports.detectEvalWithExpression=(0,eslint_devkit_2.createRule)({name:"detect-eval-with-expression",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/detect-eval-with-expression.md",description:"Detects strings turned into running code \u2014 eval(variable), the Function constructor, and the vm / vm2 sinks that are mistaken for sandboxes",cwe:"CWE-95",cvss:9.8,confidence:"high"},hasSuggestions:false,messages:{vmCodeExecution:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Code Execution Through the vm Module (CWE-94)",cwe:"CWE-94",cvss:9.8,description:'vm.{{api}}() compiles and runs its first argument as JavaScript, and that argument is not written out in full here. The vm module is NOT a security boundary \u2014 Node documents it as such \u2014 because any object reachable from the context carries a constructor chain back out: `this.constructor.constructor("return process")()`. A string that is not a constant is a string an attacker may be able to steer.',severity:"CRITICAL",fix:"Do not evaluate the value as code. Parse it (JSON.parse, an expression parser) or dispatch through a fixed map of allowed operations; if untrusted code genuinely has to run, isolate it in a separate process with its own privileges \u2014 not in vm.",documentationLink:"https://nodejs.org/api/vm.html#vm-executing-javascript"}),vm2CodeExecution:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Code Execution Through vm2 (CWE-94)",cwe:"CWE-94",cvss:9.8,description:"vm2 is abandoned and was retired by its maintainer after sandbox escapes that it could not fix (CVE-2023-37903, CVE-2023-37466). Running source that is not a constant inside it is arbitrary code execution on the host, not sandboxed execution.",severity:"CRITICAL",fix:"Stop using vm2. Run untrusted code out-of-process under an OS-level boundary (a separate process with dropped privileges, a container, or isolated-vm), or remove the need to execute caller-supplied source.",documentationLink:"https://github.com/patriksimek/vm2/issues/533"}),useJsonParse:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe eval() for JSON parsing",cwe:"CWE-95",description:"Use JSON.parse() instead of eval() for JSON string parsing",severity:"HIGH",fix:"Replace eval() with JSON.parse()",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),useObjectAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe eval() for property access",cwe:"CWE-95",description:"Use direct property access instead of eval() for dynamic property access",severity:"HIGH",fix:"Use obj[key] or Map.get(key) instead of eval()",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),useTemplateLiteral:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe eval() for string interpolation",cwe:"CWE-95",description:"Use template literals instead of eval() for string interpolation",severity:"HIGH",fix:"Replace eval() with template literals: `Hello ${name}`",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),strategyRemove:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Critical eval() security vulnerability",cwe:"CWE-95",description:"eval() usage poses severe security risk",severity:"CRITICAL",fix:"Remove eval() entirely - security risk too high",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),strategyRefactor:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"eval() refactoring required",cwe:"CWE-95",description:"eval() can be refactored to safer alternative",severity:"HIGH",fix:"{{safeAlternative}}",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),strategyValidate:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"eval() input validation needed",cwe:"CWE-95",description:"eval() requires input validation for security",severity:"MEDIUM",fix:"Add input validation before using eval()",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"})},schema:[{type:"object",properties:{allowLiteralStrings:{type:"boolean",default:false,description:"Allow eval with literal strings (false = stricter)"},additionalEvalFunctions:{type:"array",items:{type:"string"},default:[],description:"Additional functions to treat as eval-like"},strategy:{type:"string",enum:["remove","refactor","validate","auto"],default:"auto",description:"Strategy for fixing eval usage (auto = smart detection)"}},additionalProperties:false}]},defaultOptions:[{allowLiteralStrings:false,additionalEvalFunctions:[],strategy:"auto"}],create(context){const options=context.options[0]||{};const{allowLiteralStrings=false,additionalEvalFunctions=[],strategy="auto"}=options;const evalFunctions=new Set(["eval","Function",...additionalEvalFunctions]);const isLiteralString=node=>{return node.type==="Literal"&&typeof node.value==="string"};const selectStrategyMessage=pattern=>{switch(strategy){case"remove":return"strategyRemove";case"refactor":return"strategyRefactor";case"validate":return"strategyValidate";case"auto":default:if(pattern&&pattern.category==="json"){return"useJsonParse"}if(pattern&&pattern.category==="object"){return"useObjectAccess"}if(pattern&&pattern.category==="template"){return"useTemplateLiteral"}return"strategyRefactor"}};const detectPattern=expression=>{for(const pattern of EVAL_PATTERNS){if(new RegExp(pattern.pattern,"i").test(expression)){return pattern}}return null};const extractExpression=node=>{const sourceCode=context.sourceCode;if(node.arguments.length>0){return sourceCode.getText(node.arguments[0])}return"dynamic expression"};const GLOBAL_OBJECTS=new Set(["globalThis","global","window","self"]);const evalCalleeName=(callee,depth=0)=>{if(depth>3)return null;if(callee.type==="Identifier"){if(evalFunctions.has(callee.name))return callee.name;const init=(0,const_value_1.constInitializerOf)(context.sourceCode,callee);return init?evalCalleeName(init,depth+1):null}if(callee.type==="SequenceExpression"){const[last]=callee.expressions.slice(-1);return evalCalleeName(last,depth+1)}if(callee.type==="MemberExpression"&&!callee.computed&&callee.object.type==="Identifier"&&GLOBAL_OBJECTS.has(callee.object.name)&&callee.property.type==="Identifier"&&evalFunctions.has(callee.property.name)){return callee.property.name}return null};const checkCallExpression=node=>{const evalName=evalCalleeName(node.callee);if(evalName!==null){if(allowLiteralStrings&&node.arguments.length>0&&isLiteralString(node.arguments[0])){return}if(node.arguments.length>0&&evalName==="eval"&&isLiteralString(node.arguments[0])){return}const expression=extractExpression(node);const pattern=detectPattern(expression);const steps=(0,exports.generateRefactoringSteps)(pattern);context.report({node,messageId:selectStrategyMessage(pattern),data:{expression,patternCategory:pattern?.category||"dynamic code execution",safeAlternative:pattern?.safeAlternative||"Remove eval entirely",steps,effort:pattern?.effort||"15-30 minutes"}})}};const checkNewExpression=node=>{if(node.callee.type==="Identifier"&&node.callee.name==="Function"){const sourceCode=context.sourceCode;const expression=node.arguments.map(arg=>sourceCode.getText(arg)).join(", ");const pattern=detectPattern(expression);context.report({node,messageId:selectStrategyMessage(pattern),data:{expression:`new Function(${expression})`,patternCategory:"function constructor",safeAlternative:"Arrow function or regular function",steps:[" 1. Replace Function constructor with arrow function"," 2. Use regular function declaration"," 3. Validate any dynamic parts"," 4. Consider module imports instead"].join("\n"),effort:"10 minutes"}})}};const vmBindings=new Map;const vm2Bindings=new Map;const vm2SandboxCandidates=[];const pendingVmCalls=[];const pendingVmNews=[];const findVariable=node=>{let scope=context.sourceCode.getScope(node);while(scope){const found=scope.variables.find(v=>v.name===node.name);if(found)return found;scope=scope.upper}return null};const isStaticCode=node=>{if(!node)return true;if(isStaticStringNode(node))return true;if(node.type!=="Identifier")return false;const variable=findVariable(node);if(!variable)return false;const lastWrite=variable.references.filter(ref=>ref.isWrite()).map(ref=>ref.writeExpr).filter(write=>write!=null).filter(write=>write.range[1]<=node.range[0]).sort((a,b)=>a.range[1]-b.range[1]).at(-1);return lastWrite!==void 0&&isStaticStringNode(lastWrite)};const bindModuleName=(moduleName,local,imported)=>{if(VM_MODULES.has(moduleName))vmBindings.set(local,imported);else if(VM2_MODULES.has(moduleName))vm2Bindings.set(local,imported)};const bindRequire=node=>{const moduleName=requiredModule(node.init);if(moduleName===null)return;if(node.id.type==="Identifier"){bindModuleName(moduleName,node.id.name,NAMESPACE);return}if(node.id.type!=="ObjectPattern")return;for(const property of node.id.properties){if(property.type!=="Property")continue;if(property.key.type!=="Identifier")continue;if(property.value.type!=="Identifier")continue;bindModuleName(moduleName,property.value.name,property.key.name)}};const reportVmSite=(node,code,messageId,api)=>{if(isStaticCode(code))return;context.report({node,messageId,data:{api}})};const trailingName=callee=>{if(callee.type==="MemberExpression"&&callee.computed){return(0,const_value_1.resolveConstantString)(context.sourceCode,callee.property)?.value??null}return calleeTrailingName(callee)};const isVm2Run=(node,sandboxes)=>{const{callee}=node;if(callee.type!=="MemberExpression")return false;if(trailingName(callee)!=="run")return false;if(callee.object.type==="Identifier"){return sandboxes.has(callee.object.name)}if(callee.object.type!=="NewExpression")return false;const constructed=resolveModuleMember(callee.object.callee,vm2Bindings,VM2_MODULES,trailingName(callee.object.callee));return constructed!==null&&VM2_SANDBOX_CONSTRUCTORS.has(constructed)};const judgeVmSites=()=>{const sandboxes=new Set;for(const candidate of vm2SandboxCandidates){const constructed=resolveModuleMember(candidate.init.callee,vm2Bindings,VM2_MODULES,trailingName(candidate.init.callee));if(constructed!==null&&VM2_SANDBOX_CONSTRUCTORS.has(constructed)){sandboxes.add(candidate.local)}}for(const node of pendingVmCalls){const name=trailingName(node.callee);const vmApi=resolveModuleMember(node.callee,vmBindings,VM_MODULES,name);if(vmApi!==null&&VM_CODE_SINK_METHODS.has(vmApi)){reportVmSite(node,node.arguments[0],"vmCodeExecution",vmApi);continue}if(isVm2Run(node,sandboxes)){reportVmSite(node,node.arguments[0],"vm2CodeExecution","run")}}for(const node of pendingVmNews){const name=trailingName(node.callee);const vmCtor=resolveModuleMember(node.callee,vmBindings,VM_MODULES,name);if(vmCtor!==null&&VM_CODE_CONSTRUCTORS.has(vmCtor)){reportVmSite(node,node.arguments[0],"vmCodeExecution",vmCtor);continue}const vm2Ctor=resolveModuleMember(node.callee,vm2Bindings,VM2_MODULES,name);if(vm2Ctor!==null&&VM2_CODE_CONSTRUCTORS.has(vm2Ctor)){reportVmSite(node,node.arguments[0],"vm2CodeExecution",vm2Ctor)}}};return{CallExpression(node){checkCallExpression(node);if(node.callee.type==="Identifier"){pendingVmCalls.push(node);return}const name=trailingName(node.callee);if(name!==null&&(VM_CODE_SINK_METHODS.has(name)||name==="run")){pendingVmCalls.push(node)}},NewExpression(node){checkNewExpression(node);if(node.callee.type==="Identifier"){pendingVmNews.push(node);return}const name=trailingName(node.callee);if(name!==null&&(VM_CODE_CONSTRUCTORS.has(name)||VM2_CODE_CONSTRUCTORS.has(name))){pendingVmNews.push(node)}},ImportDeclaration(node){const moduleName=node.source.value;for(const specifier of node.specifiers){if(specifier.type==="ImportSpecifier"){if(specifier.imported.type!=="Identifier")continue;bindModuleName(moduleName,specifier.local.name,specifier.imported.name);continue}bindModuleName(moduleName,specifier.local.name,NAMESPACE)}},VariableDeclarator(node){bindRequire(node);if(node.id.type==="Identifier"&&node.init?.type==="NewExpression"){vm2SandboxCandidates.push({local:node.id.name,init:node.init})}},"Program:exit":judgeVmSites}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.detectEvalWithExpression=exports.generateRefactoringSteps=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const EVAL_PATTERNS=[{pattern:"JSON\\.parse|parse\\(.*\\)",category:"json",safeAlternative:"JSON.parse()",example:{bad:`eval('{"key": "' + value + '"}"')`,good:`JSON.parse('{"key": "' + value + '"}"')`},effort:"2 minutes"},{pattern:"Math\\.|parseInt|parseFloat",category:"math",safeAlternative:"Math functions or parseInt/parseFloat",example:{bad:"eval('Math.' + method + '(' + arg + ')')",good:"const mathMethods = {sin: Math.sin, cos: Math.cos}; mathMethods[method](arg)"},effort:"5 minutes"},{pattern:"\\$\\{|template|interpolat",category:"template",safeAlternative:"Template literals or template engine",example:{bad:"eval('Hello ' + userName + '!')",good:"const template = `Hello ${userName}!`;"},effort:"3 minutes"},{pattern:"\\[.*\\]|object\\[|obj\\.|\\.",category:"object",safeAlternative:"Direct property access or Map",example:{bad:"eval('obj.' + property)",good:"const allowedProps = {name: true, age: true}; if (allowedProps[property]) obj[property]"},effort:"8 minutes"}];const VM_CODE_SINK_METHODS=new Set(["runInNewContext","runInThisContext","runInContext","compileFunction"]);const VM_CODE_CONSTRUCTORS=new Set(["Script","SourceTextModule"]);const VM2_SANDBOX_CONSTRUCTORS=new Set(["VM","NodeVM"]);const VM2_CODE_CONSTRUCTORS=new Set(["VMScript"]);const VM_MODULES=new Set(["vm","node:vm"]);const VM2_MODULES=new Set(["vm2"]);const NAMESPACE="*";function requiredModule(node){if(!node||node.type!=="CallExpression")return null;if(node.callee.type!=="Identifier"||node.callee.name!=="require"){return null}const[source]=node.arguments;if(!source||source.type!=="Literal")return null;return typeof source.value==="string"?source.value:null}function resolveModuleMember(callee,bindings,modules,name){if(name===null)return null;if(callee.type==="Identifier"){const bound=bindings.get(name);return bound!==void 0&&bound!==NAMESPACE?bound:null}const{object}=callee;if(object.type==="Identifier"){return bindings.get(object.name)===NAMESPACE?name:null}const inline=requiredModule(object);return inline!==null&&modules.has(inline)?name:null}function calleeTrailingName(callee){if(callee.type==="Identifier")return callee.name;if(callee.type!=="MemberExpression"||callee.computed)return null;return callee.property.type==="Identifier"?callee.property.name:null}function isStaticStringNode(node){if(node.type==="Literal")return typeof node.value==="string";if(node.type==="TemplateLiteral")return node.expressions.length===0;return false}const generateRefactoringSteps=pattern=>{if(!pattern){return[" 1. Remove eval() usage entirely"," 2. Identify what the code is trying to achieve"," 3. Use appropriate safe alternative (JSON.parse, Map, etc.)"," 4. Add input validation if dynamic behavior needed"," 5. Test thoroughly for edge cases"].join("\n")}switch(pattern.category){case"json":return[" 1. Replace eval() with JSON.parse()"," 2. Ensure input is valid JSON string"," 3. Add try/catch for JSON parsing errors"," 4. Consider using a JSON schema validator"].join("\n");case"math":return[" 1. Create whitelist of allowed Math functions"," 2. Use direct function calls: Math.sin(x)"," 3. Validate inputs are numbers"," 4. Consider using a math expression parser library"].join("\n");case"template":return[" 1. Use template literals: `Hello ${name}`"," 2. Sanitize variables before interpolation"," 3. Use a template engine like Handlebars if complex"," 4. Validate template structure"].join("\n");case"object":return[" 1. Use Map or plain object for key-value access"," 2. Whitelist allowed property names"," 3. Use hasOwnProperty() check"," 4. Consider Object.create(null) for clean objects"].join("\n");default:return[" 1. Identify the specific use case"," 2. Find a safer alternative approach"," 3. Add comprehensive input validation"," 4. Use static analysis if possible"].join("\n")}};exports.generateRefactoringSteps=generateRefactoringSteps;exports.detectEvalWithExpression=(0,eslint_devkit_2.createRule)({name:"detect-eval-with-expression",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/detect-eval-with-expression.md",description:"Detects strings turned into running code \u2014 eval(variable), the Function constructor, and the vm / vm2 sinks that are mistaken for sandboxes",cwe:"CWE-95",cvss:9.8,confidence:"high"},hasSuggestions:false,messages:{vmCodeExecution:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Code Execution Through the vm Module (CWE-94)",cwe:"CWE-94",cvss:9.8,description:'vm.{{api}}() compiles and runs its first argument as JavaScript, and that argument is not written out in full here. The vm module is NOT a security boundary \u2014 Node documents it as such \u2014 because any object reachable from the context carries a constructor chain back out: `this.constructor.constructor("return process")()`. A string that is not a constant is a string an attacker may be able to steer.',severity:"CRITICAL",fix:"Do not evaluate the value as code. Parse it (JSON.parse, an expression parser) or dispatch through a fixed map of allowed operations; if untrusted code genuinely has to run, isolate it in a separate process with its own privileges \u2014 not in vm.",documentationLink:"https://nodejs.org/api/vm.html#vm-executing-javascript"}),vm2CodeExecution:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Code Execution Through vm2 (CWE-94)",cwe:"CWE-94",cvss:9.8,description:"vm2 is abandoned and was retired by its maintainer after sandbox escapes that it could not fix (CVE-2023-37903, CVE-2023-37466). Running source that is not a constant inside it is arbitrary code execution on the host, not sandboxed execution.",severity:"CRITICAL",fix:"Stop using vm2. Run untrusted code out-of-process under an OS-level boundary (a separate process with dropped privileges, a container, or isolated-vm), or remove the need to execute caller-supplied source.",documentationLink:"https://github.com/patriksimek/vm2/issues/533"}),useJsonParse:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe eval() for JSON parsing",cwe:"CWE-95",description:"Use JSON.parse() instead of eval() for JSON string parsing",severity:"HIGH",fix:"Replace eval() with JSON.parse()",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),useObjectAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe eval() for property access",cwe:"CWE-95",description:"Use direct property access instead of eval() for dynamic property access",severity:"HIGH",fix:"Use obj[key] or Map.get(key) instead of eval()",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),useTemplateLiteral:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe eval() for string interpolation",cwe:"CWE-95",description:"Use template literals instead of eval() for string interpolation",severity:"HIGH",fix:"Replace eval() with template literals: `Hello ${name}`",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),strategyRemove:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Critical eval() security vulnerability",cwe:"CWE-95",description:"eval() usage poses severe security risk",severity:"CRITICAL",fix:"Remove eval() entirely - security risk too high",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),strategyRefactor:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"eval() refactoring required",cwe:"CWE-95",description:"eval() can be refactored to safer alternative",severity:"HIGH",fix:"{{safeAlternative}}",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),strategyValidate:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"eval() input validation needed",cwe:"CWE-95",description:"eval() requires input validation for security",severity:"MEDIUM",fix:"Add input validation before using eval()",documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"})},schema:[{type:"object",properties:{allowLiteralStrings:{type:"boolean",default:false,description:"Allow eval with literal strings (false = stricter)"},additionalEvalFunctions:{type:"array",items:{type:"string"},default:[],description:"Additional functions to treat as eval-like"},strategy:{type:"string",enum:["remove","refactor","validate","auto"],default:"auto",description:"Strategy for fixing eval usage (auto = smart detection)"}},additionalProperties:false}]},defaultOptions:[{allowLiteralStrings:false,additionalEvalFunctions:[],strategy:"auto"}],create(context){const options=context.options[0]||{};const{allowLiteralStrings=false,additionalEvalFunctions=[],strategy="auto"}=options;const evalFunctions=new Set(["eval","Function",...additionalEvalFunctions]);const isLiteralString=node=>{return node.type==="Literal"&&typeof node.value==="string"};const selectStrategyMessage=pattern=>{switch(strategy){case"remove":return"strategyRemove";case"refactor":return"strategyRefactor";case"validate":return"strategyValidate";case"auto":default:if(pattern&&pattern.category==="json"){return"useJsonParse"}if(pattern&&pattern.category==="object"){return"useObjectAccess"}if(pattern&&pattern.category==="template"){return"useTemplateLiteral"}return"strategyRefactor"}};const detectPattern=expression=>{for(const pattern of EVAL_PATTERNS){if(new RegExp(pattern.pattern,"i").test(expression)){return pattern}}return null};const extractExpression=node=>{const sourceCode=context.sourceCode;if(node.arguments.length>0){return sourceCode.getText(node.arguments[0])}return"dynamic expression"};const GLOBAL_OBJECTS=new Set(["globalThis","global","window","self"]);const isGlobalBinding=identifier=>{const variable=findVariable(identifier);return!variable||variable.defs.length===0};const evalCalleeName=(callee,depth=0)=>{if(depth>3)return null;if(callee.type==="Identifier"){if(evalFunctions.has(callee.name)){if(isGlobalBinding(callee))return callee.name;const aliased=(0,const_value_1.constInitializerOf)(context.sourceCode,callee);return aliased?evalCalleeName(aliased,depth+1):null}const init=(0,const_value_1.constInitializerOf)(context.sourceCode,callee);return init?evalCalleeName(init,depth+1):null}if(callee.type==="SequenceExpression"){const[last]=callee.expressions.slice(-1);return evalCalleeName(last,depth+1)}if(callee.type==="MemberExpression"&&!callee.computed&&callee.object.type==="Identifier"&&GLOBAL_OBJECTS.has(callee.object.name)&&callee.property.type==="Identifier"&&evalFunctions.has(callee.property.name)){return callee.property.name}return null};const checkCallExpression=node=>{const evalName=evalCalleeName(node.callee);if(evalName!==null){if(allowLiteralStrings&&node.arguments.length>0&&isLiteralString(node.arguments[0])){return}if(node.arguments.length>0&&evalName==="eval"&&isLiteralString(node.arguments[0])){return}const expression=extractExpression(node);const pattern=detectPattern(expression);const steps=(0,exports.generateRefactoringSteps)(pattern);context.report({node,messageId:selectStrategyMessage(pattern),data:{expression,patternCategory:pattern?.category||"dynamic code execution",safeAlternative:pattern?.safeAlternative||"Remove eval entirely",steps,effort:pattern?.effort||"15-30 minutes"}})}};const checkNewExpression=node=>{if(node.callee.type==="Identifier"&&node.callee.name==="Function"&&isGlobalBinding(node.callee)){const sourceCode=context.sourceCode;const expression=node.arguments.map(arg=>sourceCode.getText(arg)).join(", ");const pattern=detectPattern(expression);context.report({node,messageId:selectStrategyMessage(pattern),data:{expression:`new Function(${expression})`,patternCategory:"function constructor",safeAlternative:"Arrow function or regular function",steps:[" 1. Replace Function constructor with arrow function"," 2. Use regular function declaration"," 3. Validate any dynamic parts"," 4. Consider module imports instead"].join("\n"),effort:"10 minutes"}})}};const vmBindings=new Map;const vm2Bindings=new Map;const vm2SandboxCandidates=[];const pendingVmCalls=[];const pendingVmNews=[];const findVariable=node=>{let scope=context.sourceCode.getScope(node);while(scope){const found=scope.variables.find(v=>v.name===node.name);if(found)return found;scope=scope.upper}return null};const isStaticCode=node=>{if(!node)return true;if(isStaticStringNode(node))return true;if(node.type!=="Identifier")return false;const variable=findVariable(node);if(!variable)return false;const lastWrite=variable.references.filter(ref=>ref.isWrite()).map(ref=>ref.writeExpr).filter(write=>write!=null).filter(write=>write.range[1]<=node.range[0]).sort((a,b)=>a.range[1]-b.range[1]).at(-1);return lastWrite!==void 0&&isStaticStringNode(lastWrite)};const bindModuleName=(moduleName,local,imported)=>{if(VM_MODULES.has(moduleName))vmBindings.set(local,imported);else if(VM2_MODULES.has(moduleName))vm2Bindings.set(local,imported)};const bindRequire=node=>{const moduleName=requiredModule(node.init);if(moduleName===null)return;if(node.id.type==="Identifier"){bindModuleName(moduleName,node.id.name,NAMESPACE);return}if(node.id.type!=="ObjectPattern")return;for(const property of node.id.properties){if(property.type!=="Property")continue;if(property.key.type!=="Identifier")continue;if(property.value.type!=="Identifier")continue;bindModuleName(moduleName,property.value.name,property.key.name)}};const reportVmSite=(node,code,messageId,api)=>{if(isStaticCode(code))return;context.report({node,messageId,data:{api}})};const trailingName=callee=>{if(callee.type==="MemberExpression"&&callee.computed){return(0,const_value_1.resolveConstantString)(context.sourceCode,callee.property)?.value??null}return calleeTrailingName(callee)};const isVm2Run=(node,sandboxes)=>{const{callee}=node;if(callee.type!=="MemberExpression")return false;if(trailingName(callee)!=="run")return false;if(callee.object.type==="Identifier"){return sandboxes.has(callee.object.name)}if(callee.object.type!=="NewExpression")return false;const constructed=resolveModuleMember(callee.object.callee,vm2Bindings,VM2_MODULES,trailingName(callee.object.callee));return constructed!==null&&VM2_SANDBOX_CONSTRUCTORS.has(constructed)};const judgeVmSites=()=>{const sandboxes=new Set;for(const candidate of vm2SandboxCandidates){const constructed=resolveModuleMember(candidate.init.callee,vm2Bindings,VM2_MODULES,trailingName(candidate.init.callee));if(constructed!==null&&VM2_SANDBOX_CONSTRUCTORS.has(constructed)){sandboxes.add(candidate.local)}}for(const node of pendingVmCalls){const name=trailingName(node.callee);const vmApi=resolveModuleMember(node.callee,vmBindings,VM_MODULES,name);if(vmApi!==null&&VM_CODE_SINK_METHODS.has(vmApi)){reportVmSite(node,node.arguments[0],"vmCodeExecution",vmApi);continue}if(isVm2Run(node,sandboxes)){reportVmSite(node,node.arguments[0],"vm2CodeExecution","run")}}for(const node of pendingVmNews){const name=trailingName(node.callee);const vmCtor=resolveModuleMember(node.callee,vmBindings,VM_MODULES,name);if(vmCtor!==null&&VM_CODE_CONSTRUCTORS.has(vmCtor)){reportVmSite(node,node.arguments[0],"vmCodeExecution",vmCtor);continue}const vm2Ctor=resolveModuleMember(node.callee,vm2Bindings,VM2_MODULES,name);if(vm2Ctor!==null&&VM2_CODE_CONSTRUCTORS.has(vm2Ctor)){reportVmSite(node,node.arguments[0],"vm2CodeExecution",vm2Ctor)}}};return{CallExpression(node){checkCallExpression(node);if(node.callee.type==="Identifier"){pendingVmCalls.push(node);return}const name=trailingName(node.callee);if(name!==null&&(VM_CODE_SINK_METHODS.has(name)||name==="run")){pendingVmCalls.push(node)}},NewExpression(node){checkNewExpression(node);if(node.callee.type==="Identifier"){pendingVmNews.push(node);return}const name=trailingName(node.callee);if(name!==null&&(VM_CODE_CONSTRUCTORS.has(name)||VM2_CODE_CONSTRUCTORS.has(name))){pendingVmNews.push(node)}},ImportDeclaration(node){const moduleName=node.source.value;for(const specifier of node.specifiers){if(specifier.type==="ImportSpecifier"){if(specifier.imported.type!=="Identifier")continue;bindModuleName(moduleName,specifier.local.name,specifier.imported.name);continue}bindModuleName(moduleName,specifier.local.name,NAMESPACE)}},VariableDeclarator(node){bindRequire(node);if(node.id.type==="Identifier"&&node.init?.type==="NewExpression"){vm2SandboxCandidates.push({local:node.id.name,init:node.init})}},"Program:exit":judgeVmSites}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMathRandomCrypto=void 0;const names_1=require("../../utils/names");const provenance_1=require("../../utils/provenance");const eslint_devkit_1=require("@interlace/eslint-devkit");const CRYPTO_WORDS=["token","tokens","key","keys","secret","secrets","password","passwd","salt","iv","nonce","seed","hash","cipher","auth","session","csrf","otp","pin","code","codes","verify","signature","credential","jwt","encryption","apikey"];const CRYPTO_WORD_SET=new Set(CRYPTO_WORDS);const DURATION_TAILS=new Set(["delay","timeout","interval","jitter","backoff","ms","millis","milliseconds","seconds","duration","wait","sleep","ttl","deadline","elapsed","latency","budget"]);const QUANTITY_TAILS=new Set(["count","counts","length","size","total","limit","quota","offset","index","rank","score","percent","ratio","rate","version","page"]);const NON_SECURITY_QUALIFIERS=new Map([["code",new Set(["http","status","error","err","exit","country","zip","postal","area","language","lang","locale","currency","promo","coupon","discount","color","colour","qr","bar","region","iso","mime","media","sort","source","char","unicode","ascii","airport"])],["key",new Set(["cache","map","object","row","index","partition","primary","foreign","sort","storage","translation","locale","i18n","react","idempotency","shortcut","keyboard","press","modifier"])]]);const baseNameSuggestsCrypto=(0,names_1.makeNameTest)(CRYPTO_WORDS);function nameSuggestsCrypto(name){if(!baseNameSuggestsCrypto(name))return false;const words=(0,names_1.identifierWords)(name);const tail=words[words.length-1];if(DURATION_TAILS.has(tail)||QUANTITY_TAILS.has(tail))return false;const matched=words.filter(word=>CRYPTO_WORD_SET.has(word));if(matched.length===0)return true;return!matched.every(word=>{const qualifiers=NON_SECURITY_QUALIFIERS.get(word);return qualifiers!==void 0&&words.some(other=>qualifiers.has(other))})}function isMathRandomProperty(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.object.name!=="Math")return false;const property=node.property;if(!node.computed){return property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.name==="random"}return property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&property.value==="random"}const CRYPTO_FUNCTION_PATTERNS=[/generate.*token/i,/generate.*key/i,/generate.*id/i,/create.*secret/i,/create.*token/i,/random.*string/i,/get.*random.*(string|bytes|token|key|secret|value|id)/i,/make.*salt/i,/gen.*password/i];exports.noMathRandomCrypto=(0,eslint_devkit_1.createRule)({name:"no-math-random-crypto",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-math-random-crypto.md",description:"Disallow Math.random() for cryptographic purposes",cwe:"CWE-338",cvss:5.3,confidence:"medium"},messages:{mathRandomCrypto:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Math.random() used for crypto",cwe:"CWE-338",description:"Math.random() is not cryptographically secure. It uses a PRNG that can be predicted. Never use it for tokens, keys, passwords, or any security-sensitive values.",severity:"CRITICAL",fix:"Use crypto.randomBytes() or crypto.randomUUID() instead",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#secure-random-number-generation"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow Math.random() in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const sourceCode=context.sourceCode;const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function stableDeclarator(id){const variable=(0,provenance_1.findVariable)(sourceCode,id);if(!variable||variable.defs.length!==1)return void 0;const def=variable.defs[0];if(def.type!=="Variable")return void 0;const reassigned=variable.references.some(reference=>reference.writeExpr!=null&&!reference.init);return reassigned?void 0:def.node}function isMathRandomCallee(callee){if(isMathRandomProperty(callee))return true;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init2=stableDeclarator(callee.object)?.init;if(!init2||init2.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return false;const wanted=callee.property.name;return init2.properties.some(property=>property.type===eslint_devkit_1.AST_NODE_TYPES.Property&&!property.computed&&property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.key.name===wanted&&isMathRandomProperty(property.value))}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const declarator=stableDeclarator(callee);const init=declarator?.init;if(!declarator||!init)return false;if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return isMathRandomProperty(init)}if(declarator.id.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectPattern)return false;if(init.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||init.name!=="Math")return false;return declarator.id.properties.some(property=>{if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property||property.computed)return false;if(property.value.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(property.value.name!==callee.name)return false;const key=property.key;if(key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return key.name==="random";return key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&key.value==="random"})}const MAX_BINDING_HOPS=2;function usedInCryptoContext(id,depth){const variable=(0,provenance_1.findVariable)(sourceCode,id);if(!variable)return false;return variable.references.some(reference=>!reference.init&&isCryptoContext(reference.identifier,depth))}function isCryptoContext(node,depth=0){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){if(current.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const varName=current.id.name;if(nameSuggestsCrypto(varName)){return true}if(depth<MAX_BINDING_HOPS&&usedInCryptoContext(current.id,depth+1)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&¤t.id){const funcName=current.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))){return true}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression){if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&¤t.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.left.property.name;if(nameSuggestsCrypto(propName)){return true}}if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(nameSuggestsCrypto(current.left.name)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.Property){if(current.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.key.name;if(nameSuggestsCrypto(propName)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){const func=findContainingFunction(current);if(func){if((func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression)&&func.id?.name){const funcName=func.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))||nameSuggestsCrypto(funcName)){return true}}}}current=current.parent}return false}function findContainingFunction(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){return current}current=current.parent}return null}return{CallExpression(node){if(isTestFile)return;if(isMathRandomCallee(node.callee)){if(isCryptoContext(node)){context.report({node,messageId:"mathRandomCrypto"})}}}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMathRandomCrypto=void 0;const names_1=require("../../utils/names");const provenance_1=require("../../utils/provenance");const eslint_devkit_1=require("@interlace/eslint-devkit");const CRYPTO_WORDS=["token","tokens","key","keys","secret","secrets","password","passwd","salt","iv","nonce","seed","hash","cipher","auth","session","csrf","otp","pin","code","codes","verify","signature","credential","jwt","encryption","apikey"];const CRYPTO_WORD_SET=new Set(CRYPTO_WORDS);const DURATION_TAILS=new Set(["delay","timeout","interval","jitter","backoff","ms","millis","milliseconds","seconds","duration","wait","sleep","ttl","deadline","elapsed","latency","budget"]);const QUANTITY_TAILS=new Set(["count","counts","length","size","total","limit","quota","offset","index","rank","score","percent","ratio","rate","version","page"]);const NON_SECURITY_QUALIFIERS=new Map([["code",new Set(["http","status","error","err","exit","country","zip","postal","area","language","lang","locale","currency","promo","coupon","discount","color","colour","qr","bar","region","iso","mime","media","sort","source","char","unicode","ascii","airport"])],["key",new Set(["cache","map","object","row","index","partition","primary","foreign","sort","storage","translation","locale","i18n","react","idempotency","shortcut","keyboard","press","modifier"])]]);const baseNameSuggestsCrypto=(0,names_1.makeNameTest)(CRYPTO_WORDS);function nameSuggestsCrypto(name){if(!baseNameSuggestsCrypto(name))return false;const words=(0,names_1.identifierWords)(name);const tail=words[words.length-1];if(DURATION_TAILS.has(tail)||QUANTITY_TAILS.has(tail))return false;const matched=words.filter(word=>CRYPTO_WORD_SET.has(word));if(matched.length===0)return true;return!matched.every(word=>{const qualifiers=NON_SECURITY_QUALIFIERS.get(word);return qualifiers!==void 0&&words.some(other=>qualifiers.has(other))})}function isMathRandomProperty(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.object.name!=="Math")return false;const property=node.property;if(!node.computed){return property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.name==="random"}return property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&property.value==="random"}const CRYPTO_FUNCTION_PATTERNS=[/generate.*token/i,/generate.*key/i,/generate.*id/i,/create.*secret/i,/create.*token/i,/random.*string/i,/get.*random.*(string|bytes|token|key|secret|value|id)/i,/make.*salt/i,/gen.*password/i];exports.noMathRandomCrypto=(0,eslint_devkit_1.createRule)({name:"no-math-random-crypto",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-math-random-crypto.md",description:"Disallow Math.random() for cryptographic purposes",cwe:"CWE-338",cvss:5.3,confidence:"medium"},messages:{mathRandomCrypto:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Math.random() used for crypto",cwe:"CWE-338",description:"Math.random() is not cryptographically secure. It uses a PRNG that can be predicted. Never use it for tokens, keys, passwords, or any security-sensitive values.",severity:"CRITICAL",fix:"Use crypto.randomBytes() or crypto.randomUUID() instead",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#secure-random-number-generation"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow Math.random() in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const sourceCode=context.sourceCode;const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function stableDeclarator(id){const variable=(0,provenance_1.findVariable)(sourceCode,id);if(!variable||variable.defs.length!==1)return void 0;const def=variable.defs[0];if(def.type!=="Variable")return void 0;const reassigned=variable.references.some(reference=>reference.writeExpr!=null&&!reference.init);return reassigned?void 0:def.node}function isMathRandomCallee(callee){if(isMathRandomProperty(callee))return true;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init2=stableDeclarator(callee.object)?.init;if(!init2||init2.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return false;const wanted=callee.property.name;return init2.properties.some(property=>property.type===eslint_devkit_1.AST_NODE_TYPES.Property&&!property.computed&&property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.key.name===wanted&&isMathRandomProperty(property.value))}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const declarator=stableDeclarator(callee);const init=declarator?.init;if(!declarator||!init)return false;if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return isMathRandomProperty(init)}if(declarator.id.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectPattern)return false;if(init.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||init.name!=="Math")return false;return declarator.id.properties.some(property=>{if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property||property.computed)return false;if(property.value.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(property.value.name!==callee.name)return false;const key=property.key;if(key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return key.name==="random";return key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&key.value==="random"})}const MAX_BINDING_HOPS=2;function usedInCryptoContext(id,depth){const variable=(0,provenance_1.findVariable)(sourceCode,id);if(!variable)return false;return variable.references.some(reference=>!reference.init&&isCryptoContext(reference.identifier,depth))}function escapesFunction(fn,cameFrom,passedReturn){if(fn.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&fn.body===cameFrom){return true}return passedReturn}function isCryptoContext(node,depth=0){let child=node;let current=node.parent;let namesThisValue=true;let passedReturn=false;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(!escapesFunction(current,child,passedReturn))namesThisValue=false;passedReturn=false}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement)passedReturn=true;if(namesThisValue&¤t.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){if(current.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const varName=current.id.name;if(nameSuggestsCrypto(varName)){return true}if(depth<MAX_BINDING_HOPS&&usedInCryptoContext(current.id,depth+1)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&¤t.id){const funcName=current.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))){return true}}if(namesThisValue&¤t.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression){if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&¤t.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.left.property.name;if(nameSuggestsCrypto(propName)){return true}}if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(nameSuggestsCrypto(current.left.name)){return true}}}if(namesThisValue&¤t.type===eslint_devkit_1.AST_NODE_TYPES.Property){if(current.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.key.name;if(nameSuggestsCrypto(propName)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){const func=findContainingFunction(current);if(func){if((func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression)&&func.id?.name){const funcName=func.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))||nameSuggestsCrypto(funcName)){return true}}}}child=current;current=current.parent}return false}function findContainingFunction(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){return current}current=current.parent}return null}return{CallExpression(node){if(isTestFile)return;if(isMathRandomCallee(node.callee)){if(isCryptoContext(node)){context.report({node,messageId:"mathRandomCrypto"})}}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimingUnsafeCompare=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const names_1=require("../../utils/names");const DEFAULT_SECRET_PATTERNS=["token","secret","password","hash","signature","mac","hmac","digest","apiKey","api_key","api-key","auth","credential","bearer","jwt","csrf","nonce","ssn","social_security","social-security","pii","private_key","private-key","privateKey","access_token","access-token","accessToken","refresh_token","refresh-token","refreshToken","session_id","session-id","sessionId","auth_token","auth-token","authToken","encryption_key","encryption-key","encryptionKey"];function isSourceConstant(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.name==="undefined")return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral)return node.expressions.length===0;return false}const BOOLEAN_PREDICATE_NAME=/^(?:is|has|should|can|did|was|will|does)[A-Z]/;const NAMED_CONSTANT=/^[A-Z][A-Z0-9_]*$/;const DEFAULT_UNTRUSTED_SOURCES=["req","request","ctx","event"];const NAMESPACE_NAME=/^(?:[A-Z][A-Z0-9_]*|[A-Z][a-zA-Z0-9]*)$/;function isNamedConstant(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.computed)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return NAMESPACE_NAME.test(node.object.name)&&NAMED_CONSTANT.test(node.property.name)}function memberRoot(node){let current=node;for(;;){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object;continue}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){current=current.callee;continue}break}return current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?current:null}function isSelfComparison(left,right){const pair=(bare,derived)=>{if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(derived.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return memberRoot(derived)?.name===bare.name};return pair(left,right)||pair(right,left)}const NON_SECRET_MEMBERS=new Map([["hash",new Set(["location"])]]);const DEFAULT_NON_SECRET_WORDS=["author","authors","authored","authoring","authorship","hashtag","hashtags"];const DEFAULT_NON_SECRET_TAILS=["count","counts","limit","limits","usage","total","size","length","price","cost","quota","address","addresses","index","rank","percent"];const CRYPTO_DERIVATIONS=new Set(["createHmac","createHash","createSign","pbkdf2Sync","scryptSync","hkdfSync","digest","sign","hmac"]);const RECEIVER_COMPARE_METHODS=new Set(["equals","startsWith","endsWith","localeCompare"]);const BINARY_EQUALITY_FUNCTIONS=new Set(["isEqual","isEqualWith","deepEqual","fastDeepEqual","shallowEqual"]);const SERVER_STATE_REQUEST_PROPERTIES=new Set(["session","user","locals","app","state"]);function unwrapChain(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.ChainExpression?node.expression:node}function memberPropertyName(node){const property=node.property;if(!node.computed){return property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.name:null}return property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof property.value==="string"?property.value:null}function isNonSecretMember(node){const propertyName=memberPropertyName(node);if(propertyName===null)return false;const receivers=NON_SECRET_MEMBERS.get(propertyName.toLowerCase());if(!receivers)return false;const owner=node.object;const ownerName=owner.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.name:owner.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!owner.computed&&owner.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.property.name:"";return receivers.has(ownerName.toLowerCase())}exports.noTimingUnsafeCompare=(0,eslint_devkit_1.createRule)({name:"no-timing-unsafe-compare",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md",description:"Disallow timing-unsafe comparison of secrets",cwe:"CWE-208",cvss:5.9},messages:{timingUnsafeCompare:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timing-unsafe comparison",cwe:"CWE-208",description:"Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.",severity:"HIGH",fix:"Use crypto.timingSafeEqual() for constant-time comparison",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"})},schema:[{type:"object",properties:{secretPatterns:{type:"array",items:{type:"string"},default:DEFAULT_SECRET_PATTERNS,description:"Variable name patterns that indicate secrets"},untrustedSources:{type:"array",items:{type:"string"},default:DEFAULT_UNTRUSTED_SOURCES,description:"Identifier roots treated as attacker-controlled (default: req, request, ctx, event)"},reportUnverifiedComparisons:{type:"boolean",default:false,description:"Report on a secret-looking name alone, without an attacker-controlled operand. Restores the pre-inversion behaviour."},nonSecretWords:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_WORDS],description:"Whole words that mean a secretPatterns match was a collision (default: author, authors, authored, authoring, authorship, hashtag, hashtags). Replaces the list."},nonSecretTails:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_TAILS],description:"Trailing words that make the value a measurement or location rather than a credential (default: count, limit, usage, total, size, length, price, cost, quota, address, index, rank, percent). Replaces the list."}},additionalProperties:false}]},defaultOptions:[{secretPatterns:DEFAULT_SECRET_PATTERNS,nonSecretWords:[...DEFAULT_NON_SECRET_WORDS],nonSecretTails:[...DEFAULT_NON_SECRET_TAILS]}],create(context,[options={}]){const{secretPatterns=DEFAULT_SECRET_PATTERNS,untrustedSources=DEFAULT_UNTRUSTED_SOURCES,reportUnverifiedComparisons=false,nonSecretWords=[...DEFAULT_NON_SECRET_WORDS],nonSecretTails=[...DEFAULT_NON_SECRET_TAILS]}=options;const nonSecretWordSet=new Set(nonSecretWords.map(word=>word.toLowerCase()));const nonSecretTailSet=new Set(nonSecretTails.map(word=>word.toLowerCase()));const sourceCode=context.sourceCode;const readsUntrusted=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const patterns=(0,eslint_devkit_1.compileUserPatterns)(secretPatterns,"i");function nameLooksSecret(name){if(BOOLEAN_PREDICATE_NAME.test(name))return false;const words=(0,names_1.identifierWords)(name);if(words.some(word=>nonSecretWordSet.has(word)))return false;if(words.length>0&&nonSecretTailSet.has(words[words.length-1])){return false}return patterns.some(p=>p.test(name))}function isCryptoDerivation(node){const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isCryptoDerivation(bare);if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return CRYPTO_DERIVATIONS.has(callee.name)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return false;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(CRYPTO_DERIVATIONS.has(callee.property.name))return true;return isCryptoDerivation(callee.object)}function isCryptoSecret(node){if(isCryptoDerivation(node))return true;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,provenance_1.bindingInit)(sourceCode,bare);return init!==void 0&&containsCryptoDerivation(init)}function containsCryptoDerivation(node){if(isCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.expressions.some(expression=>containsCryptoDerivation(expression))}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return containsCryptoDerivation(node.left)||containsCryptoDerivation(node.right)}return false}function isServerDerived(node,depth=0){if(depth>4)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isServerDerived(bare,depth+1);if(node.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression)return true;if(containsCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const propertyName=memberPropertyName(node);if(propertyName!==null&&SERVER_STATE_REQUEST_PROPERTIES.has(propertyName)){return true}return isServerDerived(node.object,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,provenance_1.bindingInit)(sourceCode,node);return init!==void 0&&isServerDerived(init,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=unwrapChain(node.callee);if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;return!readsUntrusted(callee.object)||isServerDerived(callee.object,depth+1)}return false}function isSecretIdentifier(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return nameLooksSecret(node.name)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(isNonSecretMember(node))return false;const propertyName=memberPropertyName(node);if(propertyName!==null){return nameLooksSecret(propertyName)}}return false}function isResolvedConstant(node){if(isSourceConstant(node))return true;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,provenance_1.constLiteralOf)(sourceCode,node)!==void 0}function resolveLocalFunction(callee){const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type==="FunctionName")return def.node;if(def.type!=="Variable")return null;const init=def.node.init;if(!init)return null;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(init);if(bare.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression||bare.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression){return bare}return null}function comparesLengths(node){const isLength=operand=>operand.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&memberPropertyName(operand)==="length";return isLength(node.left)||isLength(node.right)}function equalityWrapperParams(callee){const fn=resolveLocalFunction(callee);if(!fn||fn.params.length<2)return null;const paramIndex=new Map;fn.params.forEach((param,index)=>{if(param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)paramIndex.set(param.name,index)});let found=null;const visit=node=>{if(found||!node||typeof node.type!=="string")return;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){const isEquality=node.operator==="==="||node.operator==="!=="||node.operator==="=="||node.operator==="!=";if(isEquality&&!comparesLengths(node)){const leftIndex=paramIndex.get(memberRoot(node.left)?.name??"");const rightIndex=paramIndex.get(memberRoot(node.right)?.name??"");if(leftIndex!==void 0&&rightIndex!==void 0&&leftIndex!==rightIndex){found=[leftIndex,rightIndex];return}}}for(const[key,value]of Object.entries(node)){if(key==="parent")continue;if(Array.isArray(value)){for(const entry of value)visit(entry)}else if(value&&typeof value==="object"&&"type"in value){visit(value)}}};visit(fn.body);return found}function comparisonOperands(node){const args=node.arguments;if(args.some(argument=>argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)){return null}const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(BINARY_EQUALITY_FUNCTIONS.has(callee.name)&&args.length===2){return[args[0],args[1]]}const wrapped=equalityWrapperParams(callee);if(!wrapped)return null;const[leftIndex,rightIndex]=wrapped;const leftArg=args[leftIndex];const rightArg=args[rightIndex];if(!leftArg||!rightArg)return null;return[leftArg,rightArg]}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return null;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const method=callee.property.name;if(method==="compare"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&args.length===2){return[args[0],args[1]]}if(BINARY_EQUALITY_FUNCTIONS.has(method)&&args.length===2){return[args[0],args[1]]}if(RECEIVER_COMPARE_METHODS.has(method)&&args.length===1){return[callee.object,args[0]]}return null}function checkComparison(node,rawLeft,rawRight){const left=unwrapChain(rawLeft);const right=unwrapChain(rawRight);if(isResolvedConstant(left)||isResolvedConstant(right)){return}if(isNamedConstant(left)||isNamedConstant(right)){return}if(isSelfComparison(left,right)){return}const leftIsSecret=isSecretIdentifier(left)||isCryptoSecret(left);const rightIsSecret=isSecretIdentifier(right)||isCryptoSecret(right);if(leftIsSecret||rightIsSecret){if(!reportUnverifiedComparisons){const leftUntrusted=readsUntrusted(left);const rightUntrusted=readsUntrusted(right);if(leftUntrusted===rightUntrusted){if(!leftUntrusted)return;if(!isServerDerived(left)&&!isServerDerived(right))return}}context.report({node,messageId:"timingUnsafeCompare"})}}function checkBinaryExpression(node){if(node.operator!=="==="&&node.operator!=="=="&&node.operator!=="!=="&&node.operator!=="!="){return}checkComparison(node,node.left,node.right)}function checkCallExpression(node){const operands=comparisonOperands(node);if(!operands)return;checkComparison(node,operands[0],operands[1])}return{BinaryExpression:checkBinaryExpression,CallExpression:checkCallExpression}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimingUnsafeCompare=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const names_1=require("../../utils/names");const DEFAULT_SECRET_PATTERNS=["token","secret","password","hash","signature","mac","hmac","digest","apiKey","api_key","api-key","auth","credential","bearer","jwt","csrf","nonce","ssn","social_security","social-security","pii","private_key","private-key","privateKey","access_token","access-token","accessToken","refresh_token","refresh-token","refreshToken","session_id","session-id","sessionId","auth_token","auth-token","authToken","encryption_key","encryption-key","encryptionKey"];function isSourceConstant(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.name==="undefined")return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral)return node.expressions.length===0;return false}const BOOLEAN_PREDICATE_NAME=/^(?:is|has|should|can|did|was|will|does)[A-Z]/;const NAMED_CONSTANT=/^[A-Z][A-Z0-9_]*$/;const DEFAULT_UNTRUSTED_SOURCES=["req","request","ctx","event"];const NAMESPACE_NAME=/^(?:[A-Z][A-Z0-9_]*|[A-Z][a-zA-Z0-9]*)$/;function isNamedConstant(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.computed)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return NAMESPACE_NAME.test(node.object.name)&&NAMED_CONSTANT.test(node.property.name)}function memberRoot(node){let current=node;for(;;){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object;continue}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){current=current.callee;continue}break}return current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?current:null}function isSelfComparison(left,right){const pair=(bare,derived)=>{if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(derived.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return memberRoot(derived)?.name===bare.name};return pair(left,right)||pair(right,left)}const NON_SECRET_MEMBERS=new Map([["hash",new Set(["location"])]]);const DEFAULT_NON_SECRET_WORDS=["author","authors","authored","authoring","authorship","hashtag","hashtags"];const DEFAULT_NON_SECRET_TAILS=["count","counts","limit","limits","usage","total","size","length","price","cost","quota","address","addresses","index","rank","percent","path","paths","pathname","pathnames","endpoint","endpoints","route","routes","hostname","host","port","origin"];const CRYPTO_DERIVATIONS=new Set(["createHmac","createHash","createSign","pbkdf2Sync","scryptSync","hkdfSync","digest","sign","hmac"]);const RECEIVER_COMPARE_METHODS=new Set(["equals","startsWith","endsWith","localeCompare"]);const BINARY_EQUALITY_FUNCTIONS=new Set(["isEqual","isEqualWith","deepEqual","fastDeepEqual","shallowEqual"]);const SERVER_STATE_REQUEST_PROPERTIES=new Set(["session","user","locals","app","state"]);function unwrapChain(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.ChainExpression?node.expression:node}function memberPropertyName(node){const property=node.property;if(!node.computed){return property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.name:null}return property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof property.value==="string"?property.value:null}function isNonSecretMember(node){const propertyName=memberPropertyName(node);if(propertyName===null)return false;const receivers=NON_SECRET_MEMBERS.get(propertyName.toLowerCase());if(!receivers)return false;const owner=node.object;const ownerName=owner.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.name:owner.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!owner.computed&&owner.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.property.name:"";return receivers.has(ownerName.toLowerCase())}exports.noTimingUnsafeCompare=(0,eslint_devkit_1.createRule)({name:"no-timing-unsafe-compare",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md",description:"Disallow timing-unsafe comparison of secrets",cwe:"CWE-208",cvss:5.9},messages:{timingUnsafeCompare:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timing-unsafe comparison",cwe:"CWE-208",description:"Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.",severity:"HIGH",fix:"Use crypto.timingSafeEqual() for constant-time comparison",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"})},schema:[{type:"object",properties:{secretPatterns:{type:"array",items:{type:"string"},default:DEFAULT_SECRET_PATTERNS,description:"Variable name patterns that indicate secrets"},untrustedSources:{type:"array",items:{type:"string"},default:DEFAULT_UNTRUSTED_SOURCES,description:"Identifier roots treated as attacker-controlled (default: req, request, ctx, event)"},reportUnverifiedComparisons:{type:"boolean",default:false,description:"Report on a secret-looking name alone, without an attacker-controlled operand. Restores the pre-inversion behaviour."},nonSecretWords:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_WORDS],description:`Whole words that mean a secretPatterns match was a collision (default: ${DEFAULT_NON_SECRET_WORDS.join(", ")}). Replaces the list.`},nonSecretTails:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_TAILS],description:`Trailing words that make the value a measurement or location rather than a credential (default: ${DEFAULT_NON_SECRET_TAILS.join(", ")}). Replaces the list.`}},additionalProperties:false}]},defaultOptions:[{secretPatterns:DEFAULT_SECRET_PATTERNS,nonSecretWords:[...DEFAULT_NON_SECRET_WORDS],nonSecretTails:[...DEFAULT_NON_SECRET_TAILS]}],create(context,[options={}]){const{secretPatterns=DEFAULT_SECRET_PATTERNS,untrustedSources=DEFAULT_UNTRUSTED_SOURCES,reportUnverifiedComparisons=false,nonSecretWords=[...DEFAULT_NON_SECRET_WORDS],nonSecretTails=[...DEFAULT_NON_SECRET_TAILS]}=options;const nonSecretWordSet=new Set(nonSecretWords.map(word=>word.toLowerCase()));const nonSecretTailSet=new Set(nonSecretTails.map(word=>word.toLowerCase()));const sourceCode=context.sourceCode;const readsUntrusted=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const patterns=(0,eslint_devkit_1.compileUserPatterns)(secretPatterns,"i");function nameLooksSecret(name){if(BOOLEAN_PREDICATE_NAME.test(name))return false;const words=(0,names_1.identifierWords)(name);if(words.some(word=>nonSecretWordSet.has(word)))return false;if(words.length>0&&nonSecretTailSet.has(words[words.length-1])){return false}return patterns.some(p=>p.test(name))}function isCryptoDerivation(node){const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isCryptoDerivation(bare);if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return CRYPTO_DERIVATIONS.has(callee.name)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return false;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(CRYPTO_DERIVATIONS.has(callee.property.name))return true;return isCryptoDerivation(callee.object)}function isCryptoSecret(node){if(isCryptoDerivation(node))return true;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,provenance_1.bindingInit)(sourceCode,bare);return init!==void 0&&containsCryptoDerivation(init)}function containsCryptoDerivation(node){if(isCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.expressions.some(expression=>containsCryptoDerivation(expression))}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return containsCryptoDerivation(node.left)||containsCryptoDerivation(node.right)}return false}function isServerDerived(node,depth=0){if(depth>4)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isServerDerived(bare,depth+1);if(node.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression)return true;if(containsCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const propertyName=memberPropertyName(node);if(propertyName!==null&&SERVER_STATE_REQUEST_PROPERTIES.has(propertyName)){return true}return isServerDerived(node.object,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,provenance_1.bindingInit)(sourceCode,node);return init!==void 0&&isServerDerived(init,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=unwrapChain(node.callee);if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;return!readsUntrusted(callee.object)||isServerDerived(callee.object,depth+1)}return false}function isSecretIdentifier(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return nameLooksSecret(node.name)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(isNonSecretMember(node))return false;const propertyName=memberPropertyName(node);if(propertyName!==null){return nameLooksSecret(propertyName)}}return false}function isResolvedConstant(node){if(isSourceConstant(node))return true;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,provenance_1.constLiteralOf)(sourceCode,node)!==void 0}function resolveLocalFunction(callee){const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type==="FunctionName")return def.node;if(def.type!=="Variable")return null;const init=def.node.init;if(!init)return null;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(init);if(bare.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression||bare.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression){return bare}return null}function comparesLengths(node){const isLength=operand=>operand.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&memberPropertyName(operand)==="length";return isLength(node.left)||isLength(node.right)}function equalityWrapperParams(callee){const fn=resolveLocalFunction(callee);if(!fn||fn.params.length<2)return null;const paramIndex=new Map;fn.params.forEach((param,index)=>{if(param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)paramIndex.set(param.name,index)});let found=null;const visit=node=>{if(found||!node||typeof node.type!=="string")return;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){const isEquality=node.operator==="==="||node.operator==="!=="||node.operator==="=="||node.operator==="!=";if(isEquality&&!comparesLengths(node)){const leftIndex=paramIndex.get(memberRoot(node.left)?.name??"");const rightIndex=paramIndex.get(memberRoot(node.right)?.name??"");if(leftIndex!==void 0&&rightIndex!==void 0&&leftIndex!==rightIndex){found=[leftIndex,rightIndex];return}}}for(const[key,value]of Object.entries(node)){if(key==="parent")continue;if(Array.isArray(value)){for(const entry of value)visit(entry)}else if(value&&typeof value==="object"&&"type"in value){visit(value)}}};visit(fn.body);return found}function comparisonOperands(node){const args=node.arguments;if(args.some(argument=>argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)){return null}const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(BINARY_EQUALITY_FUNCTIONS.has(callee.name)&&args.length===2){return[args[0],args[1]]}const wrapped=equalityWrapperParams(callee);if(!wrapped)return null;const[leftIndex,rightIndex]=wrapped;const leftArg=args[leftIndex];const rightArg=args[rightIndex];if(!leftArg||!rightArg)return null;return[leftArg,rightArg]}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return null;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const method=callee.property.name;if(method==="compare"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&args.length===2){return[args[0],args[1]]}if(BINARY_EQUALITY_FUNCTIONS.has(method)&&args.length===2){return[args[0],args[1]]}if(RECEIVER_COMPARE_METHODS.has(method)&&args.length===1){return[callee.object,args[0]]}return null}function checkComparison(node,rawLeft,rawRight){const left=unwrapChain(rawLeft);const right=unwrapChain(rawRight);if(isResolvedConstant(left)||isResolvedConstant(right)){return}if(isNamedConstant(left)||isNamedConstant(right)){return}if(isSelfComparison(left,right)){return}const leftIsSecret=isSecretIdentifier(left)||isCryptoSecret(left);const rightIsSecret=isSecretIdentifier(right)||isCryptoSecret(right);if(leftIsSecret||rightIsSecret){if(!reportUnverifiedComparisons){const leftUntrusted=readsUntrusted(left);const rightUntrusted=readsUntrusted(right);if(leftUntrusted===rightUntrusted){if(!leftUntrusted)return;if(!isServerDerived(left)&&!isServerDerived(right))return}}context.report({node,messageId:"timingUnsafeCompare"})}}function checkBinaryExpression(node){if(node.operator!=="==="&&node.operator!=="=="&&node.operator!=="!=="&&node.operator!=="!="){return}checkComparison(node,node.left,node.right)}function checkCallExpression(node){const operands=comparisonOperands(node);if(!operands)return;checkComparison(node,operands[0],operands[1])}return{BinaryExpression:checkBinaryExpression,CallExpression:checkCallExpression}}});
|
|
@@ -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"];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"];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 filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);const nonCryptoNames=new Set(nonCryptographicNames.map(normalizeName));function isNonCryptographicUse(node){const name=assignedName(node);return name!==null&&nonCryptoNames.has(normalizeName(name))}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}const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}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&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.property.name==="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"){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"];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 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&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.property.name==="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"){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}}});
|