eslint-plugin-node-security 5.2.1 → 5.2.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.2.1",
3
+ "version": "5.2.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.17.1"
82
+ "@interlace/eslint-devkit": "^1.17.2"
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.2.1"},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
+ "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.2.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.noBufferOverread=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const provenance_1=require("../../utils/provenance");const DEFAULT_BUFFER_METHODS=["readUInt8","readInt8","readUInt16LE","readUInt16BE","readInt16LE","readInt16BE","readUInt32LE","readUInt32BE","readInt32LE","readInt32BE","readBigUInt64LE","readBigUInt64BE","readBigInt64LE","readBigInt64BE","readFloatLE","readFloatBE","readDoubleLE","readDoubleBE","readUIntLE","readUIntBE","readIntLE","readIntBE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","subarray","copy"];const DEFAULT_UNTRUSTED_SOURCES=["req","request","event","ctx","context"];const DEFAULT_BUFFER_PARAMETER_NAMES=["buf","buffer","bytes"];function isBufferReturningRead(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;const name=callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.name:callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property.name:null;return name==="readFileSync"&&node.arguments.length===1}const VIEW_METHODS=new Set(["slice","subarray"]);exports.noBufferOverread=(0,eslint_devkit_1.createRule)({name:"no-buffer-overread",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-buffer-overread.md",description:"Detects buffer access beyond bounds",cwe:"CWE-126"},messages:{unsafeBufferAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Access",cwe:"CWE-126",description:"Buffer accessed without bounds validation",severity:"HIGH",fix:"Add bounds check before buffer access",documentationLink:"https://nodejs.org/api/buffer.html"}),missingBoundsCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Bounds Check",cwe:"CWE-126",description:"Buffer operation missing bounds validation",severity:"MEDIUM",fix:"Validate indices before buffer operations",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),negativeBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Negative Buffer Index",cwe:"CWE-126",description:"Negative index used for buffer access",severity:"MEDIUM",fix:"Ensure buffer indices are non-negative",documentationLink:"https://nodejs.org/api/buffer.html"}),userControlledBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User Controlled Buffer Index",cwe:"CWE-126",description:"Buffer accessed with user-controlled index",severity:"HIGH",fix:"Validate user input before using as buffer index",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),unsafeBufferSlice:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Slice",cwe:"CWE-126",description:"Buffer slice with unvalidated indices",severity:"MEDIUM",fix:"Validate slice start/end indices",documentationLink:"https://nodejs.org/api/buffer.html#bufslicestart-end"})},schema:[{type:"object",properties:{bufferMethods:{type:"array",items:{type:"string"},default:[...DEFAULT_BUFFER_METHODS],description:"Buffer read/write methods checked for bounds"},boundsCheckFunctions:{type:"array",items:{type:"string"},default:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],description:"Function names that count as a bounds check"},bufferTypes:{type:"array",items:{type:"string"},default:["Buffer","Uint8Array","ArrayBuffer","DataView"],description:"Constructor names treated as buffer types"},trustedSanitizers:{type:"array",items:{type:"string"},default:[],description:"Additional function names to consider as buffer index validators"},trustedAnnotations:{type:"array",items:{type:"string"},default:[],description:"Additional JSDoc annotations to consider as safe markers"},untrustedSources:{type:"array",items:{type:"string"},default:[...DEFAULT_UNTRUSTED_SOURCES],description:"Identifier roots treated as carrying an inbound request (default: req, request, event, ctx, context). Replaces the list; [] disables root-based taint."},bufferParameterNames:{type:"array",items:{type:"string"},default:[...DEFAULT_BUFFER_PARAMETER_NAMES],description:"Parameter spellings treated as a Buffer when the type is not otherwise visible (default: buf, buffer, bytes). Whole-name match, never substring. Replaces the list; [] drops the convention."},strictMode:{type:"boolean",default:false,description:"Disable all false positive detection (strict mode)"},reportUnvalidatedIndices:{type:"boolean",default:false,description:"Report every index that cannot be proven validated. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{bufferMethods:[...DEFAULT_BUFFER_METHODS],boundsCheckFunctions:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes:["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false,untrustedSources:[...DEFAULT_UNTRUSTED_SOURCES],bufferParameterNames:[...DEFAULT_BUFFER_PARAMETER_NAMES]}],create(context){const options=context.options[0]||{};const{bufferMethods=[...DEFAULT_BUFFER_METHODS],boundsCheckFunctions=["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes=["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers=[],trustedAnnotations=[],strictMode=false,reportUnvalidatedIndices=false,untrustedSources=[...DEFAULT_UNTRUSTED_SOURCES],bufferParameterNames=[...DEFAULT_BUFFER_PARAMETER_NAMES]}=options;const bufferParameterSet=new Set(bufferParameterNames.map(name=>name.toLowerCase()));const sourceCode=context.sourceCode;const filename=context.filename;const safetyChecker=(0,eslint_devkit_1.createSafetyChecker)({trustedSanitizers,trustedAnnotations,trustedOrmPatterns:[],strictMode});const passedTrustedSanitizer=indexNode=>{if(trustedSanitizers.length===0)return false;if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,provenance_1.bindingInit)(sourceCode,indexNode);if(init===void 0||init.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const calleeNode=init.callee;if(calleeNode.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return trustedSanitizers.includes(calleeNode.name)}return calleeNode.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&calleeNode.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&trustedSanitizers.includes(calleeNode.property.name)};const readsTaintSource=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const bufferVars=new Set;const addBufferVar=id=>{const variable=(0,provenance_1.findVariable)(sourceCode,id);if(variable)bufferVars.add(variable)};const isBufferType=node=>{const variable=(0,provenance_1.findVariable)(sourceCode,node);if(variable&&bufferVars.has(variable))return true;if(variable!==null&&variable.defs.length>0){return variable.defs[0].type==="Parameter"&&bufferParameterSet.has(node.name.toLowerCase())}return bufferParameterSet.has(node.name.toLowerCase())};const isWriteTarget=node=>{const parent=node.parent;if(!parent)return false;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression)return parent.left===node;return parent.type===eslint_devkit_1.AST_NODE_TYPES.UpdateExpression};const isLoopBounded=indexNode=>{if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=indexNode.name;let current=indexNode.parent;while(current){const test=current.type===eslint_devkit_1.AST_NODE_TYPES.ForStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.WhileStatement?current.test:null;if(test&&test.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&(test.operator==="<"||test.operator==="<=")&&test.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&test.left.name===name){return true}current=current.parent}return false};const isUserControlledIndex=indexNode=>readsTaintSource(indexNode);const isIndexValidated=indexNode=>{if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Literal){return typeof indexNode.value==="number"&&indexNode.value>=0}if(isBoundsCheckCall(indexNode))return true;if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const variable=(0,provenance_1.findVariable)(sourceCode,indexNode);if(variable!==null&&variable.defs[0]?.type==="Parameter")return true;const init=(0,provenance_1.bindingInit)(sourceCode,indexNode);return init!==void 0&&isBoundsCheckCall(init)};const isBoundsCheckCall=node=>{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 boundsCheckFunctions.includes(callee.name)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed||callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return false}return callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Math"&&(callee.property.name==="min"||callee.property.name==="max")||boundsCheckFunctions.includes(callee.property.name)};const hasBoundsCheck=(buffer,indexNode)=>{const guards=[];collectComparisons(enclosingBody(indexNode),guards);return guards.some(test=>mentions(test.left,indexNode)&&readsLengthOf(test.right,buffer)||mentions(test.right,indexNode)&&readsLengthOf(test.left,buffer))};const sameBinding=(a,b)=>{const left=(0,provenance_1.findVariable)(sourceCode,a);const right=(0,provenance_1.findVariable)(sourceCode,b);if(left===null&&right===null)return a.name===b.name;return left===right};const enclosingBody=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||current.type===eslint_devkit_1.AST_NODE_TYPES.Program){return current}current=current.parent}return node};const collectComparisons=(node,out)=>{if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&["<","<=",">",">="].includes(node.operator)){out.push(node)}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){collectComparisons(child,out)}}}};const mentions=(node,indexNode)=>{if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const walk=current=>{if(current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return sameBinding(current,indexNode)}if(current.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return walk(current.left)||walk(current.right)}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){return current.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&walk(argument))}return false};return walk(node)};const readsLengthOf=(node,buffer)=>node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.property.name==="length"&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&sameBinding(node.object,buffer);const couldBeNegative=indexNode=>{const value=constantNumber(indexNode,0);return value!==null&&value<0};const constantNumber=(node,depth)=>{if(depth>6)return null;if(node.type===eslint_devkit_1.AST_NODE_TYPES.UnaryExpression){const inner=constantNumber(node.argument,depth+1);if(inner===null)return null;return node.operator==="-"?-inner:node.operator==="+"?inner:null}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){if(node.operator!=="-"&&node.operator!=="+")return null;const left=constantNumber(node.left,depth+1);const right=constantNumber(node.right,depth+1);if(left===null||right===null)return null;return node.operator==="-"?left-right:left+right}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,const_value_1.constInitializerOf)(sourceCode,node);return init===null?null:constantNumber(init,depth+1)}const resolved=(0,const_value_1.resolveConstant)(sourceCode,node);return resolved!==null&&typeof resolved.value==="number"?resolved.value:null};return{VariableDeclarator(node){if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init){if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferTypes.includes(node.init.callee.name)){addBufferVar(node.id)}if(isBufferReturningRead(node.init)){addBufferVar(node.id)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.init.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init.callee.object.name==="Buffer"&&node.init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&["from","alloc","allocUnsafe"].includes(node.init.callee.property.name)){addBufferVar(node.id)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=node.init.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object)){addBufferVar(node.id)}}}},MemberExpression(node){if(node.computed&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const buffer=node.object;const indexNode=node.property;if(isWriteTarget(node))return;if(isLoopBounded(indexNode))return;if(isBufferType(node.object)){if(couldBeNegative(indexNode)){context.report({node,messageId:"negativeBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}if(isUserControlledIndex(indexNode)&&!isIndexValidated(indexNode)){if(!hasBoundsCheck(buffer,indexNode)){if(safetyChecker.isSafe(node,context)){return}if(passedTrustedSanitizer(indexNode)){return}context.report({node,messageId:"userControlledBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}}if(reportUnvalidatedIndices&&!hasBoundsCheck(buffer,indexNode)&&!isIndexValidated(indexNode)){if(safetyChecker.isSafe(node,context)){return}if(passedTrustedSanitizer(indexNode)){return}context.report({node,messageId:"unsafeBufferAccess",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(node.property.type==="Identifier"&&bufferMethods.includes(node.property.name)&&node.object.type==="Identifier"&&isBufferType(node.object)){}},CallExpression(node){const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&VIEW_METHODS.has(callee.property.name)&&callee.object.type==="Identifier"&&isBufferType(callee.object)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)||passedTrustedSanitizer(arg)){continue}if(hasBoundsCheck(callee.object,arg)){continue}context.report({node:arg,messageId:"unsafeBufferSlice",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&!VIEW_METHODS.has(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)||passedTrustedSanitizer(arg)){continue}if(hasBoundsCheck(callee.object,arg)){continue}context.report({node:arg,messageId:"missingBoundsCheck",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}},BinaryExpression(node){const leftText=sourceCode.getText(node.left);const rightText=sourceCode.getText(node.right);if(leftText.includes(".length")||rightText.includes(".length")){}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noBufferOverread=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const provenance_1=require("../../utils/provenance");const DEFAULT_BUFFER_METHODS=["readUInt8","readInt8","readUInt16LE","readUInt16BE","readInt16LE","readInt16BE","readUInt32LE","readUInt32BE","readInt32LE","readInt32BE","readBigUInt64LE","readBigUInt64BE","readBigInt64LE","readBigInt64BE","readFloatLE","readFloatBE","readDoubleLE","readDoubleBE","readUIntLE","readUIntBE","readIntLE","readIntBE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","subarray","copy"];const DEFAULT_UNTRUSTED_SOURCES=["req","request","event","ctx","context"];const DEFAULT_BUFFER_PARAMETER_NAMES=["buf","buffer","bytes"];function isBufferReturningRead(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;const name=callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.name:callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property.name:null;return name==="readFileSync"&&node.arguments.length===1}const VIEW_METHODS=new Set(["slice","subarray"]);exports.noBufferOverread=(0,eslint_devkit_1.createRule)({name:"no-buffer-overread",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-buffer-overread.md",description:"Detects buffer access beyond bounds",cwe:"CWE-126"},messages:{boundsCheckDisabled:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Buffer bounds check disabled",cwe:"CWE-125",owasp:"A06:2021",cvss:7.5,description:"The deprecated noAssert argument is true, which turns off the bounds check on this read. Past the end of the buffer it returns whatever memory follows instead of throwing.",severity:"HIGH",compliance:["SOC2","PCI-DSS","ISO27001"],fix:"Drop the noAssert argument and let the read throw on an out-of-range offset.",documentationLink:"https://cwe.mitre.org/data/definitions/125.html"}),unsafeBufferAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Access",cwe:"CWE-126",description:"Buffer accessed without bounds validation",severity:"HIGH",fix:"Add bounds check before buffer access",documentationLink:"https://nodejs.org/api/buffer.html"}),missingBoundsCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Bounds Check",cwe:"CWE-126",description:"Buffer operation missing bounds validation",severity:"MEDIUM",fix:"Validate indices before buffer operations",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),negativeBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Negative Buffer Index",cwe:"CWE-126",description:"Negative index used for buffer access",severity:"MEDIUM",fix:"Ensure buffer indices are non-negative",documentationLink:"https://nodejs.org/api/buffer.html"}),userControlledBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User Controlled Buffer Index",cwe:"CWE-126",description:"Buffer accessed with user-controlled index",severity:"HIGH",fix:"Validate user input before using as buffer index",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),unsafeBufferSlice:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Slice",cwe:"CWE-126",description:"Buffer slice with unvalidated indices",severity:"MEDIUM",fix:"Validate slice start/end indices",documentationLink:"https://nodejs.org/api/buffer.html#bufslicestart-end"})},schema:[{type:"object",properties:{bufferMethods:{type:"array",items:{type:"string"},default:[...DEFAULT_BUFFER_METHODS],description:"Buffer read/write methods checked for bounds"},boundsCheckFunctions:{type:"array",items:{type:"string"},default:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],description:"Function names that count as a bounds check"},bufferTypes:{type:"array",items:{type:"string"},default:["Buffer","Uint8Array","ArrayBuffer","DataView"],description:"Constructor names treated as buffer types"},trustedSanitizers:{type:"array",items:{type:"string"},default:[],description:"Additional function names to consider as buffer index validators"},trustedAnnotations:{type:"array",items:{type:"string"},default:[],description:"Additional JSDoc annotations to consider as safe markers"},untrustedSources:{type:"array",items:{type:"string"},default:[...DEFAULT_UNTRUSTED_SOURCES],description:"Identifier roots treated as carrying an inbound request (default: req, request, event, ctx, context). Replaces the list; [] disables root-based taint."},bufferParameterNames:{type:"array",items:{type:"string"},default:[...DEFAULT_BUFFER_PARAMETER_NAMES],description:"Parameter spellings treated as a Buffer when the type is not otherwise visible (default: buf, buffer, bytes). Whole-name match, never substring. Replaces the list; [] drops the convention."},strictMode:{type:"boolean",default:false,description:"Disable all false positive detection (strict mode)"},reportUnvalidatedIndices:{type:"boolean",default:false,description:"Report every index that cannot be proven validated. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{bufferMethods:[...DEFAULT_BUFFER_METHODS],boundsCheckFunctions:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes:["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false,untrustedSources:[...DEFAULT_UNTRUSTED_SOURCES],bufferParameterNames:[...DEFAULT_BUFFER_PARAMETER_NAMES]}],create(context){const options=context.options[0]||{};const{bufferMethods=[...DEFAULT_BUFFER_METHODS],boundsCheckFunctions=["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes=["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers=[],trustedAnnotations=[],strictMode=false,reportUnvalidatedIndices=false,untrustedSources=[...DEFAULT_UNTRUSTED_SOURCES],bufferParameterNames=[...DEFAULT_BUFFER_PARAMETER_NAMES]}=options;const bufferParameterSet=new Set(bufferParameterNames.map(name=>name.toLowerCase()));const sourceCode=context.sourceCode;const filename=context.filename;const safetyChecker=(0,eslint_devkit_1.createSafetyChecker)({trustedSanitizers,trustedAnnotations,trustedOrmPatterns:[],strictMode});const passedTrustedSanitizer=indexNode=>{if(trustedSanitizers.length===0)return false;if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,provenance_1.bindingInit)(sourceCode,indexNode);if(init===void 0||init.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const calleeNode=init.callee;if(calleeNode.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return trustedSanitizers.includes(calleeNode.name)}return calleeNode.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&calleeNode.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&trustedSanitizers.includes(calleeNode.property.name)};const readsTaintSource=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const bufferVars=new Set;const addBufferVar=id=>{const variable=(0,provenance_1.findVariable)(sourceCode,id);if(variable)bufferVars.add(variable)};const isBufferType=node=>{const variable=(0,provenance_1.findVariable)(sourceCode,node);if(variable&&bufferVars.has(variable))return true;if(variable!==null&&variable.defs.length>0){return variable.defs[0].type==="Parameter"&&bufferParameterSet.has(node.name.toLowerCase())}return bufferParameterSet.has(node.name.toLowerCase())};const isWriteTarget=node=>{const parent=node.parent;if(!parent)return false;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression)return parent.left===node;return parent.type===eslint_devkit_1.AST_NODE_TYPES.UpdateExpression};const isLoopBounded=indexNode=>{if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=indexNode.name;let current=indexNode.parent;while(current){const test=current.type===eslint_devkit_1.AST_NODE_TYPES.ForStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.WhileStatement?current.test:null;if(test&&test.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&(test.operator==="<"||test.operator==="<=")&&test.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&test.left.name===name){return true}current=current.parent}return false};const isUserControlledIndex=indexNode=>readsTaintSource(indexNode);const isIndexValidated=indexNode=>{if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Literal){return typeof indexNode.value==="number"&&indexNode.value>=0}if(isBoundsCheckCall(indexNode))return true;if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const variable=(0,provenance_1.findVariable)(sourceCode,indexNode);if(variable!==null&&variable.defs[0]?.type==="Parameter")return true;const init=(0,provenance_1.bindingInit)(sourceCode,indexNode);return init!==void 0&&isBoundsCheckCall(init)};const isBoundsCheckCall=node=>{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 boundsCheckFunctions.includes(callee.name)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed||callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return false}return callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Math"&&(callee.property.name==="min"||callee.property.name==="max")||boundsCheckFunctions.includes(callee.property.name)};const hasBoundsCheck=(buffer,indexNode)=>{const guards=[];collectComparisons(enclosingBody(indexNode),guards);return guards.some(test=>mentions(test.left,indexNode)&&readsLengthOf(test.right,buffer)||mentions(test.right,indexNode)&&readsLengthOf(test.left,buffer))};const sameBinding=(a,b)=>{const left=(0,provenance_1.findVariable)(sourceCode,a);const right=(0,provenance_1.findVariable)(sourceCode,b);if(left===null&&right===null)return a.name===b.name;return left===right};const enclosingBody=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||current.type===eslint_devkit_1.AST_NODE_TYPES.Program){return current}current=current.parent}return node};const collectComparisons=(node,out)=>{if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&["<","<=",">",">="].includes(node.operator)){out.push(node)}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){collectComparisons(child,out)}}}};const mentions=(node,indexNode)=>{if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const walk=current=>{if(current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return sameBinding(current,indexNode)}if(current.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return walk(current.left)||walk(current.right)}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){return current.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&walk(argument))}return false};return walk(node)};const readsLengthOf=(node,buffer)=>node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.property.name==="length"&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&sameBinding(node.object,buffer);const couldBeNegative=indexNode=>{const value=constantNumber(indexNode,0);return value!==null&&value<0};const constantNumber=(node,depth)=>{if(depth>6)return null;if(node.type===eslint_devkit_1.AST_NODE_TYPES.UnaryExpression){const inner=constantNumber(node.argument,depth+1);if(inner===null)return null;return node.operator==="-"?-inner:node.operator==="+"?inner:null}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){if(node.operator!=="-"&&node.operator!=="+")return null;const left=constantNumber(node.left,depth+1);const right=constantNumber(node.right,depth+1);if(left===null||right===null)return null;return node.operator==="-"?left-right:left+right}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,const_value_1.constInitializerOf)(sourceCode,node);return init===null?null:constantNumber(init,depth+1)}const resolved=(0,const_value_1.resolveConstant)(sourceCode,node);return resolved!==null&&typeof resolved.value==="number"?resolved.value:null};return{'CallExpression[callee.type="MemberExpression"][callee.computed=false][callee.property.type="Identifier"]'(node){const callee=node.callee;const method=callee.property.name;if(!/^(?:read|write)[A-Z]/.test(method))return;const flag=node.arguments.at(-1);if(node.arguments.length>=2&&flag?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&flag.value===true){context.report({node:flag,messageId:"boundsCheckDisabled"})}},VariableDeclarator(node){if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init){if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferTypes.includes(node.init.callee.name)){addBufferVar(node.id)}if(isBufferReturningRead(node.init)){addBufferVar(node.id)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.init.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init.callee.object.name==="Buffer"&&node.init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&["from","alloc","allocUnsafe"].includes(node.init.callee.property.name)){addBufferVar(node.id)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=node.init.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object)){addBufferVar(node.id)}}}},MemberExpression(node){if(node.computed&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const buffer=node.object;const indexNode=node.property;if(isWriteTarget(node))return;if(isLoopBounded(indexNode))return;if(isBufferType(node.object)){if(couldBeNegative(indexNode)){context.report({node,messageId:"negativeBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}if(isUserControlledIndex(indexNode)&&!isIndexValidated(indexNode)){if(!hasBoundsCheck(buffer,indexNode)){if(safetyChecker.isSafe(node,context)){return}if(passedTrustedSanitizer(indexNode)){return}context.report({node,messageId:"userControlledBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}}if(reportUnvalidatedIndices&&!hasBoundsCheck(buffer,indexNode)&&!isIndexValidated(indexNode)){if(safetyChecker.isSafe(node,context)){return}if(passedTrustedSanitizer(indexNode)){return}context.report({node,messageId:"unsafeBufferAccess",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(node.property.type==="Identifier"&&bufferMethods.includes(node.property.name)&&node.object.type==="Identifier"&&isBufferType(node.object)){}},CallExpression(node){const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&VIEW_METHODS.has(callee.property.name)&&callee.object.type==="Identifier"&&isBufferType(callee.object)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)||passedTrustedSanitizer(arg)){continue}if(hasBoundsCheck(callee.object,arg)){continue}context.report({node:arg,messageId:"unsafeBufferSlice",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&!VIEW_METHODS.has(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)||passedTrustedSanitizer(arg)){continue}if(hasBoundsCheck(callee.object,arg)){continue}context.report({node:arg,messageId:"missingBoundsCheck",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}},BinaryExpression(node){const leftText=sourceCode.getText(node.left);const rightText=sourceCode.getText(node.right);if(leftText.includes(".length")||rightText.includes(".length")){}}}}});
@@ -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","passphrase","mnemonic"];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:true,description:"Allow Math.random() in test files. Default: true"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context,[options={}]){const{allowInTests=true}=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&&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&&current.id){const funcName=current.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))){return true}}if(namesThisValue&&current.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression){if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&current.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&&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}}}}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
+ "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","passphrase","mnemonic"];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 NON_SECURITY_ID_QUALIFIERS=new Set(["request","req","correlation","trace","span","transaction","txn","message","msg","event","job","run","task","batch","element","node","row","record","instance","component","widget","dom","operation","call","invocation","frame","render"]);function isCorrelationIdFactory(name){const words=(0,names_1.identifierWords)(name);if(words[words.length-1]!=="id")return false;if(words.some(word=>CRYPTO_WORD_SET.has(word)))return false;return words.some(word=>NON_SECURITY_ID_QUALIFIERS.has(word))}function functionNameSuggestsCrypto(name){if(!CRYPTO_FUNCTION_PATTERNS.some(pattern=>pattern.test(name)))return false;return!isCorrelationIdFactory(name)}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:7.5,confidence:"medium"},messages:{pseudoRandomBytes:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Non-cryptographic random bytes",cwe:"CWE-338",owasp:"A02:2021",cvss:7.5,description:"crypto.pseudoRandomBytes() is not cryptographically secure. The name is the API's own warning: it was deprecated in Node 4 precisely because callers assumed otherwise.",severity:"HIGH",compliance:["SOC2","PCI-DSS","ISO27001"],fix:"Use crypto.randomBytes(n), or crypto.randomUUID() for identifiers.",documentationLink:"https://cwe.mitre.org/data/definitions/338.html"}),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:true,description:"Allow Math.random() in test files. Default: true"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context,[options={}]){const{allowInTests=true}=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&&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&&current.id){const funcName=current.id.name;if(functionNameSuggestsCrypto(funcName)){return true}}if(namesThisValue&&current.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression){if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&current.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&&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(functionNameSuggestsCrypto(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(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.callee.computed&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.property.name==="pseudoRandomBytes"){context.report({node,messageId:"pseudoRandomBytes"});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.noShellInjection=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const constant_folding_1=require("../../utils/constant-folding");const SHELL_EXEC_FUNCTIONS=new Set(["exec","execSync"]);function isStringConcatOrTemplate(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral&&node.expressions.length>0)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&node.operator==="+"&&(node.left.type===eslint_devkit_1.AST_NODE_TYPES.Literal||node.left.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral||node.left.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression||node.right.type===eslint_devkit_1.AST_NODE_TYPES.Literal||node.right.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral))return true;return false}exports.noShellInjection=(0,eslint_devkit_1.createRule)({name:"no-shell-injection",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-shell-injection.md",description:"Disallow string concatenation or template expressions in shell command arguments (CWE-78)",cwe:"CWE-78",cvss:9.8},messages:{shellInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"OS Command Injection (CWE-78)",cwe:"CWE-78",description:"Shell command built via string concatenation or template literal. An attacker who controls any interpolated value can execute arbitrary OS commands.",severity:"CRITICAL",fix:"Use spawn(cmd, [arg1, arg2]) with separate arguments instead of exec(cmd + args). Never build shell commands via string interpolation.",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html"})},schema:[{type:"object",properties:{requireModuleEvidence:{type:"boolean",default:true,description:"Only report when the callee resolves to child_process. Turning this off restores the pre-2026-08 behaviour, where any callee named exec/execSync/spawn was treated as a shell sink \u2014 which reported better-sqlite3 db.exec(sql) as CWE-78 at CVSS 9.8."}},additionalProperties:false}]},defaultOptions:[{requireModuleEvidence:true}],create(context,[options={}]){const isLiteralConstant=(0,constant_folding_1.makeIsLiteralConstant)(context.sourceCode);const{requireModuleEvidence=true}=options;return{CallExpression(node){const callee=node.callee;let fnName=null;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){fnName=callee.name}else if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){fnName=callee.property.name}if(!fnName||!SHELL_EXEC_FUNCTIONS.has(fnName))return;if(requireModuleEvidence&&!(0,eslint_devkit_1.isModuleBinding)(callee,context.sourceCode.getScope(node),"child_process")){return}const firstArg=node.arguments[0];if(!firstArg||firstArg.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return;if(!isStringConcatOrTemplate(firstArg))return;if(isLiteralConstant(firstArg))return;context.report({node:firstArg,messageId:"shellInjection"})}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noShellInjection=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const constant_folding_1=require("../../utils/constant-folding");const SHELL_EXEC_FUNCTIONS=new Set(["exec","execSync"]);function isStringConcatOrTemplate(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral&&node.expressions.length>0)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&node.operator==="+"&&(node.left.type===eslint_devkit_1.AST_NODE_TYPES.Literal||node.left.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral||node.left.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression||node.right.type===eslint_devkit_1.AST_NODE_TYPES.Literal||node.right.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral))return true;return false}exports.noShellInjection=(0,eslint_devkit_1.createRule)({name:"no-shell-injection",skipTestFiles:true,meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-shell-injection.md",description:"Disallow string concatenation or template expressions in shell command arguments (CWE-78)",cwe:"CWE-78",cvss:9.8},messages:{shellInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"OS Command Injection (CWE-78)",cwe:"CWE-78",description:"Shell command built via string concatenation or template literal. An attacker who controls any interpolated value can execute arbitrary OS commands.",severity:"CRITICAL",fix:"Use spawn(cmd, [arg1, arg2]) with separate arguments instead of exec(cmd + args). Never build shell commands via string interpolation.",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html"})},schema:[{type:"object",properties:{requireModuleEvidence:{type:"boolean",default:true,description:"Only report when the callee resolves to child_process. Turning this off restores the pre-2026-08 behaviour, where any callee named exec/execSync/spawn was treated as a shell sink \u2014 which reported better-sqlite3 db.exec(sql) as CWE-78 at CVSS 9.8."}},additionalProperties:false}]},defaultOptions:[{requireModuleEvidence:true}],create(context,[options={}]){const isLiteralConstant=(0,constant_folding_1.makeIsLiteralConstant)(context.sourceCode);const{requireModuleEvidence=true}=options;return{CallExpression(node){const callee=node.callee;let fnName=null;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){fnName=callee.name}else if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){fnName=callee.property.name}if(!fnName||!SHELL_EXEC_FUNCTIONS.has(fnName))return;if(requireModuleEvidence&&!(0,eslint_devkit_1.isModuleBinding)(callee,context.sourceCode.getScope(node),"child_process")){return}const firstArg=node.arguments[0];if(!firstArg||firstArg.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return;if(!isStringConcatOrTemplate(firstArg))return;if(isLiteralConstant(firstArg))return;context.report({node:firstArg,messageId:"shellInjection"})}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakHashAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const names_1=require("../../utils/names");const const_value_1=require("../../utils/const-value");const WEAK_HASH_PATTERNS=[{pattern:/\bmd5\b/i,name:"MD5",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bmd4\b/i,name:"MD4",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bsha1\b/i,name:"SHA-1",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bripemd\b/i,name:"RIPEMD",alternatives:["SHA-256","SHA-512"],replacement:"sha256"}];const DEFAULT_NON_CRYPTOGRAPHIC_NAMES=["sha","etag","cachekey","cachebuster","thumbprint","x5t"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate","otp","mfa","totp","passphrase","pincode","mnemonic","seedphrase","masterkey","securityanswer","recoverycode","backupcode"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!target.computed&&target.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return target.property.name}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return node.property.name}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&current.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}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakHashAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const names_1=require("../../utils/names");const const_value_1=require("../../utils/const-value");const WEAK_HASH_PATTERNS=[{pattern:/\bmd5\b/i,name:"MD5",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bmd4\b/i,name:"MD4",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bsha1\b/i,name:"SHA-1",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bripemd\b/i,name:"RIPEMD",alternatives:["SHA-256","SHA-512"],replacement:"sha256"}];const DEFAULT_NON_CRYPTOGRAPHIC_NAMES=["sha","etag","cachekey","cachebuster","thumbprint","x5t"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate","otp","mfa","totp","passphrase","pincode","mnemonic","seedphrase","masterkey","securityanswer","recoverycode","backupcode"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!target.computed&&target.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return target.property.name}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return node.property.name}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&current.id){return current.id.name}const owner=current.parent;if(owner?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return owner.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.id.name:null}if((owner?.type===eslint_devkit_1.AST_NODE_TYPES.Property||owner?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition)&&!owner.computed){return expressionName(owner.key)}return null}current=current.parent}return null}function findWeakHash(value,additionalPatterns){for(const pattern of WEAK_HASH_PATTERNS){if(pattern.pattern.test(value)){return pattern}}for(const additionalPattern of additionalPatterns){const regex=new RegExp(`\\b${additionalPattern}\\b`,"i");if(regex.test(value)){return{pattern:regex,name:additionalPattern.toUpperCase(),alternatives:["SHA-256","SHA-512"],replacement:"sha256"}}}return null}exports.noWeakHashAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-hash-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-hash-algorithm.md",description:"Disallow weak hash algorithms (MD5, SHA1, MD4)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakHashAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak hash algorithm",cwe:"CWE-327",description:"Use of weak hash algorithm: {{algorithm}}. {{algorithm}} is cryptographically broken and unsuitable for security purposes.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createHash("{{replacement}}").update(data). If this hash is an identifier rather than a security control \u2014 an EVALSHA key, an ETag, a cache key \u2014 store it under one of the nonCryptographicNames instead.',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-256",description:"Replace with SHA-256 for secure hashing",severity:"LOW",fix:'crypto.createHash("sha256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-512",description:"Replace with SHA-512 for stronger hashing",severity:"LOW",fix:'crypto.createHash("sha512").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha3:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-3",description:"Replace with SHA-3 for latest standard",severity:"LOW",fix:'crypto.createHash("sha3-256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"})},schema:[{type:"object",properties:{additionalWeakAlgorithms:{type:"array",items:{type:"string"},default:[],description:"Additional weak algorithms to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak hashes in test files"},nonCryptographicNames:{type:"array",items:{type:"string"},default:DEFAULT_NON_CRYPTOGRAPHIC_NAMES,description:"Assignment target names that mark a hash as an identifier rather than a security control"},securityUseNames:{type:"array",items:{type:"string"},default:DEFAULT_SECURITY_USE_NAMES,description:"Names that mark a hash as a security control (whole-word matched)"},reportUnclassifiedHashes:{type:"boolean",default:false,description:"Report weak hashes whose purpose cannot be determined. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{additionalWeakAlgorithms:[],allowInTests:false,nonCryptographicNames:DEFAULT_NON_CRYPTOGRAPHIC_NAMES}],create(context,[options={}]){const{additionalWeakAlgorithms=[],allowInTests=false,nonCryptographicNames=DEFAULT_NON_CRYPTOGRAPHIC_NAMES,securityUseNames=DEFAULT_SECURITY_USE_NAMES,reportUnclassifiedHashes=false}=options;const isSecurityUse=(0,names_1.makeNameTest)(securityUseNames);const isNonCryptographicName=(0,names_1.makeNameTest)(nonCryptographicNames.map(normalizeName));const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function isNonCryptographicUse(node){const stored=assignedName(node);if(stored!==null&&isNonCryptographicName(stored))return true;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isNonCryptographicName(enclosing)}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}if(stored!==null)return false;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function isLocalHmacHelper(callee){const scope=context.sourceCode.getScope(callee);for(let current=scope;current;current=current.upper){const variable=current.variables.find(v=>v.name===callee.name);if(!variable)continue;return variable.defs.some(def=>def.type!=="ImportBinding"&&computesHmac(def.node))}return false}function computesHmac(root){const stack=[root];while(stack.length>0){const node=stack.pop();if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&isCreateHmacCall(node)){return true}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){stack.push(child)}}}}return false}function isCreateHmacCall(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.name==="createHmac"}return callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.property.name==="createHmac"}function checkCallExpression(node){if(isTestFile)return;if(isNonCryptographicUse(node))return;if(!reportUnclassifiedHashes&&!hasSecurityUse(node))return;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&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")&&!isLocalHmacHelper(node.callee)){const weakPattern=findWeakHash(funcName,additionalWeakAlgorithms);context.report({node,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return fixer.replaceText(node.callee,"sha256")}return null}}]})}}}function checkHashArgument(node){for(const arg of node.arguments){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,arg);if(resolved===null)continue;const weakPattern=findWeakHash(resolved.value,additionalWeakAlgorithms);if(!weakPattern)continue;const target=resolved.source;context.report({node:arg,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>fixer.replaceText(target,`"sha256"`)},{messageId:"useSha512",fix:fixer=>fixer.replaceText(target,`"sha512"`)},{messageId:"useSha3",fix:fixer=>fixer.replaceText(target,`"sha3-256"`)}]})}}return{CallExpression:checkCallExpression}}});