eslint-plugin-node-security 5.2.1 → 5.2.3
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 +2 -2
- package/src/index.js +1 -1
- package/src/rules/no-buffer-overread/index.js +1 -1
- package/src/rules/no-math-random-crypto/index.js +1 -1
- package/src/rules/no-shell-injection/index.js +1 -1
- package/src/rules/no-timing-unsafe-compare/index.js +1 -1
- package/src/rules/no-toctou-vulnerability/index.js +1 -1
- package/src/rules/no-unsafe-buffer-alloc/index.js +1 -1
- package/src/rules/no-weak-hash-algorithm/index.js +1 -1
- package/src/rules/no-zip-slip/index.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-node-security",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.3",
|
|
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.
|
|
82
|
+
"@interlace/eslint-devkit": "^1.17.3"
|
|
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
|
+
"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&¤t.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){if(current.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const varName=current.id.name;if(nameSuggestsCrypto(varName)){return true}if(depth<MAX_BINDING_HOPS&&usedInCryptoContext(current.id,depth+1)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&¤t.id){const funcName=current.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))){return true}}if(namesThisValue&¤t.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression){if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&¤t.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.left.property.name;if(nameSuggestsCrypto(propName)){return true}}if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(nameSuggestsCrypto(current.left.name)){return true}}}if(namesThisValue&¤t.type===eslint_devkit_1.AST_NODE_TYPES.Property){if(current.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.key.name;if(nameSuggestsCrypto(propName)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){const func=findContainingFunction(current);if(func){if((func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression)&&func.id?.name){const funcName=func.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))||nameSuggestsCrypto(funcName)){return true}}}}child=current;current=current.parent}return false}function findContainingFunction(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){return current}current=current.parent}return null}return{CallExpression(node){if(isTestFile)return;if(isMathRandomCallee(node.callee)){if(isCryptoContext(node)){context.report({node,messageId:"mathRandomCrypto"})}}}}}});
|
|
1
|
+
"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 CSPRNG_CALLS=new Set(["getRandomValues","randomBytes","randomUUID","randomFillSync","randomFill","generateKey","subtle"]);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 isCsprngFallback(node){let scope=node.parent;while(scope!==void 0&&scope.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&&scope.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionExpression&&scope.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&scope.type!==eslint_devkit_1.AST_NODE_TYPES.Program){scope=scope.parent}let found=false;const stack=[scope];while(stack.length>0&&!found){const current=stack.pop();if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&¤t.range[1]<=node.range[0]){const callee=current.callee;const 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:callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.name:"";if(CSPRNG_CALLS.has(name))found=true}for(const key of Object.keys(current)){if(key==="parent")continue;const value=current[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){stack.push(child)}}}}return found}function isMathRandomCallee(callee){if(isMathRandomProperty(callee))return true;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init2=stableDeclarator(callee.object)?.init;if(!init2||init2.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return false;const wanted=callee.property.name;return init2.properties.some(property=>property.type===eslint_devkit_1.AST_NODE_TYPES.Property&&!property.computed&&property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.key.name===wanted&&isMathRandomProperty(property.value))}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const declarator=stableDeclarator(callee);const init=declarator?.init;if(!declarator||!init)return false;if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return isMathRandomProperty(init)}if(declarator.id.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectPattern)return false;if(init.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||init.name!=="Math")return false;return declarator.id.properties.some(property=>{if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property||property.computed)return false;if(property.value.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(property.value.name!==callee.name)return false;const key=property.key;if(key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return key.name==="random";return key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&key.value==="random"})}const MAX_BINDING_HOPS=2;function usedInCryptoContext(id,depth){const variable=(0,provenance_1.findVariable)(sourceCode,id);if(!variable)return false;return variable.references.some(reference=>!reference.init&&isCryptoContext(reference.identifier,depth))}function escapesFunction(fn,cameFrom,passedReturn){if(fn.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&fn.body===cameFrom){return true}return passedReturn}function isCryptoContext(node,depth=0){let child=node;let current=node.parent;let namesThisValue=true;let passedReturn=false;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(!escapesFunction(current,child,passedReturn))namesThisValue=false;passedReturn=false}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement)passedReturn=true;if(namesThisValue&¤t.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){if(current.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const varName=current.id.name;if(nameSuggestsCrypto(varName)){return true}if(depth<MAX_BINDING_HOPS&&usedInCryptoContext(current.id,depth+1)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&¤t.id){const funcName=current.id.name;if(functionNameSuggestsCrypto(funcName)){return true}}if(namesThisValue&¤t.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression){if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&¤t.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.left.property.name;if(nameSuggestsCrypto(propName)){return true}}if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(nameSuggestsCrypto(current.left.name)){return true}}}if(namesThisValue&¤t.type===eslint_devkit_1.AST_NODE_TYPES.Property){if(current.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.key.name;if(nameSuggestsCrypto(propName)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){const func=findContainingFunction(current);if(func){if((func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression)&&func.id?.name){const funcName=func.id.name;if(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)&&!isCsprngFallback(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.noTimingUnsafeCompare=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const names_1=require("../../utils/names");const DEFAULT_SECRET_PATTERNS=["token","secret","password","hash","signature","mac","hmac","digest","apiKey","api_key","api-key","auth","credential","bearer","jwt","csrf","nonce","ssn","social_security","social-security","pii","private_key","private-key","privateKey","access_token","access-token","accessToken","refresh_token","refresh-token","refreshToken","session_id","session-id","sessionId","auth_token","auth-token","authToken","encryption_key","encryption-key","encryptionKey"];function isSourceConstant(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.name==="undefined")return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral)return node.expressions.length===0;return false}const BOOLEAN_PREDICATE_NAME=/^(?:is|has|should|can|did|was|will|does)[A-Z]/;const NAMED_CONSTANT=/^[A-Z][A-Z0-9_]*$/;const DEFAULT_UNTRUSTED_SOURCES=["req","request","ctx","event"];const NAMESPACE_NAME=/^(?:[A-Z][A-Z0-9_]*|[A-Z][a-zA-Z0-9]*)$/;function isNamedConstant(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.computed)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return NAMESPACE_NAME.test(node.object.name)&&NAMED_CONSTANT.test(node.property.name)}function memberRoot(node){let current=node;for(;;){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object;continue}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){current=current.callee;continue}break}return current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?current:null}function isSelfComparison(left,right){const pair=(bare,derived)=>{if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(derived.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return memberRoot(derived)?.name===bare.name};return pair(left,right)||pair(right,left)}const NON_SECRET_MEMBERS=new Map([["hash",new Set(["location"])]]);const DEFAULT_NON_SECRET_WORDS=["author","authors","authored","authoring","authorship","hashtag","hashtags"];const DEFAULT_NON_SECRET_TAILS=["count","counts","limit","limits","usage","total","size","length","price","cost","quota","address","addresses","index","rank","percent","path","paths","pathname","pathnames","endpoint","endpoints","route","routes","hostname","host","port","origin"];const CRYPTO_DERIVATIONS=new Set(["createHmac","createHash","createSign","pbkdf2Sync","scryptSync","hkdfSync","digest","sign","hmac"]);const RECEIVER_COMPARE_METHODS=new Set(["equals","startsWith","endsWith","localeCompare"]);const BINARY_EQUALITY_FUNCTIONS=new Set(["isEqual","isEqualWith","deepEqual","fastDeepEqual","shallowEqual"]);const SERVER_STATE_REQUEST_PROPERTIES=new Set(["session","user","locals","app","state"]);function unwrapChain(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.ChainExpression?node.expression:node}function memberPropertyName(node){const property=node.property;if(!node.computed){return property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.name:null}return property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof property.value==="string"?property.value:null}function isNonSecretMember(node){const propertyName=memberPropertyName(node);if(propertyName===null)return false;const receivers=NON_SECRET_MEMBERS.get(propertyName.toLowerCase());if(!receivers)return false;const owner=node.object;const ownerName=owner.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.name:owner.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!owner.computed&&owner.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.property.name:"";return receivers.has(ownerName.toLowerCase())}exports.noTimingUnsafeCompare=(0,eslint_devkit_1.createRule)({name:"no-timing-unsafe-compare",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md",description:"Disallow timing-unsafe comparison of secrets",cwe:"CWE-208",cvss:5.9},messages:{timingUnsafeCompare:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timing-unsafe comparison",cwe:"CWE-208",description:"Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.",severity:"HIGH",fix:"Use crypto.timingSafeEqual() for constant-time comparison",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"})},schema:[{type:"object",properties:{secretPatterns:{type:"array",items:{type:"string"},default:DEFAULT_SECRET_PATTERNS,description:"Variable name patterns that indicate secrets"},untrustedSources:{type:"array",items:{type:"string"},default:DEFAULT_UNTRUSTED_SOURCES,description:"Identifier roots treated as attacker-controlled (default: req, request, ctx, event)"},reportUnverifiedComparisons:{type:"boolean",default:false,description:"Report on a secret-looking name alone, without an attacker-controlled operand. Restores the pre-inversion behaviour."},nonSecretWords:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_WORDS],description:`Whole words that mean a secretPatterns match was a collision (default: ${DEFAULT_NON_SECRET_WORDS.join(", ")}). Replaces the list.`},nonSecretTails:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_TAILS],description:`Trailing words that make the value a measurement or location rather than a credential (default: ${DEFAULT_NON_SECRET_TAILS.join(", ")}). Replaces the list.`}},additionalProperties:false}]},defaultOptions:[{secretPatterns:DEFAULT_SECRET_PATTERNS,nonSecretWords:[...DEFAULT_NON_SECRET_WORDS],nonSecretTails:[...DEFAULT_NON_SECRET_TAILS]}],create(context,[options={}]){const{secretPatterns=DEFAULT_SECRET_PATTERNS,untrustedSources=DEFAULT_UNTRUSTED_SOURCES,reportUnverifiedComparisons=false,nonSecretWords=[...DEFAULT_NON_SECRET_WORDS],nonSecretTails=[...DEFAULT_NON_SECRET_TAILS]}=options;const nonSecretWordSet=new Set(nonSecretWords.map(word=>word.toLowerCase()));const nonSecretTailSet=new Set(nonSecretTails.map(word=>word.toLowerCase()));const sourceCode=context.sourceCode;const readsUntrusted=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const patterns=(0,eslint_devkit_1.compileUserPatterns)(secretPatterns,"i");function nameLooksSecret(name){if(BOOLEAN_PREDICATE_NAME.test(name))return false;const words=(0,names_1.identifierWords)(name);if(words.some(word=>nonSecretWordSet.has(word)))return false;if(words.length>0&&nonSecretTailSet.has(words[words.length-1])){return false}return patterns.some(p=>p.test(name))}function isCryptoDerivation(node){const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isCryptoDerivation(bare);if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return CRYPTO_DERIVATIONS.has(callee.name)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return false;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(CRYPTO_DERIVATIONS.has(callee.property.name))return true;return isCryptoDerivation(callee.object)}function isCryptoSecret(node){if(isCryptoDerivation(node))return true;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,provenance_1.bindingInit)(sourceCode,bare);return init!==void 0&&containsCryptoDerivation(init)}function containsCryptoDerivation(node){if(isCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.expressions.some(expression=>containsCryptoDerivation(expression))}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return containsCryptoDerivation(node.left)||containsCryptoDerivation(node.right)}return false}function isServerDerived(node,depth=0){if(depth>4)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isServerDerived(bare,depth+1);if(node.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression)return true;if(containsCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const propertyName=memberPropertyName(node);if(propertyName!==null&&SERVER_STATE_REQUEST_PROPERTIES.has(propertyName)){return true}return isServerDerived(node.object,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,provenance_1.bindingInit)(sourceCode,node);return init!==void 0&&isServerDerived(init,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=unwrapChain(node.callee);if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;return!readsUntrusted(callee.object)||isServerDerived(callee.object,depth+1)}return false}function isSecretIdentifier(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return nameLooksSecret(node.name)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(isNonSecretMember(node))return false;const propertyName=memberPropertyName(node);if(propertyName!==null){return nameLooksSecret(propertyName)}}return false}function isResolvedConstant(node){if(isSourceConstant(node))return true;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,provenance_1.constLiteralOf)(sourceCode,node)!==void 0}function resolveLocalFunction(callee){const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type==="FunctionName")return def.node;if(def.type!=="Variable")return null;const init=def.node.init;if(!init)return null;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(init);if(bare.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression||bare.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression){return bare}return null}function comparesLengths(node){const isLength=operand=>operand.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&memberPropertyName(operand)==="length";return isLength(node.left)||isLength(node.right)}function equalityWrapperParams(callee){const fn=resolveLocalFunction(callee);if(!fn||fn.params.length<2)return null;const paramIndex=new Map;fn.params.forEach((param,index)=>{if(param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)paramIndex.set(param.name,index)});let found=null;const visit=node=>{if(found||!node||typeof node.type!=="string")return;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){const isEquality=node.operator==="==="||node.operator==="!=="||node.operator==="=="||node.operator==="!=";if(isEquality&&!comparesLengths(node)){const leftIndex=paramIndex.get(memberRoot(node.left)?.name??"");const rightIndex=paramIndex.get(memberRoot(node.right)?.name??"");if(leftIndex!==void 0&&rightIndex!==void 0&&leftIndex!==rightIndex){found=[leftIndex,rightIndex];return}}}for(const[key,value]of Object.entries(node)){if(key==="parent")continue;if(Array.isArray(value)){for(const entry of value)visit(entry)}else if(value&&typeof value==="object"&&"type"in value){visit(value)}}};visit(fn.body);return found}function comparisonOperands(node){const args=node.arguments;if(args.some(argument=>argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)){return null}const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(BINARY_EQUALITY_FUNCTIONS.has(callee.name)&&args.length===2){return[args[0],args[1]]}const wrapped=equalityWrapperParams(callee);if(!wrapped)return null;const[leftIndex,rightIndex]=wrapped;const leftArg=args[leftIndex];const rightArg=args[rightIndex];if(!leftArg||!rightArg)return null;return[leftArg,rightArg]}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return null;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const method=callee.property.name;if(method==="compare"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&args.length===2){return[args[0],args[1]]}if(BINARY_EQUALITY_FUNCTIONS.has(method)&&args.length===2){return[args[0],args[1]]}if(RECEIVER_COMPARE_METHODS.has(method)&&args.length===1){return[callee.object,args[0]]}return null}function checkComparison(node,rawLeft,rawRight){const left=unwrapChain(rawLeft);const right=unwrapChain(rawRight);if(isResolvedConstant(left)||isResolvedConstant(right)){return}if(isNamedConstant(left)||isNamedConstant(right)){return}if(isSelfComparison(left,right)){return}const leftIsSecret=isSecretIdentifier(left)||isCryptoSecret(left);const rightIsSecret=isSecretIdentifier(right)||isCryptoSecret(right);if(leftIsSecret||rightIsSecret){if(!reportUnverifiedComparisons){const leftUntrusted=readsUntrusted(left);const rightUntrusted=readsUntrusted(right);if(leftUntrusted===rightUntrusted){if(!leftUntrusted)return;if(!isServerDerived(left)&&!isServerDerived(right))return}}context.report({node,messageId:"timingUnsafeCompare"})}}function checkBinaryExpression(node){if(node.operator!=="==="&&node.operator!=="=="&&node.operator!=="!=="&&node.operator!=="!="){return}checkComparison(node,node.left,node.right)}function checkCallExpression(node){const operands=comparisonOperands(node);if(!operands)return;checkComparison(node,operands[0],operands[1])}return{BinaryExpression:checkBinaryExpression,CallExpression:checkCallExpression}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimingUnsafeCompare=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const names_1=require("../../utils/names");const DEFAULT_SECRET_PATTERNS=["token","secret","password","hash","signature","mac","hmac","digest","apiKey","api_key","api-key","auth","credential","bearer","jwt","csrf","nonce","ssn","social_security","social-security","pii","private_key","private-key","privateKey","access_token","access-token","accessToken","refresh_token","refresh-token","refreshToken","session_id","session-id","sessionId","auth_token","auth-token","authToken","encryption_key","encryption-key","encryptionKey"];function isSourceConstant(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.name==="undefined")return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral)return node.expressions.length===0;return false}const BOOLEAN_PREDICATE_NAME=/^(?:is|has|should|can|did|was|will|does)[A-Z]/;const NAMED_CONSTANT=/^[A-Z][A-Z0-9_]*$/;const DEFAULT_UNTRUSTED_SOURCES=["req","request","ctx","event"];const NAMESPACE_NAME=/^(?:[A-Z][A-Z0-9_]*|[A-Z][a-zA-Z0-9]*)$/;function isNamedConstant(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.computed)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return NAMESPACE_NAME.test(node.object.name)&&NAMED_CONSTANT.test(node.property.name)}function memberRoot(node){let current=node;for(;;){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object;continue}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){current=current.callee;continue}break}return current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?current:null}function isSelfComparison(left,right){const pair=(bare,derived)=>{if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(derived.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return memberRoot(derived)?.name===bare.name};return pair(left,right)||pair(right,left)}const NON_SECRET_MEMBERS=new Map([["hash",new Set(["location"])]]);const DEFAULT_NON_SECRET_WORDS=["author","authors","authored","authoring","authorship","hashtag","hashtags"];const DEFAULT_NON_SECRET_TAILS=["count","counts","limit","limits","usage","total","size","length","price","cost","quota","address","addresses","index","rank","percent","path","paths","pathname","pathnames","endpoint","endpoints","route","routes","hostname","host","port","origin","kind","kinds","type","types","flag","flags","category"];const CRYPTO_DERIVATIONS=new Set(["createHmac","createHash","createSign","pbkdf2Sync","scryptSync","hkdfSync","digest","sign","hmac"]);const RECEIVER_COMPARE_METHODS=new Set(["equals","startsWith","endsWith","localeCompare"]);const BINARY_EQUALITY_FUNCTIONS=new Set(["isEqual","isEqualWith","deepEqual","fastDeepEqual","shallowEqual"]);const SERVER_STATE_REQUEST_PROPERTIES=new Set(["session","user","locals","app","state"]);function unwrapChain(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.ChainExpression?node.expression:node}function memberPropertyName(node){const property=node.property;if(!node.computed){return property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.name:null}return property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof property.value==="string"?property.value:null}function isNonSecretMember(node){const propertyName=memberPropertyName(node);if(propertyName===null)return false;const receivers=NON_SECRET_MEMBERS.get(propertyName.toLowerCase());if(!receivers)return false;const owner=node.object;const ownerName=owner.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.name:owner.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!owner.computed&&owner.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.property.name:"";return receivers.has(ownerName.toLowerCase())}exports.noTimingUnsafeCompare=(0,eslint_devkit_1.createRule)({name:"no-timing-unsafe-compare",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md",description:"Disallow timing-unsafe comparison of secrets",cwe:"CWE-208",cvss:5.9},messages:{timingUnsafeCompare:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timing-unsafe comparison",cwe:"CWE-208",description:"Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.",severity:"HIGH",fix:"Use crypto.timingSafeEqual() for constant-time comparison",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"})},schema:[{type:"object",properties:{secretPatterns:{type:"array",items:{type:"string"},default:DEFAULT_SECRET_PATTERNS,description:"Variable name patterns that indicate secrets"},untrustedSources:{type:"array",items:{type:"string"},default:DEFAULT_UNTRUSTED_SOURCES,description:"Identifier roots treated as attacker-controlled (default: req, request, ctx, event)"},reportUnverifiedComparisons:{type:"boolean",default:false,description:"Report on a secret-looking name alone, without an attacker-controlled operand. Restores the pre-inversion behaviour."},nonSecretWords:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_WORDS],description:`Whole words that mean a secretPatterns match was a collision (default: ${DEFAULT_NON_SECRET_WORDS.join(", ")}). Replaces the list.`},nonSecretTails:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_TAILS],description:`Trailing words that make the value a measurement or location rather than a credential (default: ${DEFAULT_NON_SECRET_TAILS.join(", ")}). Replaces the list.`}},additionalProperties:false}]},defaultOptions:[{secretPatterns:DEFAULT_SECRET_PATTERNS,nonSecretWords:[...DEFAULT_NON_SECRET_WORDS],nonSecretTails:[...DEFAULT_NON_SECRET_TAILS]}],create(context,[options={}]){const{secretPatterns=DEFAULT_SECRET_PATTERNS,untrustedSources=DEFAULT_UNTRUSTED_SOURCES,reportUnverifiedComparisons=false,nonSecretWords=[...DEFAULT_NON_SECRET_WORDS],nonSecretTails=[...DEFAULT_NON_SECRET_TAILS]}=options;const nonSecretWordSet=new Set(nonSecretWords.map(word=>word.toLowerCase()));const nonSecretTailSet=new Set(nonSecretTails.map(word=>word.toLowerCase()));const sourceCode=context.sourceCode;const readsUntrusted=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const patterns=(0,eslint_devkit_1.compileUserPatterns)(secretPatterns,"i");function nameLooksSecret(name){if(BOOLEAN_PREDICATE_NAME.test(name))return false;const words=(0,names_1.identifierWords)(name);if(words.some(word=>nonSecretWordSet.has(word)))return false;if(words.length>0&&nonSecretTailSet.has(words[words.length-1])){return false}return patterns.some(p=>p.test(name))}function isCryptoDerivation(node){const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isCryptoDerivation(bare);if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return CRYPTO_DERIVATIONS.has(callee.name)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return false;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(CRYPTO_DERIVATIONS.has(callee.property.name))return true;return isCryptoDerivation(callee.object)}function isCryptoSecret(node){if(isCryptoDerivation(node))return true;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,provenance_1.bindingInit)(sourceCode,bare);return init!==void 0&&containsCryptoDerivation(init)}function containsCryptoDerivation(node){if(isCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.expressions.some(expression=>containsCryptoDerivation(expression))}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return containsCryptoDerivation(node.left)||containsCryptoDerivation(node.right)}return false}function isServerDerived(node,depth=0){if(depth>4)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isServerDerived(bare,depth+1);if(node.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression)return true;if(containsCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const propertyName=memberPropertyName(node);if(propertyName!==null&&SERVER_STATE_REQUEST_PROPERTIES.has(propertyName)){return true}return isServerDerived(node.object,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,provenance_1.bindingInit)(sourceCode,node);return init!==void 0&&isServerDerived(init,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=unwrapChain(node.callee);if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;return!readsUntrusted(callee.object)||isServerDerived(callee.object,depth+1)}return false}function isSecretIdentifier(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return nameLooksSecret(node.name)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(isNonSecretMember(node))return false;const propertyName=memberPropertyName(node);if(propertyName!==null){return nameLooksSecret(propertyName)}}return false}function isResolvedConstant(node){if(isSourceConstant(node))return true;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,provenance_1.constLiteralOf)(sourceCode,node)!==void 0}function resolveLocalFunction(callee){const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type==="FunctionName")return def.node;if(def.type!=="Variable")return null;const init=def.node.init;if(!init)return null;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(init);if(bare.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression||bare.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression){return bare}return null}function comparesLengths(node){const isLength=operand=>operand.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&memberPropertyName(operand)==="length";return isLength(node.left)||isLength(node.right)}function equalityWrapperParams(callee){const fn=resolveLocalFunction(callee);if(!fn||fn.params.length<2)return null;const paramIndex=new Map;fn.params.forEach((param,index)=>{if(param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)paramIndex.set(param.name,index)});let found=null;const visit=node=>{if(found||!node||typeof node.type!=="string")return;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){const isEquality=node.operator==="==="||node.operator==="!=="||node.operator==="=="||node.operator==="!=";if(isEquality&&!comparesLengths(node)){const leftIndex=paramIndex.get(memberRoot(node.left)?.name??"");const rightIndex=paramIndex.get(memberRoot(node.right)?.name??"");if(leftIndex!==void 0&&rightIndex!==void 0&&leftIndex!==rightIndex){found=[leftIndex,rightIndex];return}}}for(const[key,value]of Object.entries(node)){if(key==="parent")continue;if(Array.isArray(value)){for(const entry of value)visit(entry)}else if(value&&typeof value==="object"&&"type"in value){visit(value)}}};visit(fn.body);return found}function comparisonOperands(node){const args=node.arguments;if(args.some(argument=>argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)){return null}const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(BINARY_EQUALITY_FUNCTIONS.has(callee.name)&&args.length===2){return[args[0],args[1]]}const wrapped=equalityWrapperParams(callee);if(!wrapped)return null;const[leftIndex,rightIndex]=wrapped;const leftArg=args[leftIndex];const rightArg=args[rightIndex];if(!leftArg||!rightArg)return null;return[leftArg,rightArg]}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return null;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const method=callee.property.name;if(method==="compare"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&args.length===2){return[args[0],args[1]]}if(BINARY_EQUALITY_FUNCTIONS.has(method)&&args.length===2){return[args[0],args[1]]}if(RECEIVER_COMPARE_METHODS.has(method)&&args.length===1){return[callee.object,args[0]]}return null}function checkComparison(node,rawLeft,rawRight){const left=unwrapChain(rawLeft);const right=unwrapChain(rawRight);if(isResolvedConstant(left)||isResolvedConstant(right)){return}if(isNamedConstant(left)||isNamedConstant(right)){return}if(isSelfComparison(left,right)){return}const leftIsSecret=isSecretIdentifier(left)||isCryptoSecret(left);const rightIsSecret=isSecretIdentifier(right)||isCryptoSecret(right);if(leftIsSecret||rightIsSecret){if(!reportUnverifiedComparisons){const leftUntrusted=readsUntrusted(left);const rightUntrusted=readsUntrusted(right);if(leftUntrusted===rightUntrusted){if(!leftUntrusted)return;if(!isServerDerived(left)&&!isServerDerived(right))return}}context.report({node,messageId:"timingUnsafeCompare"})}}function checkBinaryExpression(node){if(node.operator!=="==="&&node.operator!=="=="&&node.operator!=="!=="&&node.operator!=="!="){return}checkComparison(node,node.left,node.right)}function checkCallExpression(node){const operands=comparisonOperands(node);if(!operands)return;checkComparison(node,operands[0],operands[1])}return{BinaryExpression:checkBinaryExpression,CallExpression:checkCallExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noToctouVulnerability=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const provenance_1=require("../../utils/provenance");const PER_USER_ROOT_FUNCTIONS=new Set(["homedir","userInfo"]);const PER_USER_ENV_VARS=new Set(["HOME","USERPROFILE","LOCALAPPDATA","APPDATA","XDG_CACHE_HOME","XDG_CONFIG_HOME","XDG_DATA_HOME","XDG_STATE_HOME"]);const DEFAULT_FS_METHODS=["fs.existsSync","fs.statSync","fs.accessSync","fs.exists","fs.stat","fs.access"];const RISKY_USE_METHODS=new Set(["readFileSync","readFile","openSync","open","createReadStream","writeFileSync","writeFile","appendFileSync","appendFile","createWriteStream","unlinkSync","unlink","rmSync","rm","rmdirSync","rmdir","mkdirSync","mkdir","renameSync","rename","copyFileSync","copyFile","truncateSync","truncate","chmodSync","chmod","chownSync","chown","symlinkSync","symlink","linkSync","link"]);function memberName(callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;if(callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&!callee.computed){return callee.property.name}return callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof callee.property.value==="string"?callee.property.value:null}exports.noToctouVulnerability=(0,eslint_devkit_2.createRule)({name:"no-toctou-vulnerability",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-toctou-vulnerability.md",description:"Detects Time-of-Check-Time-of-Use vulnerabilities",cwe:"CWE-367",cvss:7},messages:{toctouVulnerability:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"TOCTOU vulnerability",cwe:"CWE-367",description:"Time-of-check Time-of-use race condition detected",severity:"HIGH",fix:"Act on the result and handle the failure \u2014 open/unlink and catch ENOENT \u2014 instead of checking first. Not a finding if the path is inside a directory only this user can write",documentationLink:"https://cwe.mitre.org/data/definitions/367.html"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true},fsMethods:{type:"array",items:{type:"string"},default:DEFAULT_FS_METHODS,description:"Filesystem check calls that create a time-of-check window. Replaces the built-in list. Only the final dotted segment is compared, so `fs.existsSync` and `existsSync` are the same entry."}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,fsMethods:DEFAULT_FS_METHODS}],create(context,[options={}]){const{ignoreInTests=true,fsMethods=DEFAULT_FS_METHODS}=options||{};const checkMethods=new Set(fsMethods.map(entry=>entry.split(".").at(-1)));const filename=context.filename;const isTestFile=ignoreInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function returnedExpression(callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type!=="FunctionName")return null;const body=def.node.body;if(body?.type!==eslint_devkit_1.AST_NODE_TYPES.BlockStatement)return null;for(const statement of body.body){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement&&statement.argument){return statement.argument}}return null}function reachesPerUserRoot(node,depth=0){if(depth>8)return false;const next=child=>reachesPerUserRoot(child,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const variable=(0,provenance_1.findVariable)(sourceCode,node);const def=variable?.defs.length===1?variable.defs[0]:void 0;if(def?.type!=="Variable"||!def.node.init)return false;return next(def.node.init)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{if(!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&PER_USER_ENV_VARS.has(node.property.name)){return true}return next(node.object)}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{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;if(name!==null&&PER_USER_ROOT_FUNCTIONS.has(name))return true;if(node.arguments.some(argument=>next(argument)))return true;const returned=returnedExpression(callee);return returned!==null&&next(returned)}case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:return next(node.consequent)||next(node.alternate);case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:return next(node.left)||next(node.right);case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(expression=>next(expression));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return next(node.left)||next(node.right);default:return false}}const EXISTENCE_CHECKS=new Set(["existsSync","exists"]);const READ_ONLY_USES=new Set(["readFileSync","readFile"]);function isSecurityRelevantWindow(checkMethod,useMethod){if(!EXISTENCE_CHECKS.has(checkMethod))return true;return!READ_ONLY_USES.has(useMethod)}function resolvesToNonFsLocal(callee,at){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const scope=sourceCode.getScope(at);if((0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs")||(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs/promises")){return false}const variable=(0,provenance_1.findVariable)(sourceCode,callee);return!!variable&&variable.defs.length>0}function usedMethodName(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return resolvesToNonFsLocal(callee,node)?"":callee.name}const name=memberName(callee);if(name===null)return"";const objectName=callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.object.name:"";if(objectName==="fs"||objectName==="fsPromises")return name;const scope=sourceCode.getScope(node);return(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs")||(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs/promises")?name:""}function collectConditionCalls(node,out,depth=0){if(depth>8)return;const inner=(0,eslint_devkit_1.unwrapTypeSyntax)(node);switch(inner.type){case eslint_devkit_1.AST_NODE_TYPES.CallExpression:out.push(inner);return;case eslint_devkit_1.AST_NODE_TYPES.UnaryExpression:case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:collectConditionCalls(inner.argument,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:collectConditionCalls(inner.left,out,depth+1);collectConditionCalls(inner.right,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:collectConditionCalls(inner.test,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.ChainExpression:collectConditionCalls(inner.expression,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:collectConditionCalls(inner.object,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const init=(0,const_value_1.constInitializerOf)(sourceCode,inner);if(init!==null)collectConditionCalls(init,out,depth+1);return}default:return}}function sameTarget(checkArg,useArg){const check=(0,eslint_devkit_1.unwrapTypeSyntax)(checkArg);const use=(0,eslint_devkit_1.unwrapTypeSyntax)(useArg);if(check.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&use.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return check.name===use.name}return sourceCode.getText(check).replace(/\s/g,"")===sourceCode.getText(use).replace(/\s/g,"")}function checkCallExpression(node){const useMethodName=usedMethodName(node);if(!RISKY_USE_METHODS.has(useMethodName)){return}const rawUseArg=node.arguments[0];if(!rawUseArg)return;const useArg=(0,eslint_devkit_1.unwrapTypeSyntax)(rawUseArg);if(reachesPerUserRoot(useArg))return;let current=node.parent;while(current){if(current.type==="IfStatement"){const conditionCalls=[];collectConditionCalls(current.test,conditionCalls);for(const condition of conditionCalls){const checkMethodName=resolvesToNonFsLocal(condition.callee,condition)?"":memberName(condition.callee)??(condition.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?condition.callee.name:"");if(checkMethods.has(checkMethodName)&&isSecurityRelevantWindow(checkMethodName,useMethodName)){const checkArg=condition.arguments[0];if(checkArg&&sameTarget(checkArg,useArg)){reportToctou(node);return}}if(condition.callee.type==="MemberExpression"&&condition.callee.property.type==="Identifier"&&["isFile","isDirectory"].includes(condition.callee.property.name)&&condition.callee.object.type==="Identifier"){const statsVarName=condition.callee.object.name;let currentScope=sourceCode.getScope(condition);let variable=null;while(currentScope){variable=currentScope.variables.find(v=>v.name===statsVarName)||null;if(variable)break;currentScope=currentScope.upper}if(variable&&variable.defs.length>0){const def=variable.defs[0];if(def.type==="Variable"&&def.node.init&&def.node.init.type==="CallExpression"){const init=def.node.init;const statMethod=memberName(init.callee)??(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?init.callee.name:"");if(["statSync","lstatSync","stat","lstat"].includes(statMethod)){const statArg=init.arguments[0];if(statArg&&sameTarget(statArg,useArg)){reportToctou(node);return}}}}}}}current=current.parent}}function reportToctou(node){context.report({node,messageId:"toctouVulnerability"})}return{CallExpression:checkCallExpression}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noToctouVulnerability=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const provenance_1=require("../../utils/provenance");const PER_USER_ROOT_FUNCTIONS=new Set(["homedir","userInfo"]);const PER_USER_ENV_VARS=new Set(["HOME","USERPROFILE","LOCALAPPDATA","APPDATA","XDG_CACHE_HOME","XDG_CONFIG_HOME","XDG_DATA_HOME","XDG_STATE_HOME"]);const DEFAULT_FS_METHODS=["fs.existsSync","fs.statSync","fs.accessSync","fs.exists","fs.stat","fs.access"];const MKDIR_METHODS=new Set(["mkdir","mkdirSync"]);const RISKY_USE_METHODS=new Set(["readFileSync","readFile","openSync","open","createReadStream","writeFileSync","writeFile","appendFileSync","appendFile","createWriteStream","unlinkSync","unlink","rmSync","rm","rmdirSync","rmdir","mkdirSync","mkdir","renameSync","rename","copyFileSync","copyFile","truncateSync","truncate","chmodSync","chmod","chownSync","chown","symlinkSync","symlink","linkSync","link"]);function memberName(callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;if(callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&!callee.computed){return callee.property.name}return callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof callee.property.value==="string"?callee.property.value:null}exports.noToctouVulnerability=(0,eslint_devkit_2.createRule)({name:"no-toctou-vulnerability",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-toctou-vulnerability.md",description:"Detects Time-of-Check-Time-of-Use vulnerabilities",cwe:"CWE-367",cvss:7},messages:{toctouVulnerability:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"TOCTOU vulnerability",cwe:"CWE-367",description:"Time-of-check Time-of-use race condition detected",severity:"HIGH",fix:"Act on the result and handle the failure \u2014 open/unlink and catch ENOENT \u2014 instead of checking first. Not a finding if the path is inside a directory only this user can write",documentationLink:"https://cwe.mitre.org/data/definitions/367.html"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true},fsMethods:{type:"array",items:{type:"string"},default:DEFAULT_FS_METHODS,description:"Filesystem check calls that create a time-of-check window. Replaces the built-in list. Only the final dotted segment is compared, so `fs.existsSync` and `existsSync` are the same entry."}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,fsMethods:DEFAULT_FS_METHODS}],create(context,[options={}]){const{ignoreInTests=true,fsMethods=DEFAULT_FS_METHODS}=options||{};const checkMethods=new Set(fsMethods.map(entry=>entry.split(".").at(-1)));const filename=context.filename;const isTestFile=ignoreInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function returnedExpression(callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type!=="FunctionName")return null;const body=def.node.body;if(body?.type!==eslint_devkit_1.AST_NODE_TYPES.BlockStatement)return null;for(const statement of body.body){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement&&statement.argument){return statement.argument}}return null}function reachesPerUserRoot(node,depth=0){if(depth>8)return false;const next=child=>reachesPerUserRoot(child,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const variable=(0,provenance_1.findVariable)(sourceCode,node);const def=variable?.defs.length===1?variable.defs[0]:void 0;if(def?.type!=="Variable"||!def.node.init)return false;return next(def.node.init)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{if(!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&PER_USER_ENV_VARS.has(node.property.name)){return true}return next(node.object)}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{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;if(name!==null&&PER_USER_ROOT_FUNCTIONS.has(name))return true;if(node.arguments.some(argument=>next(argument)))return true;const returned=returnedExpression(callee);return returned!==null&&next(returned)}case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:return next(node.consequent)||next(node.alternate);case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:return next(node.left)||next(node.right);case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(expression=>next(expression));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return next(node.left)||next(node.right);default:return false}}const EXISTENCE_CHECKS=new Set(["existsSync","exists"]);const READ_ONLY_USES=new Set(["readFileSync","readFile"]);function isSecurityRelevantWindow(checkMethod,useMethod){if(!EXISTENCE_CHECKS.has(checkMethod))return true;return!READ_ONLY_USES.has(useMethod)}function resolvesToNonFsLocal(callee,at){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const scope=sourceCode.getScope(at);if((0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs")||(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs/promises")){return false}const variable=(0,provenance_1.findVariable)(sourceCode,callee);return!!variable&&variable.defs.length>0}function usedMethodName(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return resolvesToNonFsLocal(callee,node)?"":callee.name}const name=memberName(callee);if(name===null)return"";const objectName=callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.object.name:"";if(objectName==="fs"||objectName==="fsPromises")return name;const scope=sourceCode.getScope(node);return(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs")||(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs/promises")?name:""}function collectConditionCalls(node,out,depth=0){if(depth>8)return;const inner=(0,eslint_devkit_1.unwrapTypeSyntax)(node);switch(inner.type){case eslint_devkit_1.AST_NODE_TYPES.CallExpression:out.push(inner);return;case eslint_devkit_1.AST_NODE_TYPES.UnaryExpression:case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:collectConditionCalls(inner.argument,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:collectConditionCalls(inner.left,out,depth+1);collectConditionCalls(inner.right,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:collectConditionCalls(inner.test,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.ChainExpression:collectConditionCalls(inner.expression,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:collectConditionCalls(inner.object,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const init=(0,const_value_1.constInitializerOf)(sourceCode,inner);if(init!==null)collectConditionCalls(init,out,depth+1);return}default:return}}function sameTarget(checkArg,useArg){const check=(0,eslint_devkit_1.unwrapTypeSyntax)(checkArg);const use=(0,eslint_devkit_1.unwrapTypeSyntax)(useArg);if(check.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&use.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return check.name===use.name}return sourceCode.getText(check).replace(/\s/g,"")===sourceCode.getText(use).replace(/\s/g,"")}function hasRecursiveOption(node){return node.arguments.some(argument=>argument.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression&&argument.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==="recursive"&&property.value.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&property.value.value===true))}function checkCallExpression(node){const useMethodName=usedMethodName(node);if(!RISKY_USE_METHODS.has(useMethodName)){return}if(MKDIR_METHODS.has(useMethodName)&&hasRecursiveOption(node)){return}const rawUseArg=node.arguments[0];if(!rawUseArg)return;const useArg=(0,eslint_devkit_1.unwrapTypeSyntax)(rawUseArg);if(reachesPerUserRoot(useArg))return;let current=node.parent;while(current){if(current.type==="IfStatement"){const conditionCalls=[];collectConditionCalls(current.test,conditionCalls);for(const condition of conditionCalls){const checkMethodName=resolvesToNonFsLocal(condition.callee,condition)?"":memberName(condition.callee)??(condition.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?condition.callee.name:"");if(checkMethods.has(checkMethodName)&&isSecurityRelevantWindow(checkMethodName,useMethodName)){const checkArg=condition.arguments[0];if(checkArg&&sameTarget(checkArg,useArg)){reportToctou(node);return}}if(condition.callee.type==="MemberExpression"&&condition.callee.property.type==="Identifier"&&["isFile","isDirectory"].includes(condition.callee.property.name)&&condition.callee.object.type==="Identifier"){const statsVarName=condition.callee.object.name;let currentScope=sourceCode.getScope(condition);let variable=null;while(currentScope){variable=currentScope.variables.find(v=>v.name===statsVarName)||null;if(variable)break;currentScope=currentScope.upper}if(variable&&variable.defs.length>0){const def=variable.defs[0];if(def.type==="Variable"&&def.node.init&&def.node.init.type==="CallExpression"){const init=def.node.init;const statMethod=memberName(init.callee)??(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?init.callee.name:"");if(["statSync","lstatSync","stat","lstat"].includes(statMethod)){const statArg=init.arguments[0];if(statArg&&sameTarget(statArg,useArg)){reportToctou(node);return}}}}}}}current=current.parent}}function reportToctou(node){context.report({node,messageId:"toctouVulnerability"})}return{CallExpression:checkCallExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeBufferAlloc=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 UNSAFE_ALLOCATORS=new Set(["allocUnsafe","allocUnsafeSlow"]);const SIZED_ALLOCATORS=new Set(["Array","Buffer","Uint8Array","Uint16Array","Uint32Array","Int8Array","Int16Array","Int32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array","ArrayBuffer","SharedArrayBuffer"]);function looksNumeric(node){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Literal:return typeof node.value==="number";case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return["+","-","*","/","%","<<",">>",">>>"].includes(node.operator);case eslint_devkit_1.AST_NODE_TYPES.Identifier:return COUNT_NAMES.has(node.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&COUNT_NAMES.has(node.property.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.object.name==="Math"){return true}const name=calleeName(node.callee);return name!==null&&/^(read|len|size|count|decode|parse)/i.test(name)}default:return false}}const COUNT_NAMES=new Set(["length","len","size","count","n","num","total","capacity","bytelength"]);const BUFFER_ALLOCATORS=new Set(["alloc","allocUnsafe","allocUnsafeSlow"]);const WIRE_NAMES=new Set(["chunk","chunks","buffer","buf","payload","frame","packet","raw","message","msg"]);const REQUEST_ROOTS=new Set(["req","request","ctx","event"]);function isWriteMethod(name){return name==="fill"||name==="set"||name.startsWith("write")}const DESTINATION_ARGUMENT_CALLS=new Set(["copy","randomFill","randomFillSync"]);const METADATA_PROPERTIES=new Set(["length","byteLength","byteOffset","buffer"]);const WRITE_WIDTHS=new Map([["writeUInt8",1],["writeInt8",1],["writeUInt16LE",2],["writeUInt16BE",2],["writeInt16LE",2],["writeInt16BE",2],["writeUInt32LE",4],["writeUInt32BE",4],["writeInt32LE",4],["writeInt32BE",4],["writeFloatLE",4],["writeFloatBE",4],["writeDoubleLE",8],["writeDoubleBE",8],["writeBigInt64LE",8],["writeBigInt64BE",8],["writeBigUInt64LE",8],["writeBigUInt64BE",8]]);const MAX_TRACKED_ALLOCATION=4096;const BUFFER_MODULES=new Set(["buffer","node:buffer"]);const CRYPTO_MODULES=new Set(["crypto","node:crypto"]);const RANDOM_FILL_CALLS=new Set(["randomFill","randomFillSync"]);function isCryptoModuleRequire(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"&&node.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.arguments[0].value==="string"&&CRYPTO_MODULES.has(node.arguments[0].value)}function isBufferModuleRequire(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"&&node.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.arguments[0].value==="string"&&BUFFER_MODULES.has(node.arguments[0].value)}function isBufferObject(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name==="Buffer";return node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.property.name==="Buffer"&&isBufferModuleRequire(node.object)}function isInsideLoop(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.ForStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.ForOfStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.ForInStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.WhileStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.DoWhileStatement){return true}current=current.parent}return false}function coversWholeBuffer(call,method){if(method==="fill")return true;const width=WRITE_WIDTHS.get(method);if(width!==void 0){const offset2=call.arguments[1];return offset2!==void 0&&offset2.type!==eslint_devkit_1.AST_NODE_TYPES.Literal&&isInsideLoop(call)}if(call.arguments.length===1)return true;const offset=call.arguments[1];return offset.type!==eslint_devkit_1.AST_NODE_TYPES.Literal&&isInsideLoop(call)}function fixedWriteSpan(call,method){const width=WRITE_WIDTHS.get(method);if(width===void 0)return null;const offset=call.arguments[1];if(offset===void 0)return{start:0,end:width};if(offset.type!==eslint_devkit_1.AST_NODE_TYPES.Literal||typeof offset.value!=="number"){return null}return{start:offset.value,end:offset.value+width}}function calleeName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed){return callee.property.name}return null}function declaredName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&&node.id)return node.id.name;const parent=node.parent;if(parent?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if((parent?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition||parent?.type===eslint_devkit_1.AST_NODE_TYPES.Property)&&!parent.computed){const key=parent.key;return key.name}return null}exports.noUnsafeBufferAlloc=(0,eslint_devkit_1.createRule)({name:"no-unsafe-buffer-alloc",meta:{type:"problem",hasSuggestions:true,docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-unsafe-buffer-alloc.md",description:"Disallow `Buffer.allocUnsafe()` and `Buffer.allocUnsafeSlow()`, which return uninitialized memory",cwe:"CWE-908",cweJustification:"CWE-908 (Use of Uninitialized Resource) \u2014 allocUnsafe returns a view over non-zeroed heap memory; any byte not overwritten before the buffer is read or transmitted discloses prior process memory.",cvss:7.5,confidence:"high"},messages:{unsafeAlloc:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Uninitialized Buffer Allocation",cwe:"CWE-908",cvss:7.5,description:"`Buffer.allocUnsafe(size)` returns memory that has not been zeroed. Every byte not overwritten before the buffer is read or sent leaks whatever the allocator previously stored there.",severity:"HIGH",fix:"Use `Buffer.alloc(size)` (zero-filled), or keep `allocUnsafe` only where the very next statement overwrites the whole buffer \u2014 `Buffer.allocUnsafe(size).fill(0)` is accepted by this rule.",documentationLink:"https://nodejs.org/api/buffer.html#static-method-bufferallocunsafesize"}),unsafeAllocSlow:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Uninitialized Buffer Allocation (allocUnsafeSlow)",cwe:"CWE-908",cvss:7.5,description:"`Buffer.allocUnsafeSlow(size)` allocates outside the shared pool but is equally uninitialized \u2014 the returned bytes are whatever was last in that memory.",severity:"HIGH",fix:"Use `Buffer.alloc(size)`, or append `.fill(0)` to zero the allocation at the call site.",documentationLink:"https://nodejs.org/api/buffer.html#static-method-bufferallocunsafeslowsize"}),useSafeAlloc:"Replace with `Buffer.alloc()` (zero-filled).",unboundedAllocation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Allocation Sized By Untrusted Input",cwe:"CWE-789",cvss:7.5,description:'The allocation size is read off the wire, so the peer picks it. For `new Array(n)` the hazard is a narrow band, not "large n": V8 keeps a packed backing store only up to ~33.5M elements, and a length just under that turns a 12-byte length prefix into a 229MB allocation (measured: `new Array(3e7)` = 228.9MB in 20.7ms). Past the threshold V8 switches the array to dictionary mode and the allocation costs nothing at all (`new Array(4e7)` and `new Array(1e9)` are both 0.0MB in ~0.005ms). A typed allocation \u2014 `Buffer.alloc(n)`, `new Uint8Array(n)` \u2014 has no such threshold and commits n bytes at any n.',severity:"HIGH",fix:'Clamp the length against the maximum the protocol actually permits, before allocating: `if (length > MAX_LENGTH) throw new Error("too long")`, or `new Array(Math.min(length, MAX_LENGTH))`. A guard that only rejects implausibly huge values is not a fix for `new Array` \u2014 those are the sizes V8 makes free. The damaging lengths are the plausible ones just below the packed-elements limit.',documentationLink:"https://cwe.mitre.org/data/definitions/789.html"})},schema:[]},defaultOptions:[],create(context){const sourceCode=context.sourceCode;const wireParams=new Map;const pendingAllocations=[];const pendingCallSites=[];function readsWire(node,depth=0){if(depth>10)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return readsWire(bare,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{if(node.name==="Buffer"){const bound=(0,provenance_1.findVariable)(sourceCode,node);if(bound===null||bound.defs.length===0)return false;if(bound.defs[0].type==="ImportBinding")return false}const lower=node.name.toLowerCase();if(WIRE_NAMES.has(lower)||REQUEST_ROOTS.has(lower)){const bound=(0,provenance_1.findVariable)(sourceCode,node);const def=bound===null?void 0:bound.defs[0];if(def!==void 0&&def.type==="Variable"&&def.node.init!==null&&def.node.init!==void 0){return readsWire(def.node.init,depth+1)}return true}const owner=enclosingFunction(node);if(owner!==null){const name=declaredName(owner);const indices=name===null?void 0:wireParams.get(name);if(indices!==void 0){const index=paramIndexOf(node,owner);if(index!==null&&indices.has(index))return true}}const variable=(0,provenance_1.findVariable)(sourceCode,node);const lastWrite=(variable?.references??[]).map(reference=>reference.writeExpr).filter(write=>write!=null).filter(write=>write.range[1]<=node.range[0]).sort((a,b)=>a.range[1]-b.range[1]).at(-1);return lastWrite!==void 0&&readsWire(lastWrite,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return readsWire(node.object,depth+1);case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&readsWire(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.CallExpression:return readsWire(node.callee,depth+1)||node.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&readsWire(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return readsWire(node.left,depth+1)||readsWire(node.right,depth+1);default:return false}}function enclosingFunction(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}function paramIndexOf(node,owner){const params=owner.params;const index=params.findIndex(param=>param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&¶m.name===node.name);return index===-1?null:index}function isClamped(size){if(size.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&size.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&size.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&size.callee.object.name==="Math"&&size.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&size.callee.property.name==="min"){return size.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&!readsWire(argument))}if(size.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=size.name;let current=size.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){if(mentionsInComparison(current.test,name))return true}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){const body=current.body;if(body.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){for(const statement of body.body){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement&&mentionsInComparison(statement.test,name)){return true}}}return false}current=current.parent}return false}function mentionsInComparison(test,name){if(test.type===eslint_devkit_1.AST_NODE_TYPES.LogicalExpression){return mentionsInComparison(test.left,name)||mentionsInComparison(test.right,name)}if(test.type!==eslint_devkit_1.AST_NODE_TYPES.BinaryExpression)return false;if(!["<","<=",">",">="].includes(test.operator))return false;const named=side=>side.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&side.name===name;return named(test.left)||named(test.right)}function allocationSize(node){const size=node.arguments[0];if(size===void 0||size.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return null;const callee=node.callee;if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&SIZED_ALLOCATORS.has(callee.name)){return looksNumeric(size)?size:null}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&BUFFER_ALLOCATORS.has(callee.property.name)){return size}return null}function recordCallSite(node){const name=calleeName(node.callee);if(name===null)return;node.arguments.forEach((argument,index)=>{if(argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return;if(!readsWire(argument))return;const indices=wireParams.get(name)??new Set;indices.add(index);wireParams.set(name,indices)})}function judgeAllocation(node,size){if(isClamped(size))return;if(!readsWire(size))return;context.report({node,messageId:"unboundedAllocation"})}function classifyUse(identifier){const parent=identifier.parent;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===identifier){if(parent.computed){const grandparent2=parent.parent;return grandparent2.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&grandparent2.left===parent?"partial":"read"}if(parent.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return"read";const name=parent.property.name;if(METADATA_PROPERTIES.has(name))return"metadata";const grandparent=parent.parent;if(grandparent.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||grandparent.callee!==parent||!isWriteMethod(name)){return"read"}return coversWholeBuffer(grandparent,name)?"covering":"partial"}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.arguments[0]===identifier){const method=destinationArgumentCallee(parent.callee);if(method===null)return"read";return coversWholeBuffer(parent,method)?"covering":"partial"}return"read"}function destinationArgumentCallee(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&DESTINATION_ARGUMENT_CALLS.has(callee.property.name)){return callee.property.name}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||!RANDOM_FILL_CALLS.has(callee.name)){return null}const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(variable===null||variable.defs.length===0)return null;const def=variable.defs[0];if(def.type==="ImportBinding"){const declaration=def.parent;return declaration.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration&&CRYPTO_MODULES.has(declaration.source.value)?callee.name:null}return def.type==="Variable"&&def.node.init!==null&&isCryptoModuleRequire(def.node.init)?callee.name:null}function isCoveredBeforeRead(call){const declarator=call.parent;if(declarator.type!==eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator||declarator.init!==call||declarator.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return false}const variable=context.sourceCode.getDeclaredVariables(declarator)[0];const uses=variable.references.filter(reference=>reference.identifier!==declarator.id).sort((a,b)=>a.identifier.range[0]-b.identifier.range[0]);const covered=byteMapFor(call);let remaining=covered===null?-1:covered.length;for(const use of uses){const kind=classifyUse(use.identifier);if(kind==="metadata")continue;if(kind==="partial"){if(covered!==null){remaining-=markFixedWrite(covered,use.identifier);if(remaining===0)return true}continue}return kind==="covering"}return false}function byteMapFor(call){const argument=call.arguments[0];if(argument===void 0||argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement){return null}const resolved=(0,const_value_1.resolveConstant)(sourceCode,argument);if(resolved===null||typeof resolved.value!=="number")return null;const size=resolved.value;if(!Number.isInteger(size)||size<=0||size>MAX_TRACKED_ALLOCATION){return null}return new Uint8Array(size)}function markFixedWrite(covered,identifier){const member=identifier.parent;if(member.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||member.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return 0}const call=member.parent;if(call.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return 0;const span=fixedWriteSpan(call,member.property.name);if(span===null||span.start<0||span.end>covered.length)return 0;let added=0;for(let index=span.start;index<span.end;index+=1){if(covered[index]===0){covered[index]=1;added+=1}}return added}function unsafeAllocator(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(!isBufferObject(callee.object))return null;const name=callee.computed?(0,const_value_1.resolveConstantString)(sourceCode,callee.property)?.value:callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property.name:void 0;return name!==void 0&&UNSAFE_ALLOCATORS.has(name)?name:null}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(variable===null||variable.defs.length===0)return null;const def=variable.defs[0];if(def.type!=="Variable"||def.node.init===null)return null;if(!isBufferObject(def.node.init))return null;const property=def.name.parent;if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property||property.computed||property.key.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return null}return UNSAFE_ALLOCATORS.has(property.key.name)?property.key.name:null}function isFilledInPlace(node){const parent=node.parent;if(parent?.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||parent.object!==node||parent.computed||parent.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||parent.property.name!=="fill"){return false}const grandparent=parent.parent;return grandparent?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&grandparent.callee===parent}return{NewExpression(node){pendingCallSites.push(node);const size=allocationSize(node);if(size!==null)pendingAllocations.push({node,size})},"Program:exit"(){for(const call of pendingCallSites)recordCallSite(call);for(const{node,size}of pendingAllocations)judgeAllocation(node,size)},CallExpression(node){pendingCallSites.push(node);const size=allocationSize(node);if(size!==null)pendingAllocations.push({node,size});const callee=node.callee;const allocator=unsafeAllocator(callee);if(allocator===null)return;if(isFilledInPlace(node))return;if(isCoveredBeforeRead(node))return;const rewritable=callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property:null;context.report({node,messageId:allocator==="allocUnsafe"?"unsafeAlloc":"unsafeAllocSlow",...rewritable===null?{}:{suggest:[{messageId:"useSafeAlloc",fix:fixer=>fixer.replaceText(rewritable,"alloc")}]}})}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeBufferAlloc=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 UNSAFE_ALLOCATORS=new Set(["allocUnsafe","allocUnsafeSlow"]);const SIZED_ALLOCATORS=new Set(["Array","Buffer","Uint8Array","Uint16Array","Uint32Array","Int8Array","Int16Array","Int32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array","ArrayBuffer","SharedArrayBuffer"]);function looksNumeric(node){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Literal:return typeof node.value==="number";case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return["+","-","*","/","%","<<",">>",">>>"].includes(node.operator);case eslint_devkit_1.AST_NODE_TYPES.Identifier:return COUNT_NAMES.has(node.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&COUNT_NAMES.has(node.property.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.object.name==="Math"){return true}const name=calleeName(node.callee);return name!==null&&/^(read|len|size|count|decode|parse)/i.test(name)}default:return false}}const COUNT_NAMES=new Set(["length","len","size","count","n","num","total","capacity","bytelength"]);const BUFFER_ALLOCATORS=new Set(["alloc","allocUnsafe","allocUnsafeSlow"]);const WIRE_NAMES=new Set(["chunk","chunks","buffer","buf","payload","frame","packet","raw","message","msg"]);const REQUEST_ROOTS=new Set(["req","request","ctx","event"]);function isWriteMethod(name){return name==="fill"||name==="set"||name.startsWith("write")}const DESTINATION_ARGUMENT_CALLS=new Set(["copy","randomFill","randomFillSync"]);const METADATA_PROPERTIES=new Set(["length","byteLength","byteOffset","buffer"]);const WRITE_WIDTHS=new Map([["writeUInt8",1],["writeInt8",1],["writeUInt16LE",2],["writeUInt16BE",2],["writeInt16LE",2],["writeInt16BE",2],["writeUInt32LE",4],["writeUInt32BE",4],["writeInt32LE",4],["writeInt32BE",4],["writeFloatLE",4],["writeFloatBE",4],["writeDoubleLE",8],["writeDoubleBE",8],["writeBigInt64LE",8],["writeBigInt64BE",8],["writeBigUInt64LE",8],["writeBigUInt64BE",8]]);const MAX_TRACKED_ALLOCATION=4096;const BUFFER_MODULES=new Set(["buffer","node:buffer"]);const CRYPTO_MODULES=new Set(["crypto","node:crypto"]);const RANDOM_FILL_CALLS=new Set(["randomFill","randomFillSync"]);function isCryptoModuleRequire(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"&&node.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.arguments[0].value==="string"&&CRYPTO_MODULES.has(node.arguments[0].value)}function isBufferModuleRequire(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"&&node.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.arguments[0].value==="string"&&BUFFER_MODULES.has(node.arguments[0].value)}function isBufferObject(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name==="Buffer";return node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.property.name==="Buffer"&&isBufferModuleRequire(node.object)}function isInsideLoop(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.ForStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.ForOfStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.ForInStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.WhileStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.DoWhileStatement){return true}current=current.parent}return false}function coversWholeBuffer(call,method){if(method==="fill")return true;const width=WRITE_WIDTHS.get(method);if(width!==void 0){const offset2=call.arguments[1];return offset2!==void 0&&offset2.type!==eslint_devkit_1.AST_NODE_TYPES.Literal&&isInsideLoop(call)}if(call.arguments.length===1)return true;const offset=call.arguments[1];return offset.type!==eslint_devkit_1.AST_NODE_TYPES.Literal&&isInsideLoop(call)}function fixedWriteSpan(call,method){const width=WRITE_WIDTHS.get(method);if(width===void 0)return null;const offset=call.arguments[1];if(offset===void 0)return{start:0,end:width};if(offset.type!==eslint_devkit_1.AST_NODE_TYPES.Literal||typeof offset.value!=="number"){return null}return{start:offset.value,end:offset.value+width}}function calleeName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed){return callee.property.name}return null}function declaredName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&&node.id)return node.id.name;const parent=node.parent;if(parent?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if((parent?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition||parent?.type===eslint_devkit_1.AST_NODE_TYPES.Property)&&!parent.computed){const key=parent.key;return key.name}return null}exports.noUnsafeBufferAlloc=(0,eslint_devkit_1.createRule)({name:"no-unsafe-buffer-alloc",meta:{type:"problem",hasSuggestions:true,docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-unsafe-buffer-alloc.md",description:"Disallow `Buffer.allocUnsafe()` and `Buffer.allocUnsafeSlow()`, which return uninitialized memory",cwe:"CWE-908",cweJustification:"CWE-908 (Use of Uninitialized Resource) \u2014 allocUnsafe returns a view over non-zeroed heap memory; any byte not overwritten before the buffer is read or transmitted discloses prior process memory.",cvss:7.5,confidence:"high"},messages:{unsafeAlloc:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Uninitialized Buffer Allocation",cwe:"CWE-908",cvss:7.5,description:"`Buffer.allocUnsafe(size)` returns memory that has not been zeroed. Every byte not overwritten before the buffer is read or sent leaks whatever the allocator previously stored there.",severity:"HIGH",fix:"Use `Buffer.alloc(size)` (zero-filled), or keep `allocUnsafe` only where the very next statement overwrites the whole buffer \u2014 `Buffer.allocUnsafe(size).fill(0)` is accepted by this rule.",documentationLink:"https://nodejs.org/api/buffer.html#static-method-bufferallocunsafesize"}),unsafeAllocSlow:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Uninitialized Buffer Allocation (allocUnsafeSlow)",cwe:"CWE-908",cvss:7.5,description:"`Buffer.allocUnsafeSlow(size)` allocates outside the shared pool but is equally uninitialized \u2014 the returned bytes are whatever was last in that memory.",severity:"HIGH",fix:"Use `Buffer.alloc(size)`, or append `.fill(0)` to zero the allocation at the call site.",documentationLink:"https://nodejs.org/api/buffer.html#static-method-bufferallocunsafeslowsize"}),useSafeAlloc:"Replace with `Buffer.alloc()` (zero-filled).",unboundedAllocation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Allocation Sized By Untrusted Input",cwe:"CWE-789",cvss:7.5,description:'The allocation size is read off the wire, so the peer picks it. For `new Array(n)` the hazard is a narrow band, not "large n": V8 keeps a packed backing store only up to ~33.5M elements, and a length just under that turns a 12-byte length prefix into a 229MB allocation (measured: `new Array(3e7)` = 228.9MB in 20.7ms). Past the threshold V8 switches the array to dictionary mode and the allocation costs nothing at all (`new Array(4e7)` and `new Array(1e9)` are both 0.0MB in ~0.005ms). A typed allocation \u2014 `Buffer.alloc(n)`, `new Uint8Array(n)` \u2014 has no such threshold and commits n bytes at any n.',severity:"HIGH",fix:'Clamp the length against the maximum the protocol actually permits, before allocating: `if (length > MAX_LENGTH) throw new Error("too long")`, or `new Array(Math.min(length, MAX_LENGTH))`. A guard that only rejects implausibly huge values is not a fix for `new Array` \u2014 those are the sizes V8 makes free. The damaging lengths are the plausible ones just below the packed-elements limit.',documentationLink:"https://cwe.mitre.org/data/definitions/789.html"})},schema:[]},defaultOptions:[],create(context){const sourceCode=context.sourceCode;const wireParams=new Map;const pendingAllocations=[];const pendingCallSites=[];function readsWire(node,depth=0){if(depth>10)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return readsWire(bare,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{if(node.name==="Buffer"){const bound=(0,provenance_1.findVariable)(sourceCode,node);if(bound===null||bound.defs.length===0)return false;if(bound.defs[0].type==="ImportBinding")return false}const lower=node.name.toLowerCase();if(WIRE_NAMES.has(lower)||REQUEST_ROOTS.has(lower)){const bound=(0,provenance_1.findVariable)(sourceCode,node);const def=bound===null?void 0:bound.defs[0];if(def!==void 0&&def.type==="Variable"&&def.node.init!==null&&def.node.init!==void 0){return readsWire(def.node.init,depth+1)}return true}const owner=enclosingFunction(node);if(owner!==null){const name=declaredName(owner);const indices=name===null?void 0:wireParams.get(name);if(indices!==void 0){const index=paramIndexOf(node,owner);if(index!==null&&indices.has(index))return true}}const variable=(0,provenance_1.findVariable)(sourceCode,node);const lastWrite=(variable?.references??[]).map(reference=>reference.writeExpr).filter(write=>write!=null).filter(write=>write.range[1]<=node.range[0]).sort((a,b)=>a.range[1]-b.range[1]).at(-1);return lastWrite!==void 0&&readsWire(lastWrite,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return readsWire(node.object,depth+1);case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&readsWire(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.CallExpression:return readsWire(node.callee,depth+1)||node.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&readsWire(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return readsWire(node.left,depth+1)||readsWire(node.right,depth+1);default:return false}}function enclosingFunction(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}function paramIndexOf(node,owner){const params=owner.params;const index=params.findIndex(param=>param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&¶m.name===node.name);return index===-1?null:index}function isClamped(size){if(size.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&size.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&size.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&size.callee.object.name==="Math"&&size.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&size.callee.property.name==="min"){return size.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&!readsWire(argument))}if(size.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=size.name;let current=size.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){if(mentionsInComparison(current.test,name))return true}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){const body=current.body;if(body.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){for(const statement of body.body){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement&&mentionsInComparison(statement.test,name)){return true}}}return false}current=current.parent}return false}function mentionsInComparison(test,name){if(test.type===eslint_devkit_1.AST_NODE_TYPES.LogicalExpression){return mentionsInComparison(test.left,name)||mentionsInComparison(test.right,name)}if(test.type!==eslint_devkit_1.AST_NODE_TYPES.BinaryExpression)return false;if(!["<","<=",">",">="].includes(test.operator))return false;const named=side=>side.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&side.name===name;return named(test.left)||named(test.right)}function allocationSize(node){const size=node.arguments[0];if(size===void 0||size.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return null;const callee=node.callee;if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&SIZED_ALLOCATORS.has(callee.name)){return looksNumeric(size)?size:null}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&BUFFER_ALLOCATORS.has(callee.property.name)){return size}return null}function recordCallSite(node){const name=calleeName(node.callee);if(name===null)return;node.arguments.forEach((argument,index)=>{if(argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return;if(!readsWire(argument))return;const indices=wireParams.get(name)??new Set;indices.add(index);wireParams.set(name,indices)})}function judgeAllocation(node,size){if(isClamped(size))return;if(!readsWire(size))return;context.report({node,messageId:"unboundedAllocation"})}function classifyUse(identifier){const parent=identifier.parent;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===identifier){if(parent.computed){const grandparent2=parent.parent;const isWrite=grandparent2.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&grandparent2.left===parent;if(!isWrite)return"read";return parent.property.type!==eslint_devkit_1.AST_NODE_TYPES.Literal&&isInsideLoop(parent)?"covering":"partial"}if(parent.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return"read";const name=parent.property.name;if(METADATA_PROPERTIES.has(name))return"metadata";const grandparent=parent.parent;if(grandparent.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||grandparent.callee!==parent||!isWriteMethod(name)){return"read"}return coversWholeBuffer(grandparent,name)?"covering":"partial"}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.arguments[0]===identifier){const method=destinationArgumentCallee(parent.callee);if(method===null)return"read";return coversWholeBuffer(parent,method)?"covering":"partial"}return"read"}function destinationArgumentCallee(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&DESTINATION_ARGUMENT_CALLS.has(callee.property.name)){return callee.property.name}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||!RANDOM_FILL_CALLS.has(callee.name)){return null}const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(variable===null||variable.defs.length===0)return null;const def=variable.defs[0];if(def.type==="ImportBinding"){const declaration=def.parent;return declaration.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration&&CRYPTO_MODULES.has(declaration.source.value)?callee.name:null}return def.type==="Variable"&&def.node.init!==null&&isCryptoModuleRequire(def.node.init)?callee.name:null}function isCoveredBeforeRead(call){const parent=call.parent;let target;let variable;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===call&&parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){target=parent.id;variable=context.sourceCode.getDeclaredVariables(parent)[0]}else if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.operator==="="&&parent.right===call&&parent.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){target=parent.left;variable=(0,provenance_1.findVariable)(context.sourceCode,target)??void 0}if(target===void 0||variable===void 0){return false}const uses=variable.references.filter(reference=>reference.identifier!==target&&reference.identifier.range[0]>call.range[1]).sort((a,b)=>a.identifier.range[0]-b.identifier.range[0]);const covered=byteMapFor(call);let remaining=covered===null?-1:covered.length;for(const use of uses){const kind=classifyUse(use.identifier);if(kind==="metadata")continue;if(kind==="partial"){if(covered!==null){remaining-=markFixedWrite(covered,use.identifier);if(remaining===0)return true}continue}return kind==="covering"}return false}function byteMapFor(call){const argument=call.arguments[0];if(argument===void 0||argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement){return null}const resolved=(0,const_value_1.resolveConstant)(sourceCode,argument);if(resolved===null||typeof resolved.value!=="number")return null;const size=resolved.value;if(!Number.isInteger(size)||size<=0||size>MAX_TRACKED_ALLOCATION){return null}return new Uint8Array(size)}function markFixedWrite(covered,identifier){const member=identifier.parent;if(member.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||member.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return 0}const call=member.parent;if(call.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return 0;const span=fixedWriteSpan(call,member.property.name);if(span===null||span.start<0||span.end>covered.length)return 0;let added=0;for(let index=span.start;index<span.end;index+=1){if(covered[index]===0){covered[index]=1;added+=1}}return added}function unsafeAllocator(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(!isBufferObject(callee.object))return null;const name=callee.computed?(0,const_value_1.resolveConstantString)(sourceCode,callee.property)?.value:callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property.name:void 0;return name!==void 0&&UNSAFE_ALLOCATORS.has(name)?name:null}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(variable===null||variable.defs.length===0)return null;const def=variable.defs[0];if(def.type!=="Variable"||def.node.init===null)return null;if(!isBufferObject(def.node.init))return null;const property=def.name.parent;if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property||property.computed||property.key.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return null}return UNSAFE_ALLOCATORS.has(property.key.name)?property.key.name:null}function isFilledInPlace(node){const parent=node.parent;if(parent?.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||parent.object!==node||parent.computed||parent.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||parent.property.name!=="fill"){return false}const grandparent=parent.parent;return grandparent?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&grandparent.callee===parent}return{NewExpression(node){pendingCallSites.push(node);const size=allocationSize(node);if(size!==null)pendingAllocations.push({node,size})},"Program:exit"(){for(const call of pendingCallSites)recordCallSite(call);for(const{node,size}of pendingAllocations)judgeAllocation(node,size)},CallExpression(node){pendingCallSites.push(node);const size=allocationSize(node);if(size!==null)pendingAllocations.push({node,size});const callee=node.callee;const allocator=unsafeAllocator(callee);if(allocator===null)return;if(isFilledInPlace(node))return;if(isCoveredBeforeRead(node))return;const rewritable=callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property:null;context.report({node,messageId:allocator==="allocUnsafe"?"unsafeAlloc":"unsafeAllocSlow",...rewritable===null?{}:{suggest:[{messageId:"useSafeAlloc",fix:fixer=>fixer.replaceText(rewritable,"alloc")}]}})}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakHashAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const names_1=require("../../utils/names");const const_value_1=require("../../utils/const-value");const WEAK_HASH_PATTERNS=[{pattern:/\bmd5\b/i,name:"MD5",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bmd4\b/i,name:"MD4",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bsha1\b/i,name:"SHA-1",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bripemd\b/i,name:"RIPEMD",alternatives:["SHA-256","SHA-512"],replacement:"sha256"}];const DEFAULT_NON_CRYPTOGRAPHIC_NAMES=["sha","etag","cachekey","cachebuster","thumbprint","x5t"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate","otp","mfa","totp","passphrase","pincode","mnemonic","seedphrase","masterkey","securityanswer","recoverycode","backupcode"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!target.computed&&target.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return target.property.name}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return node.property.name}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&¤t.id){return current.id.name}const owner=current.parent;if(owner?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return owner.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.id.name:null}if((owner?.type===eslint_devkit_1.AST_NODE_TYPES.Property||owner?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition)&&!owner.computed){return expressionName(owner.key)}return null}current=current.parent}return null}function findWeakHash(value,additionalPatterns){for(const pattern of WEAK_HASH_PATTERNS){if(pattern.pattern.test(value)){return pattern}}for(const additionalPattern of additionalPatterns){const regex=new RegExp(`\\b${additionalPattern}\\b`,"i");if(regex.test(value)){return{pattern:regex,name:additionalPattern.toUpperCase(),alternatives:["SHA-256","SHA-512"],replacement:"sha256"}}}return null}exports.noWeakHashAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-hash-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-hash-algorithm.md",description:"Disallow weak hash algorithms (MD5, SHA1, MD4)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakHashAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak hash algorithm",cwe:"CWE-327",description:"Use of weak hash algorithm: {{algorithm}}. {{algorithm}} is cryptographically broken and unsuitable for security purposes.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createHash("{{replacement}}").update(data). If this hash is an identifier rather than a security control \u2014 an EVALSHA key, an ETag, a cache key \u2014 store it under one of the nonCryptographicNames instead.',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-256",description:"Replace with SHA-256 for secure hashing",severity:"LOW",fix:'crypto.createHash("sha256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-512",description:"Replace with SHA-512 for stronger hashing",severity:"LOW",fix:'crypto.createHash("sha512").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha3:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-3",description:"Replace with SHA-3 for latest standard",severity:"LOW",fix:'crypto.createHash("sha3-256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"})},schema:[{type:"object",properties:{additionalWeakAlgorithms:{type:"array",items:{type:"string"},default:[],description:"Additional weak algorithms to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak hashes in test files"},nonCryptographicNames:{type:"array",items:{type:"string"},default:DEFAULT_NON_CRYPTOGRAPHIC_NAMES,description:"Assignment target names that mark a hash as an identifier rather than a security control"},securityUseNames:{type:"array",items:{type:"string"},default:DEFAULT_SECURITY_USE_NAMES,description:"Names that mark a hash as a security control (whole-word matched)"},reportUnclassifiedHashes:{type:"boolean",default:false,description:"Report weak hashes whose purpose cannot be determined. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{additionalWeakAlgorithms:[],allowInTests:false,nonCryptographicNames:DEFAULT_NON_CRYPTOGRAPHIC_NAMES}],create(context,[options={}]){const{additionalWeakAlgorithms=[],allowInTests=false,nonCryptographicNames=DEFAULT_NON_CRYPTOGRAPHIC_NAMES,securityUseNames=DEFAULT_SECURITY_USE_NAMES,reportUnclassifiedHashes=false}=options;const isSecurityUse=(0,names_1.makeNameTest)(securityUseNames);const isNonCryptographicName=(0,names_1.makeNameTest)(nonCryptographicNames.map(normalizeName));const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function isNonCryptographicUse(node){const stored=assignedName(node);if(stored!==null&&isNonCryptographicName(stored))return true;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isNonCryptographicName(enclosing)}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}if(stored!==null)return false;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function 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&¤t.id){return current.id.name}const owner=current.parent;if(owner?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return owner.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.id.name:null}if((owner?.type===eslint_devkit_1.AST_NODE_TYPES.Property||owner?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition)&&!owner.computed){return expressionName(owner.key)}return null}current=current.parent}return null}function findWeakHash(value,additionalPatterns){for(const pattern of WEAK_HASH_PATTERNS){if(pattern.pattern.test(value)){return pattern}}for(const additionalPattern of additionalPatterns){const regex=new RegExp(`\\b${additionalPattern}\\b`,"i");if(regex.test(value)){return{pattern:regex,name:additionalPattern.toUpperCase(),alternatives:["SHA-256","SHA-512"],replacement:"sha256"}}}return null}exports.noWeakHashAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-hash-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-hash-algorithm.md",description:"Disallow weak hash algorithms (MD5, SHA1, MD4)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakHashAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak hash algorithm",cwe:"CWE-327",description:"Use of weak hash algorithm: {{algorithm}}. {{algorithm}} is cryptographically broken and unsuitable for security purposes.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createHash("{{replacement}}").update(data). If this hash is an identifier rather than a security control \u2014 an EVALSHA key, an ETag, a cache key \u2014 store it under one of the nonCryptographicNames instead.',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-256",description:"Replace with SHA-256 for secure hashing",severity:"LOW",fix:'crypto.createHash("sha256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-512",description:"Replace with SHA-512 for stronger hashing",severity:"LOW",fix:'crypto.createHash("sha512").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha3:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-3",description:"Replace with SHA-3 for latest standard",severity:"LOW",fix:'crypto.createHash("sha3-256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"})},schema:[{type:"object",properties:{additionalWeakAlgorithms:{type:"array",items:{type:"string"},default:[],description:"Additional weak algorithms to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak hashes in test files"},nonCryptographicNames:{type:"array",items:{type:"string"},default:DEFAULT_NON_CRYPTOGRAPHIC_NAMES,description:"Assignment target names that mark a hash as an identifier rather than a security control"},securityUseNames:{type:"array",items:{type:"string"},default:DEFAULT_SECURITY_USE_NAMES,description:"Names that mark a hash as a security control (whole-word matched)"},reportUnclassifiedHashes:{type:"boolean",default:false,description:"Report weak hashes whose purpose cannot be determined. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{additionalWeakAlgorithms:[],allowInTests:false,nonCryptographicNames:DEFAULT_NON_CRYPTOGRAPHIC_NAMES}],create(context,[options={}]){const{additionalWeakAlgorithms=[],allowInTests=false,nonCryptographicNames=DEFAULT_NON_CRYPTOGRAPHIC_NAMES,securityUseNames=DEFAULT_SECURITY_USE_NAMES,reportUnclassifiedHashes=false}=options;const isSecurityUse=(0,names_1.makeNameTest)(securityUseNames);const isNonCryptographicName=(0,names_1.makeNameTest)(nonCryptographicNames.map(normalizeName));const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function isNonCryptographicUse(node){const stored=assignedName(node);if(stored!==null&&isNonCryptographicName(stored))return true;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isNonCryptographicName(enclosing)}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}if(stored!==null)return false;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function isLocalHmacHelper(callee){const scope=context.sourceCode.getScope(callee);for(let current=scope;current;current=current.upper){const variable=current.variables.find(v=>v.name===callee.name);if(!variable)continue;return variable.defs.some(def=>def.type!=="ImportBinding"&&computesHmac(def.node))}return false}function computesHmac(root){const stack=[root];while(stack.length>0){const node=stack.pop();if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&isCreateHmacCall(node)){return true}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){stack.push(child)}}}}return false}function isCreateHmacCall(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.name==="createHmac"}return callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!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}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noZipSlip=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const DEFAULT_ARCHIVE_MODULES=["adm-zip","unzipper","yauzl","yazl","tar","tar-fs","tar-stream","extract-zip","node-stream-zip","jszip","archiver","decompress","unzip-stream","zip-stream","gunzip-maybe","7zip-min","node-7z"];const containsPathTraversal=pathText=>{return/\.\.\//.test(pathText)||/\.\.\\/.test(pathText)||pathText.startsWith("..")||/\/\.\./.test(pathText)};const isDangerousDestination=destText=>{if(destText.startsWith("/tmp")||destText.includes("os.tmpdir")||destText.includes("TMPDIR")){return false}return destText.includes("/var")||destText.includes("/usr")||destText.includes("/etc")||destText.includes("/root")||destText.includes("/home")||destText.includes("C:\\Windows")||destText.includes("C:\\Program Files")||destText.includes("C:\\Users")};exports.noZipSlip=(0,eslint_devkit_1.createRule)({name:"no-zip-slip",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-zip-slip.md",description:"Detects zip slip/archive extraction vulnerabilities",cwe:"CWE-22"},messages:{unsafeArchiveExtraction:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Unsafe Archive Extraction",cwe:"CWE-22",description:"Archive extraction without path validation",severity:"HIGH",fix:"Use safe extraction libraries or validate all paths",documentationLink:"https://snyk.io/research/zip-slip-vulnerability"}),pathTraversalInArchive:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Path Traversal in Archive",cwe:"CWE-22",description:"Archive contains path traversal sequences",severity:"CRITICAL",fix:"Reject archives with path traversal or sanitize paths",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"}),unvalidatedArchivePath:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Unvalidated Archive Path",cwe:"CWE-22",description:"Archive entry path used without validation",severity:"HIGH",fix:"Validate paths before extraction",documentationLink:"https://snyk.io/research/zip-slip-vulnerability"}),dangerousArchiveDestination:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Dangerous Archive Destination",cwe:"CWE-22",description:"Archive extracted to sensitive location",severity:"MEDIUM",fix:"Extract to safe temporary directory",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"})},schema:[{type:"object",properties:{archiveFunctions:{type:"array",items:{type:"string"},default:["extract","extractAll","extractAllTo","unzip","untar","extractArchive"]},pathValidationFunctions:{type:"array",items:{type:"string"},default:["validatePath","sanitizePath","checkPath","safePath"]},safeLibraries:{type:"array",items:{type:"string"},default:["yauzl","safe-archive-extract","tar-stream","unzipper"]},archiveModules:{type:"array",items:{type:"string"},default:DEFAULT_ARCHIVE_MODULES,description:"Module specifiers that mean this file works with archives"},reportWithoutArchiveContext:{type:"boolean",default:false,description:"Report entry-name and traversal shapes in files with no archive. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{archiveFunctions:["extract","extractAll","extractAllTo","unzip","untar","extractArchive"],pathValidationFunctions:["validatePath","sanitizePath","checkPath","safePath"],safeLibraries:["yauzl","safe-archive-extract","tar-stream","unzipper"]}],create(context){const options=context.options[0]||{};const{archiveFunctions=["extract","extractAll","extractAllTo","unzip","untar","extractArchive"],pathValidationFunctions=["validatePath","sanitizePath","checkPath","safePath"],safeLibraries=["yauzl","safe-archive-extract","tar-stream","unzipper"],archiveModules=DEFAULT_ARCHIVE_MODULES,reportWithoutArchiveContext=false}=options;const filename=context.filename;const archiveModuleSet=new Set(archiveModules.map(name=>name.toLowerCase()));let hasArchiveContext=reportWithoutArchiveContext;const isArchiveModule=specifier=>typeof specifier==="string"&&archiveModuleSet.has(specifier.toLowerCase());const ARCHIVE_NAME=/zip|tarball|archive|gzip|gunzip|untar|tarstream|\btar\b/i;const namesArchive=node=>{if(node.type==="Identifier")return ARCHIVE_NAME.test(node.name);if(node.type==="MemberExpression"){return namesArchive(node.object)||node.property.type==="Identifier"&&ARCHIVE_NAME.test(node.property.name)}return false};const pending=[];const isArchiveExtraction=node=>{const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&archiveFunctions.includes(callee.property.name)){return true}if(callee.type==="Identifier"&&archiveFunctions.includes(callee.name)){return true}return false};const isPathValidated=pathNode=>{let current=pathNode;while(current){if(current.type==="CallExpression"&¤t.callee.type==="Identifier"&&pathValidationFunctions.includes(current.callee.name)){return true}if(current.type==="CallExpression"&¤t.callee.type==="MemberExpression"&¤t.callee.object.type==="Identifier"&¤t.callee.object.name==="path"&¤t.callee.property.type==="Identifier"&¤t.callee.property.name==="basename"){return true}if(current.type==="IfStatement"){const test=current.test;if(test.type==="CallExpression"&&test.callee.type==="MemberExpression"&&test.callee.property.type==="Identifier"&&test.callee.property.name==="startsWith"){return true}if(test.type==="UnaryExpression"&&test.operator==="!"&&test.argument.type==="CallExpression"&&test.argument.callee.type==="MemberExpression"&&test.argument.callee.property.type==="Identifier"&&test.argument.callee.property.name==="startsWith"){return true}if(test.type==="CallExpression"&&test.callee.type==="MemberExpression"&&test.callee.property.type==="Identifier"&&test.callee.property.name==="includes"){return true}}current=current.parent}return false};const isSafeLibrary=node=>{const callee=node.callee;if(callee.type==="MemberExpression"&&callee.object.type==="Identifier"&&safeLibraries.includes(callee.object.name)){return true}if(callee.type==="Identifier"){const name=callee.name.toLowerCase();if(name==="extract"||name==="unzipper"||safeLibraries.some(lib=>name.includes(lib.toLowerCase()))){return true}}return false};return{ImportDeclaration(node){if(isArchiveModule(node.source.value))hasArchiveContext=true},NewExpression(node){if(namesArchive(node.callee))hasArchiveContext=true},"Program:exit"(){for(const report of pending)report()},CallExpression(node){if(node.callee.type==="Identifier"&&node.callee.name==="require"&&node.arguments[0]?.type==="Literal"&&isArchiveModule(node.arguments[0].value)){hasArchiveContext=true}if(isArchiveExtraction(node)||namesArchive(node.callee))hasArchiveContext=true;if(isArchiveExtraction(node)&&!isSafeLibrary(node)){const sourceCode=context.sourceCode;let hasSafeAnnotation=false;const allComments=sourceCode.getAllComments();for(const comment of allComments){if(comment.type==="Block"&&comment.value.includes("@safe")){hasSafeAnnotation=true;break}}if(hasSafeAnnotation){return}const args=node.arguments;let destArg;if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const methodName=node.callee.property.name;if(["extractAllTo","unzip"].includes(methodName)){destArg=args[0]}else{destArg=args.length>=2?args[1]:void 0}}else{destArg=args.length>=2?args[1]:void 0}const destText=destArg&&destArg.type==="Literal"&&typeof destArg.value==="string"?destArg.value:"";const isDestDangerous=isDangerousDestination(destText);const isMethodCall=node.callee.type==="MemberExpression";if(isMethodCall){const isSafeRelativePath=destText.startsWith("./")||destText.startsWith("../");if(!isSafeRelativePath){context.report({node,messageId:"unsafeArchiveExtraction",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}if(isDestDangerous&&destArg){context.report({node:destArg,messageId:"dangerousArchiveDestination",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}else{if(isDestDangerous){context.report({node,messageId:"dangerousArchiveDestination",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}else{context.report({node,messageId:"unsafeArchiveExtraction",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&["join","resolve","relative","normalize"].includes(callee.property.name)){const args=node.arguments;for(const arg of args){if(arg.type==="MemberExpression"&&arg.property.type==="Identifier"&&["name","path","fileName","entryName","relativePath","filename","pathname"].includes(arg.property.name)){if(!isPathValidated(arg)){pending.push(()=>{if(!hasArchiveContext)return;context.report({node:arg,messageId:"unvalidatedArchivePath",data:{filePath:filename,line:String(node.loc?.start.line??0)}})})}}}}},Literal(node){if(typeof node.value!=="string"){return}const text=node.value;if(!(text.includes("/")||text.includes("\\"))||!containsPathTraversal(text)){return}let current=node;while(current){if(current.type==="CallExpression"&&isArchiveExtraction(current)){context.report({node,messageId:"pathTraversalInArchive",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}current=current.parent}},VariableDeclarator(node){if(!node.init||node.id.type!=="Identifier"){return}const varName=node.id.name.toLowerCase();if(varName.includes("entry")||varName.includes("file")||varName.includes("path")){if(node.init.type==="MemberExpression"&&node.init.property.type==="Identifier"&&["name","path"].includes(node.init.property.name)){}}}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noZipSlip=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const AMBIGUOUS_EXTRACTORS=new Set(["extract","extractAll","unzip","untar"]);const DEFAULT_ARCHIVE_MODULES=["adm-zip","unzipper","yauzl","yazl","tar","tar-fs","tar-stream","extract-zip","node-stream-zip","jszip","archiver","decompress","unzip-stream","zip-stream","gunzip-maybe","7zip-min","node-7z"];const containsPathTraversal=pathText=>{return/\.\.\//.test(pathText)||/\.\.\\/.test(pathText)||pathText.startsWith("..")||/\/\.\./.test(pathText)};const isDangerousDestination=destText=>{if(destText.startsWith("/tmp")||destText.includes("os.tmpdir")||destText.includes("TMPDIR")){return false}return destText.includes("/var")||destText.includes("/usr")||destText.includes("/etc")||destText.includes("/root")||destText.includes("/home")||destText.includes("C:\\Windows")||destText.includes("C:\\Program Files")||destText.includes("C:\\Users")};exports.noZipSlip=(0,eslint_devkit_1.createRule)({name:"no-zip-slip",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-zip-slip.md",description:"Detects zip slip/archive extraction vulnerabilities",cwe:"CWE-22"},messages:{unsafeArchiveExtraction:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Unsafe Archive Extraction",cwe:"CWE-22",description:"Archive extraction without path validation",severity:"HIGH",fix:"Use safe extraction libraries or validate all paths",documentationLink:"https://snyk.io/research/zip-slip-vulnerability"}),pathTraversalInArchive:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Path Traversal in Archive",cwe:"CWE-22",description:"Archive contains path traversal sequences",severity:"CRITICAL",fix:"Reject archives with path traversal or sanitize paths",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"}),unvalidatedArchivePath:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Unvalidated Archive Path",cwe:"CWE-22",description:"Archive entry path used without validation",severity:"HIGH",fix:"Validate paths before extraction",documentationLink:"https://snyk.io/research/zip-slip-vulnerability"}),dangerousArchiveDestination:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Dangerous Archive Destination",cwe:"CWE-22",description:"Archive extracted to sensitive location",severity:"MEDIUM",fix:"Extract to safe temporary directory",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"})},schema:[{type:"object",properties:{archiveFunctions:{type:"array",items:{type:"string"},default:["extract","extractAll","extractAllTo","unzip","untar","extractArchive"]},pathValidationFunctions:{type:"array",items:{type:"string"},default:["validatePath","sanitizePath","checkPath","safePath"]},safeLibraries:{type:"array",items:{type:"string"},default:["yauzl","safe-archive-extract","tar-stream","unzipper"]},archiveModules:{type:"array",items:{type:"string"},default:DEFAULT_ARCHIVE_MODULES,description:"Module specifiers that mean this file works with archives"},reportWithoutArchiveContext:{type:"boolean",default:false,description:"Report entry-name and traversal shapes in files with no archive. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{archiveFunctions:["extract","extractAll","extractAllTo","unzip","untar","extractArchive"],pathValidationFunctions:["validatePath","sanitizePath","checkPath","safePath"],safeLibraries:["yauzl","safe-archive-extract","tar-stream","unzipper"]}],create(context){const options=context.options[0]||{};const{archiveFunctions=["extract","extractAll","extractAllTo","unzip","untar","extractArchive"],pathValidationFunctions=["validatePath","sanitizePath","checkPath","safePath"],safeLibraries=["yauzl","safe-archive-extract","tar-stream","unzipper"],archiveModules=DEFAULT_ARCHIVE_MODULES,reportWithoutArchiveContext=false}=options;const filename=context.filename;const archiveModuleSet=new Set(archiveModules.map(name=>name.toLowerCase()));let hasArchiveContext=reportWithoutArchiveContext;const isArchiveModule=specifier=>typeof specifier==="string"&&archiveModuleSet.has(specifier.toLowerCase());const ARCHIVE_NAME=/zip|tarball|archive|gzip|gunzip|untar|tarstream|\btar\b/i;const namesArchive=node=>{if(node.type==="Identifier")return ARCHIVE_NAME.test(node.name);if(node.type==="MemberExpression"){return namesArchive(node.object)||node.property.type==="Identifier"&&ARCHIVE_NAME.test(node.property.name)}return false};const pending=[];const isArchiveExtraction=node=>{const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&archiveFunctions.includes(callee.property.name)){if(!AMBIGUOUS_EXTRACTORS.has(callee.property.name))return true;return namesArchive(callee.object)}if(callee.type==="Identifier"&&archiveFunctions.includes(callee.name)){return true}return false};const isPathValidated=pathNode=>{let current=pathNode;while(current){if(current.type==="CallExpression"&¤t.callee.type==="Identifier"&&pathValidationFunctions.includes(current.callee.name)){return true}if(current.type==="CallExpression"&¤t.callee.type==="MemberExpression"&¤t.callee.object.type==="Identifier"&¤t.callee.object.name==="path"&¤t.callee.property.type==="Identifier"&¤t.callee.property.name==="basename"){return true}if(current.type==="IfStatement"){const test=current.test;if(test.type==="CallExpression"&&test.callee.type==="MemberExpression"&&test.callee.property.type==="Identifier"&&test.callee.property.name==="startsWith"){return true}if(test.type==="UnaryExpression"&&test.operator==="!"&&test.argument.type==="CallExpression"&&test.argument.callee.type==="MemberExpression"&&test.argument.callee.property.type==="Identifier"&&test.argument.callee.property.name==="startsWith"){return true}if(test.type==="CallExpression"&&test.callee.type==="MemberExpression"&&test.callee.property.type==="Identifier"&&test.callee.property.name==="includes"){return true}}current=current.parent}return false};const isSafeLibrary=node=>{const callee=node.callee;if(callee.type==="MemberExpression"&&callee.object.type==="Identifier"&&safeLibraries.includes(callee.object.name)){return true}if(callee.type==="Identifier"){const name=callee.name.toLowerCase();if(name==="extract"||name==="unzipper"||safeLibraries.some(lib=>name.includes(lib.toLowerCase()))){return true}}return false};return{ImportDeclaration(node){if(isArchiveModule(node.source.value))hasArchiveContext=true},NewExpression(node){if(namesArchive(node.callee))hasArchiveContext=true},"Program:exit"(){for(const report of pending)report()},CallExpression(node){if(node.callee.type==="Identifier"&&node.callee.name==="require"&&node.arguments[0]?.type==="Literal"&&isArchiveModule(node.arguments[0].value)){hasArchiveContext=true}if(namesArchive(node.callee))hasArchiveContext=true;if(isArchiveExtraction(node)&&!isSafeLibrary(node)){const sourceCode=context.sourceCode;let hasSafeAnnotation=false;const allComments=sourceCode.getAllComments();for(const comment of allComments){if(comment.type==="Block"&&comment.value.includes("@safe")){hasSafeAnnotation=true;break}}if(hasSafeAnnotation){return}const args=node.arguments;let destArg;if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const methodName=node.callee.property.name;if(["extractAllTo","unzip"].includes(methodName)){destArg=args[0]}else{destArg=args.length>=2?args[1]:void 0}}else{destArg=args.length>=2?args[1]:void 0}const destText=destArg&&destArg.type==="Literal"&&typeof destArg.value==="string"?destArg.value:"";const isDestDangerous=isDangerousDestination(destText);const isMethodCall=node.callee.type==="MemberExpression";if(isMethodCall){const isSafeRelativePath=destText.startsWith("./")||destText.startsWith("../");if(!isSafeRelativePath){context.report({node,messageId:"unsafeArchiveExtraction",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}if(isDestDangerous&&destArg){context.report({node:destArg,messageId:"dangerousArchiveDestination",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}else{if(isDestDangerous){context.report({node,messageId:"dangerousArchiveDestination",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}else{context.report({node,messageId:"unsafeArchiveExtraction",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&["join","resolve","relative","normalize"].includes(callee.property.name)){const args=node.arguments;for(const arg of args){if(arg.type==="MemberExpression"&&arg.property.type==="Identifier"&&["name","path","fileName","entryName","relativePath","filename","pathname"].includes(arg.property.name)){if(!isPathValidated(arg)){pending.push(()=>{if(!hasArchiveContext)return;context.report({node:arg,messageId:"unvalidatedArchivePath",data:{filePath:filename,line:String(node.loc?.start.line??0)}})})}}}}},Literal(node){if(typeof node.value!=="string"){return}const text=node.value;if(!(text.includes("/")||text.includes("\\"))||!containsPathTraversal(text)){return}let current=node;while(current){if(current.type==="CallExpression"&&isArchiveExtraction(current)){context.report({node,messageId:"pathTraversalInArchive",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}current=current.parent}},VariableDeclarator(node){if(!node.init||node.id.type!=="Identifier"){return}const varName=node.id.name.toLowerCase();if(varName.includes("entry")||varName.includes("file")||varName.includes("path")){if(node.init.type==="MemberExpression"&&node.init.property.type==="Identifier"&&["name","path"].includes(node.init.property.name)){}}}}}});
|