eslint-plugin-node-security 5.3.0 → 5.4.0

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.
Files changed (36) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -2
  3. package/src/index.js +1 -1
  4. package/src/rules/detect-child-process/index.js +1 -1
  5. package/src/rules/detect-eval-with-expression/index.js +1 -1
  6. package/src/rules/detect-non-literal-fs-filename/index.js +1 -1
  7. package/src/rules/no-arbitrary-file-access/index.js +1 -1
  8. package/src/rules/no-buffer-overread/index.js +1 -1
  9. package/src/rules/no-data-in-temp-storage/index.js +1 -1
  10. package/src/rules/no-deprecated-buffer/index.js +1 -1
  11. package/src/rules/no-deprecated-cipher-method/index.js +1 -1
  12. package/src/rules/no-dynamic-algorithm-selection/index.js +1 -1
  13. package/src/rules/no-dynamic-command-string/index.js +1 -1
  14. package/src/rules/no-dynamic-dependency-loading/index.js +1 -1
  15. package/src/rules/no-ecb-mode/index.js +1 -1
  16. package/src/rules/no-env-injection/index.js +1 -1
  17. package/src/rules/no-insecure-key-derivation/index.js +1 -1
  18. package/src/rules/no-insecure-rsa-padding/index.js +1 -1
  19. package/src/rules/no-math-random-crypto/index.js +1 -1
  20. package/src/rules/no-self-signed-certs/index.js +1 -1
  21. package/src/rules/no-shell-injection/index.js +1 -1
  22. package/src/rules/no-ssrf/index.js +1 -1
  23. package/src/rules/no-timing-unsafe-compare/index.js +1 -1
  24. package/src/rules/no-toctou-vulnerability/index.js +1 -1
  25. package/src/rules/no-unbounded-decompression/index.js +1 -1
  26. package/src/rules/no-unsafe-buffer-alloc/index.js +1 -1
  27. package/src/rules/no-unsafe-dynamic-require/index.js +1 -1
  28. package/src/rules/no-weak-cipher-algorithm/index.js +1 -1
  29. package/src/rules/no-weak-hash-algorithm/index.js +1 -1
  30. package/src/rules/no-zip-slip/index.js +1 -1
  31. package/src/rules/require-aead-tag-verification/index.js +1 -1
  32. package/src/rules/require-secure-credential-storage/index.js +1 -1
  33. package/src/rules/require-secure-deletion/index.js +1 -1
  34. package/src/rules/require-stream-error-handler/index.js +1 -1
  35. package/src/utils/credential-evidence.js +1 -1
  36. package/src/utils/provenance.js +1 -1
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnboundedDecompression=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const ZLIB_MODULE=/^(node:)?zlib$/;const ASYNC_DECOMPRESSORS=new Set(["gunzip","inflate","inflateRaw","unzip","brotliDecompress","zstdDecompress"]);const SYNC_DECOMPRESSORS=new Set(["gunzipSync","inflateSync","inflateRawSync","unzipSync","brotliDecompressSync","zstdDecompressSync"]);exports.noUnboundedDecompression=(0,eslint_devkit_1.createRule)({name:"no-unbounded-decompression",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-unbounded-decompression.md",description:"Require a maxOutputLength ceiling on zlib one-shot decompression",cwe:"CWE-409",cvss:7.5},hasSuggestions:false,messages:{unboundedDecompression:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unbounded decompression",cwe:"CWE-409",description:"zlib.{{fn}}() buffers the whole decompressed result in memory with no maxOutputLength cap. A few KB of crafted input can expand to gigabytes and exhaust the heap (decompression bomb).",severity:"HIGH",fix:"Pass an explicit ceiling: zlib.{{fn}}(input, { maxOutputLength: 10 * 1024 * 1024 }, \u2026)",documentationLink:"https://nodejs.org/api/zlib.html#class-options"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow unbounded decompression in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename);const namespaceBindings=new Set;const directBindings=new Map;const pending=[];function noteDirect(local,imported){if(ASYNC_DECOMPRESSORS.has(imported)||SYNC_DECOMPRESSORS.has(imported)){directBindings.set(local,imported)}}function requiredModule(init){if(init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.callee.name==="require"&&init.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof init.arguments[0].value==="string"){return init.arguments[0].value}return null}function decompressorName(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&!callee.computed&&namespaceBindings.has(callee.object.name)){const name=callee.property.name;return ASYNC_DECOMPRESSORS.has(name)||SYNC_DECOMPRESSORS.has(name)?name:null}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return directBindings.get(callee.name)??null}return null}function isLiteralPayload(argument){if(argument.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return true;return argument.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&argument.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&argument.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&argument.callee.object.name==="Buffer"&&argument.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal}function outputCap(options_){let capped=false;for(const property of options_.properties){if(property.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return"unknown";if(property.computed)continue;const named=property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.key.name:property.key.value;if(named==="maxOutputLength")capped=true}return capped?"capped":"uncapped"}function judge(node){const fn=decompressorName(node);if(fn===null)return;const args=node.arguments;const payload=args[0];if(payload===void 0)return;if(isLiteralPayload(payload))return;const isAsync=ASYNC_DECOMPRESSORS.has(fn);if(isAsync&&args.length<2)return;const candidates=isAsync?args.slice(1,args.length-1):args.slice(1);if(candidates.length>1)return;const optionsArgument=candidates[0];if(optionsArgument!==void 0){if(optionsArgument.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;if(outputCap(optionsArgument)!=="uncapped")return}context.report({node,messageId:"unboundedDecompression",data:{fn}})}return{ImportDeclaration(node){if(!ZLIB_MODULE.test(node.source.value))return;for(const specifier of node.specifiers){if(specifier.type===eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier){const imported=specifier.imported.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?specifier.imported.name:specifier.imported.value;noteDirect(specifier.local.name,imported);continue}namespaceBindings.add(specifier.local.name)}},VariableDeclarator(node){if(!node.init)return;const source=requiredModule(node.init);if(source===null||!ZLIB_MODULE.test(source))return;if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){namespaceBindings.add(node.id.name);return}if(node.id.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectPattern)return;for(const property of node.id.properties){if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;if(property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){noteDirect(property.value.name,property.key.name)}}},CallExpression(node){if(isTestFile)return;pending.push({node})},"Program:exit"(){for(const{node}of pending)judge(node)}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnboundedDecompression=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const ZLIB_MODULE=/^(node:)?zlib$/;const ASYNC_DECOMPRESSORS=new Set(["gunzip","inflate","inflateRaw","unzip","brotliDecompress","zstdDecompress"]);const SYNC_DECOMPRESSORS=new Set(["gunzipSync","inflateSync","inflateRawSync","unzipSync","brotliDecompressSync","zstdDecompressSync"]);exports.noUnboundedDecompression=(0,eslint_devkit_1.createRule)({name:"no-unbounded-decompression",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-unbounded-decompression.md",description:"Require a maxOutputLength ceiling on zlib one-shot decompression",cwe:"CWE-409",cvss:7.5},hasSuggestions:false,messages:{unboundedDecompression:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unbounded decompression",cwe:"CWE-409",description:"zlib.{{fn}}() buffers the whole decompressed result in memory with no maxOutputLength cap. A few KB of crafted input can expand to gigabytes and exhaust the heap (decompression bomb).",severity:"HIGH",fix:"Pass an explicit ceiling: zlib.{{fn}}(input, { maxOutputLength: 10 * 1024 * 1024 }, \u2026)",documentationLink:"https://nodejs.org/api/zlib.html#class-options"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow unbounded decompression in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename);const namespaceBindings=new Set;const directBindings=new Map;const pending=[];function noteDirect(local,imported){if(ASYNC_DECOMPRESSORS.has(imported)||SYNC_DECOMPRESSORS.has(imported)){directBindings.set(local,imported)}}function requiredModule(init){if(init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.callee.name==="require"&&init.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof init.arguments[0].value==="string"){return init.arguments[0].value}return null}function decompressorName(node){const callee=node.callee;const name=(0,eslint_devkit_1.memberPropertyName)(callee);if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&name!==null&&namespaceBindings.has(callee.object.name)){return ASYNC_DECOMPRESSORS.has(name)||SYNC_DECOMPRESSORS.has(name)?name:null}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return directBindings.get(callee.name)??null}return null}function isLiteralPayload(argument){if(argument.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return true;return argument.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&argument.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&argument.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&argument.callee.object.name==="Buffer"&&argument.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal}function outputCap(options_){let capped=false;for(const property of options_.properties){if(property.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return"unknown";if(property.computed)continue;const named=property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.key.name:property.key.value;if(named==="maxOutputLength")capped=true}return capped?"capped":"uncapped"}function judge(node){const fn=decompressorName(node);if(fn===null)return;const args=node.arguments;const payload=args[0];if(payload===void 0)return;if(isLiteralPayload(payload))return;const isAsync=ASYNC_DECOMPRESSORS.has(fn);if(isAsync&&args.length<2)return;const candidates=isAsync?args.slice(1,args.length-1):args.slice(1);if(candidates.length>1)return;const optionsArgument=candidates[0];if(optionsArgument!==void 0){if(optionsArgument.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;if(outputCap(optionsArgument)!=="uncapped")return}context.report({node,messageId:"unboundedDecompression",data:{fn}})}return{ImportDeclaration(node){if(!ZLIB_MODULE.test(node.source.value))return;for(const specifier of node.specifiers){if(specifier.type===eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier){const imported=specifier.imported.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?specifier.imported.name:specifier.imported.value;noteDirect(specifier.local.name,imported);continue}namespaceBindings.add(specifier.local.name)}},VariableDeclarator(node){if(!node.init)return;const source=requiredModule(node.init);if(source===null||!ZLIB_MODULE.test(source))return;if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){namespaceBindings.add(node.id.name);return}if(node.id.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectPattern)return;for(const property of node.id.properties){if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;if(property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){noteDirect(property.value.name,property.key.name)}}},CallExpression(node){if(isTestFile)return;pending.push({node})},"Program:exit"(){for(const{node}of pending)judge(node)}}}});
@@ -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,countNames){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 countNames.has(node.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{const prop=(0,eslint_devkit_1.propertyName)(node);return prop!==null&&countNames.has(prop.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}}function toLowerSet(names){return new Set(names.map(n=>n.toLowerCase()))}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:[{type:"object",properties:{wireNames:{type:"array",items:{type:"string"},default:[...WIRE_NAMES],description:"Binding names that carry bytes off the wire. Replaces the default; pass [] to turn the name arm off."},requestRootNames:{type:"array",items:{type:"string"},default:[...REQUEST_ROOTS],description:"Root identifiers treated as a request. Replaces the default."},countNames:{type:"array",items:{type:"string"},default:[...COUNT_NAMES],description:"Identifiers that hold an allocation size rather than a payload. Replaces the default."}},additionalProperties:false}]},defaultOptions:[{}],create(context,[options={}]){const sourceCode=context.sourceCode;const vocab={wireNames:toLowerSet(options.wireNames??[...WIRE_NAMES]),requestRoots:toLowerSet(options.requestRootNames??[...REQUEST_ROOTS]),countNames:toLowerSet(options.countNames??[...COUNT_NAMES])};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(vocab.wireNames.has(lower)||vocab.requestRoots.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:if((0,eslint_devkit_1.readsRequestShape)(node,sourceCode))return true;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&&param.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,vocab.countNames)?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
+ "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,countNames){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 countNames.has(node.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{const prop=(0,eslint_devkit_1.propertyName)(node);return prop!==null&&countNames.has(prop.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}}function toLowerSet(names){return new Set(names.map(n=>n.toLowerCase()))}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&&(0,eslint_devkit_1.propertyName)(node)==="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){if(!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.PrivateIdentifier){return callee.property.name}return(0,eslint_devkit_1.propertyName)(callee)}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:[{type:"object",properties:{wireNames:{type:"array",items:{type:"string"},default:[...WIRE_NAMES],description:"Binding names that carry bytes off the wire. Replaces the default; pass [] to turn the name arm off."},requestRootNames:{type:"array",items:{type:"string"},default:[...REQUEST_ROOTS],description:"Root identifiers treated as a request. Replaces the default."},countNames:{type:"array",items:{type:"string"},default:[...COUNT_NAMES],description:"Identifiers that hold an allocation size rather than a payload. Replaces the default."}},additionalProperties:false}]},defaultOptions:[{}],create(context,[options={}]){const sourceCode=context.sourceCode;const vocab={wireNames:toLowerSet(options.wireNames??[...WIRE_NAMES]),requestRoots:toLowerSet(options.requestRootNames??[...REQUEST_ROOTS]),countNames:toLowerSet(options.countNames??[...COUNT_NAMES])};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(vocab.wireNames.has(lower)||vocab.requestRoots.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:if((0,eslint_devkit_1.readsRequestShape)(node,sourceCode))return true;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&&param.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"&&(0,eslint_devkit_1.propertyName)(size.callee)==="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,vocab.countNames)?size:null}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),BUFFER_ALLOCATORS)){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&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),DESTINATION_ARGUMENT_CALLS)){return(0,eslint_devkit_1.propertyName)(callee)}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.noUnsafeDynamicRequire=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const const_value_1=require("../../utils/const-value");const DEFAULT_TAINT_SOURCES=["req","request","ctx","event","process"];exports.noUnsafeDynamicRequire=(0,eslint_devkit_2.createRule)({name:"no-unsafe-dynamic-require",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-unsafe-dynamic-require.md",description:"Prevent unsafe dynamic require() calls that could enable code injection",cwe:"CWE-95",cvss:9.8},hasSuggestions:false,messages:{unsafeDynamicRequire:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic require()",cwe:"CWE-95",description:"Dynamic require() detected",severity:"CRITICAL",fix:'Use allowlist: const ALLOWED = ["mod1", "mod2"]; if (!ALLOWED.includes(name)) throw Error("Not allowed")',documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),unsafeDynamicImport:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic import()",cwe:"CWE-95",description:"import() resolves and EVALUATES the module it is given, exactly as require() does. This specifier is reachable from request or process input, so an attacker chooses which file executes.",severity:"CRITICAL",fix:'Use allowlist: const ALLOWED = { csv: "./formatters/csv" }; const specifier = ALLOWED[name]; if (!specifier) throw Error("Not allowed"); await import(specifier)',documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"})},schema:[{type:"object",properties:{allowDynamicImport:{type:"boolean",default:false},taintSources:{type:"array",items:{type:"string"},default:DEFAULT_TAINT_SOURCES,description:"Identifier roots treated as attacker-reachable (default: req, request, ctx, event, process)"},reportUnresolvedSpecifiers:{type:"boolean",default:false,description:'Report specifiers whose provenance cannot be resolved. Restores the pre-inversion "any non-literal is dangerous" behaviour.'}},additionalProperties:false}]},defaultOptions:[{allowDynamicImport:false}],create(context){const options=context.options[0]??{};const readsTaintSource=(0,provenance_1.makeReadsTaintSource)(context.sourceCode,new Set((options.taintSources??DEFAULT_TAINT_SOURCES).map(s=>s.toLowerCase())));const reportUnresolvedSpecifiers=options.reportUnresolvedSpecifiers??false;const allowDynamicImport=options.allowDynamicImport??false;const requireVariables=new Set;const isRequireReference=node=>{if(node.type==="Identifier"&&node.name==="require"){return true}if(node.type==="Identifier"&&requireVariables.has(node.name)){return true}return node.type==="MemberExpression"&&!node.computed&&node.object.type==="Identifier"&&node.object.name==="module"&&node.property.type==="Identifier"&&node.property.name==="require"};const isDangerousSpecifier=arg=>{if(arg.type==="Literal")return false;if(arg.type==="TemplateLiteral"&&arg.expressions.length===0)return false;if((0,const_value_1.resolveConstantString)(context.sourceCode,arg)!==null)return false;if(arg.type==="Identifier"){const variable=(0,provenance_1.findVariable)(context.sourceCode,arg);if(variable?.defs.length===1&&variable.defs[0].type==="Parameter"){return reportUnresolvedSpecifiers}}if(readsTaintSource(arg))return true;return reportUnresolvedSpecifiers};return{VariableDeclarator(node){if(node.id.type==="Identifier"&&node.init){if(node.init.type==="Identifier"&&node.init.name==="require"){requireVariables.add(node.id.name)}if(node.init.type==="CallExpression"&&node.init.callee.type==="Identifier"&&node.init.callee.name==="createRequire"){requireVariables.add(node.id.name)}}},ImportExpression(node){if(allowDynamicImport)return;if(!isDangerousSpecifier(node.source))return;context.report({node,messageId:"unsafeDynamicImport"})},CallExpression(node){if(node.callee.type!=="Identifier"&&node.callee.type!=="MemberExpression"){return}if(!isRequireReference(node.callee)){return}if(node.arguments.length===0)return;const firstArg=node.arguments[0];if(firstArg.type==="SpreadElement")return;if(!isDangerousSpecifier(firstArg))return;context.report({node,messageId:"unsafeDynamicRequire"})}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeDynamicRequire=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const const_value_1=require("../../utils/const-value");const DEFAULT_TAINT_SOURCES=["req","request","ctx","event","process"];exports.noUnsafeDynamicRequire=(0,eslint_devkit_2.createRule)({name:"no-unsafe-dynamic-require",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-unsafe-dynamic-require.md",description:"Prevent unsafe dynamic require() calls that could enable code injection",cwe:"CWE-95",cvss:9.8},hasSuggestions:false,messages:{unsafeDynamicRequire:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic require()",cwe:"CWE-95",description:"Dynamic require() detected",severity:"CRITICAL",fix:'Use allowlist: const ALLOWED = ["mod1", "mod2"]; if (!ALLOWED.includes(name)) throw Error("Not allowed")',documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"}),unsafeDynamicImport:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic import()",cwe:"CWE-95",description:"import() resolves and EVALUATES the module it is given, exactly as require() does. This specifier is reachable from request or process input, so an attacker chooses which file executes.",severity:"CRITICAL",fix:'Use allowlist: const ALLOWED = { csv: "./formatters/csv" }; const specifier = ALLOWED[name]; if (!specifier) throw Error("Not allowed"); await import(specifier)',documentationLink:"https://owasp.org/www-community/attacks/Code_Injection"})},schema:[{type:"object",properties:{allowDynamicImport:{type:"boolean",default:false},taintSources:{type:"array",items:{type:"string"},default:DEFAULT_TAINT_SOURCES,description:"Identifier roots treated as attacker-reachable (default: req, request, ctx, event, process)"},reportUnresolvedSpecifiers:{type:"boolean",default:false,description:'Report specifiers whose provenance cannot be resolved. Restores the pre-inversion "any non-literal is dangerous" behaviour.'}},additionalProperties:false}]},defaultOptions:[{allowDynamicImport:false}],create(context){const options=context.options[0]??{};const readsTaintSource=(0,provenance_1.makeReadsTaintSource)(context.sourceCode,new Set((options.taintSources??DEFAULT_TAINT_SOURCES).map(s=>s.toLowerCase())));const reportUnresolvedSpecifiers=options.reportUnresolvedSpecifiers??false;const allowDynamicImport=options.allowDynamicImport??false;const requireVariables=new Set;const isRequireReference=node=>{if(node.type==="Identifier"&&node.name==="require"){return true}if(node.type==="Identifier"&&requireVariables.has(node.name)){return true}return node.type==="MemberExpression"&&node.object.type==="Identifier"&&node.object.name==="module"&&(0,eslint_devkit_1.propertyName)(node)==="require"};const isDangerousSpecifier=arg=>{if(arg.type==="Literal")return false;if(arg.type==="TemplateLiteral"&&arg.expressions.length===0)return false;if((0,const_value_1.resolveConstantString)(context.sourceCode,arg)!==null)return false;if(arg.type==="Identifier"){const variable=(0,provenance_1.findVariable)(context.sourceCode,arg);if(variable?.defs.length===1&&variable.defs[0].type==="Parameter"){return reportUnresolvedSpecifiers}}if(readsTaintSource(arg))return true;return reportUnresolvedSpecifiers};return{VariableDeclarator(node){if(node.id.type==="Identifier"&&node.init){if(node.init.type==="Identifier"&&node.init.name==="require"){requireVariables.add(node.id.name)}if(node.init.type==="CallExpression"&&node.init.callee.type==="Identifier"&&node.init.callee.name==="createRequire"){requireVariables.add(node.id.name)}}},ImportExpression(node){if(allowDynamicImport)return;if(!isDangerousSpecifier(node.source))return;context.report({node,messageId:"unsafeDynamicImport"})},CallExpression(node){if(node.callee.type!=="Identifier"&&node.callee.type!=="MemberExpression"){return}if(!isRequireReference(node.callee)){return}if(node.arguments.length===0)return;const firstArg=node.arguments[0];if(firstArg.type==="SpreadElement")return;if(!isDangerousSpecifier(firstArg))return;context.report({node,messageId:"unsafeDynamicRequire"})}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakCipherAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const WEAK_CIPHER_PATTERNS=[{pattern:/\bdes\b(?!-ede)/i,name:"DES",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\bdes-ede3?\b|\b3des\b|\btripledes\b/i,name:"3DES",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\brc4\b|\barc4\b/i,name:"RC4",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\bblowfish\b|\bbf\b/i,name:"Blowfish",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\brc2\b/i,name:"RC2",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\bidea\b/i,name:"IDEA",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"}];function findWeakCipher(value,additionalPatterns){for(const pattern of WEAK_CIPHER_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:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"}}}return null}exports.noWeakCipherAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-cipher-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-cipher-algorithm.md",description:"Disallow weak cipher algorithms (DES, 3DES, RC4, Blowfish)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakCipherAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak cipher algorithm",cwe:"CWE-327",description:"Use of weak cipher algorithm: {{algorithm}}. {{algorithm}} has known vulnerabilities and should not be used.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createCipheriv("{{replacement}}", key, iv)',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useAes256Gcm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use AES-256-GCM",description:"Replace with AES-256-GCM for authenticated encryption",severity:"LOW",fix:'crypto.createCipheriv("aes-256-gcm", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"}),useChaCha20:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use ChaCha20-Poly1305",description:"Replace with ChaCha20-Poly1305 for modern encryption",severity:"LOW",fix:'crypto.createCipheriv("chacha20-poly1305", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"})},schema:[{type:"object",properties:{additionalWeakCiphers:{type:"array",items:{type:"string"},default:[],description:"Additional weak ciphers to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak ciphers in test files"}},additionalProperties:false}]},defaultOptions:[{additionalWeakCiphers:[],allowInTests:false}],create(context,[options={}]){const{additionalWeakCiphers=[],allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function checkCallExpression(node){if(isTestFile)return;const cipherMethods=new Set(["createCipher","createCipheriv","createDecipher","createDecipheriv"]);if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&cipherMethods.has(node.callee.property.name)){checkCipherArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&cipherMethods.has(node.callee.name)){checkCipherArgument(node)}}function checkCipherArgument(node){const firstArg=node.arguments[0];if(firstArg===void 0)return;const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,firstArg);if(resolved===null)return;const weakPattern=findWeakCipher(resolved.value,additionalWeakCiphers);if(!weakPattern)return;const target=resolved.source;context.report({node:firstArg,messageId:"weakCipherAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useAes256Gcm",fix:fixer=>fixer.replaceText(target,`"aes-256-gcm"`)},{messageId:"useChaCha20",fix:fixer=>fixer.replaceText(target,`"chacha20-poly1305"`)}]})}return{CallExpression:checkCallExpression}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakCipherAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const WEAK_CIPHER_PATTERNS=[{pattern:/\bdes\b(?!-ede)/i,name:"DES",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\bdes-ede3?\b|\b3des\b|\btripledes\b/i,name:"3DES",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\brc4\b|\barc4\b/i,name:"RC4",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\bblowfish\b|\bbf\b/i,name:"Blowfish",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\brc2\b/i,name:"RC2",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"},{pattern:/\bidea\b/i,name:"IDEA",alternatives:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"}];function findWeakCipher(value,additionalPatterns){for(const pattern of WEAK_CIPHER_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:["AES-256-GCM","ChaCha20-Poly1305"],replacement:"aes-256-gcm"}}}return null}exports.noWeakCipherAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-cipher-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-cipher-algorithm.md",description:"Disallow weak cipher algorithms (DES, 3DES, RC4, Blowfish)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakCipherAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak cipher algorithm",cwe:"CWE-327",description:"Use of weak cipher algorithm: {{algorithm}}. {{algorithm}} has known vulnerabilities and should not be used.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createCipheriv("{{replacement}}", key, iv)',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useAes256Gcm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use AES-256-GCM",description:"Replace with AES-256-GCM for authenticated encryption",severity:"LOW",fix:'crypto.createCipheriv("aes-256-gcm", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"}),useChaCha20:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use ChaCha20-Poly1305",description:"Replace with ChaCha20-Poly1305 for modern encryption",severity:"LOW",fix:'crypto.createCipheriv("chacha20-poly1305", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"})},schema:[{type:"object",properties:{additionalWeakCiphers:{type:"array",items:{type:"string"},default:[],description:"Additional weak ciphers to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak ciphers in test files"}},additionalProperties:false}]},defaultOptions:[{additionalWeakCiphers:[],allowInTests:false}],create(context,[options={}]){const{additionalWeakCiphers=[],allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function checkCallExpression(node){if(isTestFile)return;const cipherMethods=new Set(["createCipher","createCipheriv","createDecipher","createDecipheriv"]);if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(node.callee),cipherMethods)){checkCipherArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&cipherMethods.has(node.callee.name)){checkCipherArgument(node)}}function checkCipherArgument(node){const firstArg=node.arguments[0];if(firstArg===void 0)return;const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,firstArg);if(resolved===null)return;const weakPattern=findWeakCipher(resolved.value,additionalWeakCiphers);if(!weakPattern)return;const target=resolved.source;context.report({node:firstArg,messageId:"weakCipherAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useAes256Gcm",fix:fixer=>fixer.replaceText(target,`"aes-256-gcm"`)},{messageId:"useChaCha20",fix:fixer=>fixer.replaceText(target,`"chacha20-poly1305"`)}]})}return{CallExpression:checkCallExpression}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakHashAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const names_1=require("../../utils/names");const const_value_1=require("../../utils/const-value");const WEAK_HASH_PATTERNS=[{pattern:/\bmd5\b/i,name:"MD5",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bmd4\b/i,name:"MD4",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bsha1\b/i,name:"SHA-1",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bripemd\b/i,name:"RIPEMD",alternatives:["SHA-256","SHA-512"],replacement:"sha256"}];const DEFAULT_NON_CRYPTOGRAPHIC_NAMES=["sha","etag","cachekey","cachebuster","thumbprint","x5t"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate","otp","mfa","totp","passphrase","pincode","mnemonic","seedphrase","masterkey","securityanswer","recoverycode","backupcode"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!target.computed&&target.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return target.property.name}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return node.property.name}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&current.id){return current.id.name}const owner=current.parent;if(owner?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return owner.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.id.name:null}if((owner?.type===eslint_devkit_1.AST_NODE_TYPES.Property||owner?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition)&&!owner.computed){return expressionName(owner.key)}return null}current=current.parent}return null}function findWeakHash(value,additionalPatterns){for(const pattern of WEAK_HASH_PATTERNS){if(pattern.pattern.test(value)){return pattern}}for(const additionalPattern of additionalPatterns){const regex=new RegExp(`\\b${additionalPattern}\\b`,"i");if(regex.test(value)){return{pattern:regex,name:additionalPattern.toUpperCase(),alternatives:["SHA-256","SHA-512"],replacement:"sha256"}}}return null}exports.noWeakHashAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-hash-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-hash-algorithm.md",description:"Disallow weak hash algorithms (MD5, SHA1, MD4)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakHashAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak hash algorithm",cwe:"CWE-327",description:"Use of weak hash algorithm: {{algorithm}}. {{algorithm}} is cryptographically broken and unsuitable for security purposes.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createHash("{{replacement}}").update(data). If this hash is an identifier rather than a security control \u2014 an EVALSHA key, an ETag, a cache key \u2014 store it under one of the nonCryptographicNames instead.',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-256",description:"Replace with SHA-256 for secure hashing",severity:"LOW",fix:'crypto.createHash("sha256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-512",description:"Replace with SHA-512 for stronger hashing",severity:"LOW",fix:'crypto.createHash("sha512").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha3:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-3",description:"Replace with SHA-3 for latest standard",severity:"LOW",fix:'crypto.createHash("sha3-256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"})},schema:[{type:"object",properties:{additionalWeakAlgorithms:{type:"array",items:{type:"string"},default:[],description:"Additional weak algorithms to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak hashes in test files"},nonCryptographicNames:{type:"array",items:{type:"string"},default:DEFAULT_NON_CRYPTOGRAPHIC_NAMES,description:"Assignment target names that mark a hash as an identifier rather than a security control"},securityUseNames:{type:"array",items:{type:"string"},default:DEFAULT_SECURITY_USE_NAMES,description:"Names that mark a hash as a security control (whole-word matched)"},reportUnclassifiedHashes:{type:"boolean",default:false,description:"Report weak hashes whose purpose cannot be determined. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{additionalWeakAlgorithms:[],allowInTests:false,nonCryptographicNames:DEFAULT_NON_CRYPTOGRAPHIC_NAMES}],create(context,[options={}]){const{additionalWeakAlgorithms=[],allowInTests=false,nonCryptographicNames=DEFAULT_NON_CRYPTOGRAPHIC_NAMES,securityUseNames=DEFAULT_SECURITY_USE_NAMES,reportUnclassifiedHashes=false}=options;const isSecurityUse=(0,names_1.makeNameTest)(securityUseNames);const isNonCryptographicName=(0,names_1.makeNameTest)(nonCryptographicNames.map(normalizeName));const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function isNonCryptographicUse(node){const stored=assignedName(node);if(stored!==null&&isNonCryptographicName(stored))return true;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isNonCryptographicName(enclosing)}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}if(stored!==null)return false;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function isLocalHmacHelper(callee){const scope=context.sourceCode.getScope(callee);for(let current=scope;current;current=current.upper){const variable=current.variables.find(v=>v.name===callee.name);if(!variable)continue;return variable.defs.some(def=>def.type!=="ImportBinding"&&computesHmac(def.node))}return false}function computesHmac(root){const stack=[root];while(stack.length>0){const node=stack.pop();if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&isCreateHmacCall(node)){return true}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){stack.push(child)}}}}return false}function isCreateHmacCall(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.name==="createHmac"}return callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.property.name==="createHmac"}function checkCallExpression(node){if(isTestFile)return;if(isNonCryptographicUse(node))return;if(!reportUnclassifiedHashes&&!hasSecurityUse(node))return;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.property.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const funcName=node.callee.name.toLowerCase();if((funcName==="sha1"||funcName==="md5"||funcName==="md4")&&!isLocalHmacHelper(node.callee)){const weakPattern=findWeakHash(funcName,additionalWeakAlgorithms);context.report({node,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return fixer.replaceText(node.callee,"sha256")}return null}}]})}}}function checkHashArgument(node){for(const arg of node.arguments){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,arg);if(resolved===null)continue;const weakPattern=findWeakHash(resolved.value,additionalWeakAlgorithms);if(!weakPattern)continue;const target=resolved.source;context.report({node:arg,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>fixer.replaceText(target,`"sha256"`)},{messageId:"useSha512",fix:fixer=>fixer.replaceText(target,`"sha512"`)},{messageId:"useSha3",fix:fixer=>fixer.replaceText(target,`"sha3-256"`)}]})}}return{CallExpression:checkCallExpression}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakHashAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const names_1=require("../../utils/names");const const_value_1=require("../../utils/const-value");const WEAK_HASH_PATTERNS=[{pattern:/\bmd5\b/i,name:"MD5",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bmd4\b/i,name:"MD4",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bsha1\b/i,name:"SHA-1",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bripemd\b/i,name:"RIPEMD",alternatives:["SHA-256","SHA-512"],replacement:"sha256"}];const DEFAULT_NON_CRYPTOGRAPHIC_NAMES=["sha","etag","cachekey","cachebuster","thumbprint","x5t"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate","otp","mfa","totp","passphrase","pincode","mnemonic","seedphrase","masterkey","securityanswer","recoverycode","backupcode"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!target.computed&&target.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return target.property.name}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return node.property.name}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&current.id){return current.id.name}const owner=current.parent;if(owner?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return owner.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.id.name:null}if((owner?.type===eslint_devkit_1.AST_NODE_TYPES.Property||owner?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition)&&!owner.computed){return expressionName(owner.key)}return null}current=current.parent}return null}function findWeakHash(value,additionalPatterns){for(const pattern of WEAK_HASH_PATTERNS){if(pattern.pattern.test(value)){return pattern}}for(const additionalPattern of additionalPatterns){const regex=new RegExp(`\\b${additionalPattern}\\b`,"i");if(regex.test(value)){return{pattern:regex,name:additionalPattern.toUpperCase(),alternatives:["SHA-256","SHA-512"],replacement:"sha256"}}}return null}exports.noWeakHashAlgorithm=(0,eslint_devkit_1.createRule)({name:"no-weak-hash-algorithm",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-weak-hash-algorithm.md",description:"Disallow weak hash algorithms (MD5, SHA1, MD4)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{weakHashAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak hash algorithm",cwe:"CWE-327",description:"Use of weak hash algorithm: {{algorithm}}. {{algorithm}} is cryptographically broken and unsuitable for security purposes.",severity:"CRITICAL",fix:'Replace with {{replacement}}: crypto.createHash("{{replacement}}").update(data). If this hash is an identifier rather than a security control \u2014 an EVALSHA key, an ETag, a cache key \u2014 store it under one of the nonCryptographicNames instead.',documentationLink:"https://owasp.org/www-community/vulnerabilities/Weak_Cryptography"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-256",description:"Replace with SHA-256 for secure hashing",severity:"LOW",fix:'crypto.createHash("sha256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-512",description:"Replace with SHA-512 for stronger hashing",severity:"LOW",fix:'crypto.createHash("sha512").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"}),useSha3:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use SHA-3",description:"Replace with SHA-3 for latest standard",severity:"LOW",fix:'crypto.createHash("sha3-256").update(data)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatehashmethod-options"})},schema:[{type:"object",properties:{additionalWeakAlgorithms:{type:"array",items:{type:"string"},default:[],description:"Additional weak algorithms to detect"},allowInTests:{type:"boolean",default:false,description:"Allow weak hashes in test files"},nonCryptographicNames:{type:"array",items:{type:"string"},default:DEFAULT_NON_CRYPTOGRAPHIC_NAMES,description:"Assignment target names that mark a hash as an identifier rather than a security control"},securityUseNames:{type:"array",items:{type:"string"},default:DEFAULT_SECURITY_USE_NAMES,description:"Names that mark a hash as a security control (whole-word matched)"},reportUnclassifiedHashes:{type:"boolean",default:false,description:"Report weak hashes whose purpose cannot be determined. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{additionalWeakAlgorithms:[],allowInTests:false,nonCryptographicNames:DEFAULT_NON_CRYPTOGRAPHIC_NAMES}],create(context,[options={}]){const{additionalWeakAlgorithms=[],allowInTests=false,nonCryptographicNames=DEFAULT_NON_CRYPTOGRAPHIC_NAMES,securityUseNames=DEFAULT_SECURITY_USE_NAMES,reportUnclassifiedHashes=false}=options;const isSecurityUse=(0,names_1.makeNameTest)(securityUseNames);const isNonCryptographicName=(0,names_1.makeNameTest)(nonCryptographicNames.map(normalizeName));const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function isNonCryptographicUse(node){const stored=assignedName(node);if(stored!==null&&isNonCryptographicName(stored))return true;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isNonCryptographicName(enclosing)}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}if(stored!==null)return false;const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function isLocalHmacHelper(callee){const scope=context.sourceCode.getScope(callee);for(let current=scope;current;current=current.upper){const variable=current.variables.find(v=>v.name===callee.name);if(!variable)continue;return variable.defs.some(def=>def.type!=="ImportBinding"&&computesHmac(def.node))}return false}function computesHmac(root){const stack=[root];while(stack.length>0){const node=stack.pop();if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&isCreateHmacCall(node)){return true}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){stack.push(child)}}}}return false}function isCreateHmacCall(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.name==="createHmac"}return callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(callee)==="createHmac"}function checkCallExpression(node){if(isTestFile)return;if(isNonCryptographicUse(node))return;if(!reportUnclassifiedHashes&&!hasSecurityUse(node))return;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(node.callee)==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const funcName=node.callee.name.toLowerCase();if((funcName==="sha1"||funcName==="md5"||funcName==="md4")&&!isLocalHmacHelper(node.callee)){const weakPattern=findWeakHash(funcName,additionalWeakAlgorithms);context.report({node,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return fixer.replaceText(node.callee,"sha256")}return null}}]})}}}function checkHashArgument(node){for(const arg of node.arguments){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,arg);if(resolved===null)continue;const weakPattern=findWeakHash(resolved.value,additionalWeakAlgorithms);if(!weakPattern)continue;const target=resolved.source;context.report({node:arg,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>fixer.replaceText(target,`"sha256"`)},{messageId:"useSha512",fix:fixer=>fixer.replaceText(target,`"sha512"`)},{messageId:"useSha3",fix:fixer=>fixer.replaceText(target,`"sha3-256"`)}]})}}return{CallExpression:checkCallExpression}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.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_ENTRY_FIELDS=["name","path","fileName","entryName","filename"];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"]},archiveEntryFields:{type:"array",items:{type:"string"},default:[...DEFAULT_ARCHIVE_ENTRY_FIELDS],description:"Property names an archive entry exposes its path on. Replaces the default."},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 archiveEntryFields=new Set(options.archiveEntryFields??DEFAULT_ARCHIVE_ENTRY_FIELDS);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"&&current.callee.type==="Identifier"&&pathValidationFunctions.includes(current.callee.name)){return true}if(current.type==="CallExpression"&&current.callee.type==="MemberExpression"&&current.callee.object.type==="Identifier"&&current.callee.object.name==="path"&&current.callee.property.type==="Identifier"&&current.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===void 0?null:(0,eslint_devkit_1.staticString)(destArg))??"";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"&&archiveEntryFields.has(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}}}}});
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_ENTRY_FIELDS=["name","path","fileName","entryName","filename"];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"]},archiveEntryFields:{type:"array",items:{type:"string"},default:[...DEFAULT_ARCHIVE_ENTRY_FIELDS],description:"Property names an archive entry exposes its path on. Replaces the default."},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 archiveEntryFields=new Set(options.archiveEntryFields??DEFAULT_ARCHIVE_ENTRY_FIELDS);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)||ARCHIVE_NAME.test((0,eslint_devkit_1.propertyName)(node)??"")}return false};const pending=[];const isArchiveExtraction=node=>{const callee=node.callee;const method=callee.type==="MemberExpression"?(0,eslint_devkit_1.propertyName)(callee):null;if(method!==null&&archiveFunctions.includes(method)){if(!AMBIGUOUS_EXTRACTORS.has(method))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"&&current.callee.type==="Identifier"&&pathValidationFunctions.includes(current.callee.name)){return true}if(current.type==="CallExpression"&&current.callee.type==="MemberExpression"&&current.callee.object.type==="Identifier"&&current.callee.object.name==="path"&&(0,eslint_devkit_1.propertyName)(current.callee)==="basename"){return true}if(current.type==="IfStatement"){const test=current.test;if(test.type==="CallExpression"&&test.callee.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(test.callee)==="startsWith"){return true}if(test.type==="UnaryExpression"&&test.operator==="!"&&test.argument.type==="CallExpression"&&test.argument.callee.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(test.argument.callee)==="startsWith"){return true}if(test.type==="CallExpression"&&test.callee.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(test.callee)==="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;const extractor=node.callee.type==="MemberExpression"?(0,eslint_devkit_1.propertyName)(node.callee):null;if(extractor!==null){const methodName=extractor;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===void 0?null:(0,eslint_devkit_1.staticString)(destArg))??"";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"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),["join","resolve","relative","normalize"])){const args=node.arguments;for(const arg of args){if(arg.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(arg),archiveEntryFields)){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}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAeadTagVerification=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const AEAD_SUFFIX=/-(gcm|ccm|ocb|poly1305)$/;const STREAM_METHODS=new Set(["pipe","write","end","setEncoding"]);exports.requireAeadTagVerification=(0,eslint_devkit_1.createRule)({name:"require-aead-tag-verification",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-aead-tag-verification.md",description:"Require AEAD decryption to verify the authentication tag (setAuthTag + final)",cwe:"CWE-327",cvss:7.5},hasSuggestions:false,messages:{missingAuthTag:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AEAD decryption without setAuthTag",cwe:"CWE-327",description:"An AEAD decipher (GCM/CCM/OCB/ChaCha20-Poly1305) is created but setAuthTag() is never called, so the authentication tag is never checked. Forged or tampered ciphertext decrypts as if it were authentic.",severity:"HIGH",fix:"Call decipher.setAuthTag(tag) with the tag produced at encryption time, then decipher.final()",documentationLink:"https://nodejs.org/api/crypto.html#decipher_setauthtagbuffer"}),missingFinal:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AEAD decryption never calls final()",cwe:"CWE-327",description:"setAuthTag() was called but decipher.final() never is. final() is the call that compares the tag and throws on mismatch \u2014 without it the tag is loaded and then ignored, so update() returns unauthenticated plaintext.",severity:"HIGH",fix:"Append decipher.final() to the decryption and let it throw on a tag mismatch",documentationLink:"https://nodejs.org/api/crypto.html#decipherfinaloutputencoding"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow unverified AEAD decryption in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename);function isCreateDecipheriv(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.property.name==="createDecipheriv"}return callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.name==="createDecipheriv"}function isAeadAlgorithm(argument){return argument!==void 0&&argument.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof argument.value==="string"&&AEAD_SUFFIX.test(argument.value.toLowerCase())}function collectMethods(variable){const methods=new Set;for(const reference of variable.references){const identifier=reference.identifier;const parent=identifier.parent;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator)continue;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===identifier&&parent.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&!parent.computed){methods.add(parent.property.name);continue}return null}return methods}function checkVariableDeclarator(node){if(isTestFile)return;if(node.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return;const init=node.init;if(!init||init.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return;if(!isCreateDecipheriv(init.callee))return;if(!isAeadAlgorithm(init.arguments[0]))return;const[variable]=context.sourceCode.getDeclaredVariables(node);const methods=collectMethods(variable);if(methods===null)return;if(!methods.has("setAuthTag")){context.report({node:init,messageId:"missingAuthTag"});return}const drivenAsStream=[...methods].some(name=>STREAM_METHODS.has(name));if(!methods.has("final")&&!drivenAsStream){context.report({node:init,messageId:"missingFinal"})}}return{VariableDeclarator:checkVariableDeclarator}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAeadTagVerification=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const AEAD_SUFFIX=/-(gcm|ccm|ocb|poly1305)$/;const STREAM_METHODS=new Set(["pipe","write","end","setEncoding"]);exports.requireAeadTagVerification=(0,eslint_devkit_1.createRule)({name:"require-aead-tag-verification",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-aead-tag-verification.md",description:"Require AEAD decryption to verify the authentication tag (setAuthTag + final)",cwe:"CWE-327",cvss:7.5},hasSuggestions:false,messages:{missingAuthTag:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AEAD decryption without setAuthTag",cwe:"CWE-327",description:"An AEAD decipher (GCM/CCM/OCB/ChaCha20-Poly1305) is created but setAuthTag() is never called, so the authentication tag is never checked. Forged or tampered ciphertext decrypts as if it were authentic.",severity:"HIGH",fix:"Call decipher.setAuthTag(tag) with the tag produced at encryption time, then decipher.final()",documentationLink:"https://nodejs.org/api/crypto.html#decipher_setauthtagbuffer"}),missingFinal:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AEAD decryption never calls final()",cwe:"CWE-327",description:"setAuthTag() was called but decipher.final() never is. final() is the call that compares the tag and throws on mismatch \u2014 without it the tag is loaded and then ignored, so update() returns unauthenticated plaintext.",severity:"HIGH",fix:"Append decipher.final() to the decryption and let it throw on a tag mismatch",documentationLink:"https://nodejs.org/api/crypto.html#decipherfinaloutputencoding"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow unverified AEAD decryption in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename);function isCreateDecipheriv(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return(0,eslint_devkit_1.propertyName)(callee)==="createDecipheriv"}return callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.name==="createDecipheriv"}function isAeadAlgorithm(argument){return argument!==void 0&&argument.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof argument.value==="string"&&AEAD_SUFFIX.test(argument.value.toLowerCase())}function collectMethods(variable){const methods=new Set;for(const reference of variable.references){const identifier=reference.identifier;const parent=identifier.parent;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator)continue;const method=(0,eslint_devkit_1.memberPropertyName)(parent);if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===identifier&&method!==null){methods.add(method);continue}return null}return methods}function checkVariableDeclarator(node){if(isTestFile)return;if(node.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return;const init=node.init;if(!init||init.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return;if(!isCreateDecipheriv(init.callee))return;if(!isAeadAlgorithm(init.arguments[0]))return;const[variable]=context.sourceCode.getDeclaredVariables(node);const methods=collectMethods(variable);if(methods===null)return;if(!methods.has("setAuthTag")){context.report({node:init,messageId:"missingAuthTag"});return}const drivenAsStream=[...methods].some(name=>STREAM_METHODS.has(name));if(!methods.has("final")&&!drivenAsStream){context.report({node:init,messageId:"missingFinal"})}}return{VariableDeclarator:checkVariableDeclarator}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireSecureCredentialStorage=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const credential_evidence_1=require("../../utils/credential-evidence");const const_value_1=require("../../utils/const-value");exports.requireSecureCredentialStorage=(0,eslint_devkit_1.createRule)({name:"require-secure-credential-storage",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-secure-credential-storage.md",description:"Enforce secure storage patterns for credentials",cwe:"CWE-312",cvss:5.5},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"violation Detected",cwe:"CWE-312",description:"Enforce secure storage patterns for credentials detected - Credentials without encryption",severity:"HIGH",fix:"Review and apply secure practices",documentationLink:"https://cwe.mitre.org/data/definitions/312.html"}),credentialInEnvironment:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Credential written to process.env",cwe:"CWE-526",description:"A credential assigned into process.env is inherited by every child process this app spawns, is readable at /proc/<pid>/environ, and is captured verbatim by crash dumps and by the environment snapshots error reporters send upstream.",severity:"HIGH",fix:"Keep the secret in a variable scoped to the code that needs it, or fetch it from a secrets manager at the point of use. If a child process genuinely needs it, pass it through the `env` option of spawn/execFile for that one call instead of mutating the parent environment.",documentationLink:"https://cwe.mitre.org/data/definitions/526.html"})},schema:[]},defaultOptions:[],create(context){function isProcessEnv(node,depth=0){if(depth>2)return false;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.object.name==="process"&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.property.name==="env"){return true}if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,const_value_1.constInitializerOf)(context.sourceCode,node);return init!==null&&isProcessEnv(init,depth+1)}function isAliasedEnvironmentWrite(node){const target=node.left;return target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&isProcessEnv(target.object)}return{CallExpression(node){if((0,credential_evidence_1.isWebStorageWrite)(node)){if(!(0,credential_evidence_1.storesACredential)(node)||(0,credential_evidence_1.isEncrypted)(node,context.sourceCode))return;context.report({node,messageId:"violationDetected"});return}const callee=node.callee;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!=="Object"||callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||callee.property.name!=="assign"){return}const[target,...sources]=node.arguments;if(!target||!isProcessEnv(target))return;for(const source of sources){if(source.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)continue;for(const property of source.properties){if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;if((0,credential_evidence_1.isEncryptedExpression)(property.value,context.sourceCode))continue;if(!(0,credential_evidence_1.expressionNamesACredential)(property.key)&&!(0,credential_evidence_1.expressionNamesACredential)(property.value)){continue}context.report({node:property,messageId:"credentialInEnvironment"})}}},AssignmentExpression(node){if(!(0,credential_evidence_1.isEnvironmentWrite)(node)&&!isAliasedEnvironmentWrite(node))return;if((0,credential_evidence_1.isEncryptedExpression)(node.right,context.sourceCode))return;if(!(0,credential_evidence_1.expressionNamesACredential)(node.left)&&!(0,credential_evidence_1.expressionNamesACredential)(node.right)){return}context.report({node,messageId:"credentialInEnvironment"})}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireSecureCredentialStorage=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const credential_evidence_1=require("../../utils/credential-evidence");const const_value_1=require("../../utils/const-value");exports.requireSecureCredentialStorage=(0,eslint_devkit_1.createRule)({name:"require-secure-credential-storage",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-secure-credential-storage.md",description:"Enforce secure storage patterns for credentials",cwe:"CWE-312",cvss:5.5},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"violation Detected",cwe:"CWE-312",description:"Enforce secure storage patterns for credentials detected - Credentials without encryption",severity:"HIGH",fix:"Review and apply secure practices",documentationLink:"https://cwe.mitre.org/data/definitions/312.html"}),credentialInEnvironment:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Credential written to process.env",cwe:"CWE-526",description:"A credential assigned into process.env is inherited by every child process this app spawns, is readable at /proc/<pid>/environ, and is captured verbatim by crash dumps and by the environment snapshots error reporters send upstream.",severity:"HIGH",fix:"Keep the secret in a variable scoped to the code that needs it, or fetch it from a secrets manager at the point of use. If a child process genuinely needs it, pass it through the `env` option of spawn/execFile for that one call instead of mutating the parent environment.",documentationLink:"https://cwe.mitre.org/data/definitions/526.html"})},schema:[]},defaultOptions:[],create(context){function isProcessEnv(node,depth=0){if(depth>2)return false;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.object.name==="process"&&(0,eslint_devkit_1.propertyName)(node)==="env"){return true}if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,const_value_1.constInitializerOf)(context.sourceCode,node);return init!==null&&isProcessEnv(init,depth+1)}function isAliasedEnvironmentWrite(node){const target=node.left;return target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&isProcessEnv(target.object)}return{CallExpression(node){if((0,credential_evidence_1.isWebStorageWrite)(node)){if(!(0,credential_evidence_1.storesACredential)(node)||(0,credential_evidence_1.isEncrypted)(node,context.sourceCode))return;context.report({node,messageId:"violationDetected"});return}const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||callee.object.name!=="Object"||(0,eslint_devkit_1.propertyName)(callee)!=="assign"){return}const[target,...sources]=node.arguments;if(!target||!isProcessEnv(target))return;for(const source of sources){if(source.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)continue;for(const property of source.properties){if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;if((0,credential_evidence_1.isEncryptedExpression)(property.value,context.sourceCode))continue;if(!(0,credential_evidence_1.expressionNamesACredential)(property.key)&&!(0,credential_evidence_1.expressionNamesACredential)(property.value)){continue}context.report({node:property,messageId:"credentialInEnvironment"})}}},AssignmentExpression(node){if(!(0,credential_evidence_1.isEnvironmentWrite)(node)&&!isAliasedEnvironmentWrite(node))return;if((0,credential_evidence_1.isEncryptedExpression)(node.right,context.sourceCode))return;if(!(0,credential_evidence_1.expressionNamesACredential)(node.left)&&!(0,credential_evidence_1.expressionNamesACredential)(node.right)){return}context.report({node,messageId:"credentialInEnvironment"})}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireSecureDeletion=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const SENSITIVE_PROPERTY_NAMES=["password","passwd","pwd","passphrase","secret","token","jwt","bearer","credential","api key","secret key","private key","signing key","encryption key","access key","session id","ssn","credit card","card number","cvv"];function isSensitiveName(name,phrases){const words=(0,eslint_devkit_1.identifierWords)(name);if(words.length===0)return false;return phrases.some(phrase=>{const needle=(0,eslint_devkit_1.identifierWords)(phrase);if(needle.length===0||needle.length>words.length)return false;const start=words.length-needle.length;return needle.every((word,offset)=>words[start+offset]===word)})}exports.requireSecureDeletion=(0,eslint_devkit_1.createRule)({name:"require-secure-deletion",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-secure-deletion.md",description:"Require secure data deletion patterns",cwe:"CWE-459",cvss:5.3},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Incomplete Secret Cleanup",cwe:"CWE-459",description:"`delete` on the sensitive property `{{property}}` unbinds it without scrubbing the value",severity:"MEDIUM",fix:"Overwrite the value before deleting it (obj.{{property}} = undefined, or zero-fill the Buffer), and make sure no copy of the object was spread, logged, or serialised first",documentationLink:"https://cwe.mitre.org/data/definitions/459.html"})},schema:[{type:"object",properties:{sensitiveProperties:{type:"array",items:{type:"string"},default:[...SENSITIVE_PROPERTY_NAMES],description:"Replace the built-in sensitive-property vocabulary. Takes precedence over additionalSensitiveProperties."},additionalSensitiveProperties:{type:"array",items:{type:"string"},default:[],description:'Extra sensitive property names, matched as whole words at the END of the name. "pin code", "pin_code" and "pinCode" all match a property called pinCode; "pincode" does not.'}},additionalProperties:false}]},defaultOptions:[{additionalSensitiveProperties:[],sensitiveProperties:[...SENSITIVE_PROPERTY_NAMES]}],create(context,[options={}]){const{additionalSensitiveProperties=[],sensitiveProperties=SENSITIVE_PROPERTY_NAMES}=options;const phrases=[...sensitiveProperties,...additionalSensitiveProperties];function deletedPropertyName(node){const argument=node.type===eslint_devkit_1.AST_NODE_TYPES.ChainExpression?node.expression:node;if(argument.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return void 0;const property=argument.property;if(!argument.computed&&property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return property.name;const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,property);return resolved?.value}function reportIfSensitive(node,property){if(!property)return;if(!isSensitiveName(property,phrases))return;context.report({node,messageId:"violationDetected",data:{property}})}return{UnaryExpression(node){if(node.operator!=="delete")return;reportIfSensitive(node,deletedPropertyName(node.argument))},CallExpression(node){const callee=node.callee;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!=="Reflect"||callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||callee.property.name!=="deleteProperty"){return}const key=node.arguments[1];if(!key)return;reportIfSensitive(node,(0,const_value_1.resolveConstantString)(context.sourceCode,key)?.value)}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireSecureDeletion=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const SENSITIVE_PROPERTY_NAMES=["password","passwd","pwd","passphrase","secret","token","jwt","bearer","credential","api key","secret key","private key","signing key","encryption key","access key","session id","ssn","credit card","card number","cvv"];function isSensitiveName(name,phrases){const words=(0,eslint_devkit_1.identifierWords)(name);if(words.length===0)return false;return phrases.some(phrase=>{const needle=(0,eslint_devkit_1.identifierWords)(phrase);if(needle.length===0||needle.length>words.length)return false;const start=words.length-needle.length;return needle.every((word,offset)=>words[start+offset]===word)})}exports.requireSecureDeletion=(0,eslint_devkit_1.createRule)({name:"require-secure-deletion",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-secure-deletion.md",description:"Require secure data deletion patterns",cwe:"CWE-459",cvss:5.3},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Incomplete Secret Cleanup",cwe:"CWE-459",description:"`delete` on the sensitive property `{{property}}` unbinds it without scrubbing the value",severity:"MEDIUM",fix:"Overwrite the value before deleting it (obj.{{property}} = undefined, or zero-fill the Buffer), and make sure no copy of the object was spread, logged, or serialised first",documentationLink:"https://cwe.mitre.org/data/definitions/459.html"})},schema:[{type:"object",properties:{sensitiveProperties:{type:"array",items:{type:"string"},default:[...SENSITIVE_PROPERTY_NAMES],description:"Replace the built-in sensitive-property vocabulary. Takes precedence over additionalSensitiveProperties."},additionalSensitiveProperties:{type:"array",items:{type:"string"},default:[],description:'Extra sensitive property names, matched as whole words at the END of the name. "pin code", "pin_code" and "pinCode" all match a property called pinCode; "pincode" does not.'}},additionalProperties:false}]},defaultOptions:[{additionalSensitiveProperties:[],sensitiveProperties:[...SENSITIVE_PROPERTY_NAMES]}],create(context,[options={}]){const{additionalSensitiveProperties=[],sensitiveProperties=SENSITIVE_PROPERTY_NAMES}=options;const phrases=[...sensitiveProperties,...additionalSensitiveProperties];function deletedPropertyName(node){const argument=node.type===eslint_devkit_1.AST_NODE_TYPES.ChainExpression?node.expression:node;if(argument.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return void 0;const property=argument.property;if(!argument.computed&&property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return property.name;const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,property);return resolved?.value}function reportIfSensitive(node,property){if(!property)return;if(!isSensitiveName(property,phrases))return;context.report({node,messageId:"violationDetected",data:{property}})}return{UnaryExpression(node){if(node.operator!=="delete")return;reportIfSensitive(node,deletedPropertyName(node.argument))},CallExpression(node){const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||callee.object.name!=="Reflect"||(0,eslint_devkit_1.propertyName)(callee)!=="deleteProperty"){return}const key=node.arguments[1];if(!key)return;reportIfSensitive(node,(0,const_value_1.resolveConstantString)(context.sourceCode,key)?.value)}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireStreamErrorHandler=void 0;exports.calleeMethodName=calleeMethodName;exports.constructsStream=constructsStream;const eslint_devkit_1=require("@interlace/eslint-devkit");const STREAM_CONSTRUCTORS=new Set(["createReadStream","createWriteStream","createGzip","createGunzip","createDeflate","createInflate","createBrotliCompress","createBrotliDecompress"]);const LISTENER_METHODS=new Set(["on","once","addListener","prependListener","prependOnceListener"]);function calleeMethodName(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&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.property.name}return void 0}function constructsStream(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const name=calleeMethodName(node.callee);return name!==void 0&&STREAM_CONSTRUCTORS.has(name)}exports.requireStreamErrorHandler=(0,eslint_devkit_1.createRule)({name:"require-stream-error-handler",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-stream-error-handler.md",description:"Require an 'error' listener on streams passed to .pipe(), which does not forward errors",cwe:"CWE-248",cvss:7.5},messages:{unhandledStreamError:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unhandled stream error",cwe:"CWE-248",description:".pipe() forwards data but not errors. A stream that emits 'error' with no listener throws inside the EventEmitter, which is an uncaught exception \u2014 the process exits. A single request for a missing or unreadable file is enough to stop the server.",severity:"HIGH",fix:"Attach stream.on('error', handler) before piping, or use pipeline(), which destroys every stream and reports the failure.",documentationLink:"https://cwe.mitre.org/data/definitions/248.html"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:true,description:"Allow unhandled stream errors in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context,[options={}]){const{allowInTests=true}=options;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return{}}const handled=new Set;const streamBindings=new Set;const pending=[];function unhandledStream(node){if(constructsStream(node))return node;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;if(!streamBindings.has(node.name))return null;if(handled.has(node.name))return null;return node}return{VariableDeclarator(node){if(node.init!==null&&node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&constructsStream(node.init)){streamBindings.add(node.id.name)}},CallExpression(node){const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed||callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return}const method=callee.property.name;if(LISTENER_METHODS.has(method)){const event=node.arguments[0];if(event?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&event.value==="error"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){handled.add(callee.object.name)}return}if(method!=="pipe")return;pending.push({node,offender:node})},"Program:exit"(){for(const{node}of pending){const callee=node.callee;const source=unhandledStream(callee.object);const destinationArg=node.arguments[0];const destination=destinationArg===void 0||destinationArg.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement?null:unhandledStream(destinationArg);const offender=source??destination;if(offender===null)continue;context.report({node:offender,messageId:"unhandledStreamError"})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireStreamErrorHandler=void 0;exports.calleeMethodName=calleeMethodName;exports.constructsStream=constructsStream;const eslint_devkit_1=require("@interlace/eslint-devkit");const STREAM_CONSTRUCTORS=new Set(["createReadStream","createWriteStream","createGzip","createGunzip","createDeflate","createInflate","createBrotliCompress","createBrotliDecompress"]);const LISTENER_METHODS=new Set(["on","once","addListener","prependListener","prependOnceListener"]);function calleeMethodName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return(0,eslint_devkit_1.propertyName)(callee)??void 0}return void 0}function constructsStream(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const name=calleeMethodName(node.callee);return name!==void 0&&STREAM_CONSTRUCTORS.has(name)}exports.requireStreamErrorHandler=(0,eslint_devkit_1.createRule)({name:"require-stream-error-handler",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-stream-error-handler.md",description:"Require an 'error' listener on streams passed to .pipe(), which does not forward errors",cwe:"CWE-248",cvss:7.5},messages:{unhandledStreamError:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unhandled stream error",cwe:"CWE-248",description:".pipe() forwards data but not errors. A stream that emits 'error' with no listener throws inside the EventEmitter, which is an uncaught exception \u2014 the process exits. A single request for a missing or unreadable file is enough to stop the server.",severity:"HIGH",fix:"Attach stream.on('error', handler) before piping, or use pipeline(), which destroys every stream and reports the failure.",documentationLink:"https://cwe.mitre.org/data/definitions/248.html"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:true,description:"Allow unhandled stream errors in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context,[options={}]){const{allowInTests=true}=options;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return{}}const handled=new Set;const streamBindings=new Set;const pending=[];function unhandledStream(node){if(constructsStream(node))return node;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;if(!streamBindings.has(node.name))return null;if(handled.has(node.name))return null;return node}return{VariableDeclarator(node){if(node.init!==null&&node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&constructsStream(node.init)){streamBindings.add(node.id.name)}},CallExpression(node){const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return;const method=(0,eslint_devkit_1.propertyName)(callee);if(method===null)return;if(LISTENER_METHODS.has(method)){const event=node.arguments[0];if(event?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&event.value==="error"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){handled.add(callee.object.name)}return}if(method!=="pipe")return;pending.push({node,offender:node})},"Program:exit"(){for(const{node}of pending){const callee=node.callee;const source=unhandledStream(callee.object);const destinationArg=node.arguments[0];const destination=destinationArg===void 0||destinationArg.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement?null:unhandledStream(destinationArg);const offender=source??destination;if(offender===null)continue;context.report({node:offender,messageId:"unhandledStreamError"})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.storesACredential=storesACredential;exports.expressionNamesACredential=expressionNamesACredential;exports.isEncrypted=isEncrypted;exports.isEncryptedExpression=isEncryptedExpression;exports.isWebStorageWrite=isWebStorageWrite;exports.isEnvironmentWrite=isEnvironmentWrite;exports.isFileWrite=isFileWrite;const eslint_devkit_1=require("@interlace/eslint-devkit");const CREDENTIAL_WORDS=["password","passwd","secret","token","credential","apikey","api_key","accesskey","access_key","privatekey","private_key","jwt","sessionid","session_id","refreshtoken","refresh_token","clientsecret","client_secret","authtoken","auth_token"];const CONFIG_ABOUT_A_CREDENTIAL=new Set(["alg","algo","algorithm","cipher","digest","expiry","expiration","ttl","lifetime","maxage","rotation","type","kind","format","encoding","scheme","length","len","size","count","limit","name","label","id","prefix","suffix","path","file","url","uri","endpoint","host","header","issuer","audience","realm","enabled","disabled","required","strategy","provider"]);function segmentsOf(text){return text.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map(part=>part.toLowerCase())}function namesACredential(text){const normalized=text.toLowerCase();if(!CREDENTIAL_WORDS.some(word=>normalized.includes(word)))return false;const segments=segmentsOf(text);const tail=segments[segments.length-1];if(tail!==void 0&&CONFIG_ABOUT_A_CREDENTIAL.has(tail))return false;return true}function nameOf(node){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:return node.name;case eslint_devkit_1.AST_NODE_TYPES.Literal:return typeof node.value==="string"?node.value:"";case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.quasis.map(q=>q.value.raw).join("");case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:if(node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.property.name;return node.property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.property.value==="string"?node.property.value:"";default:return""}}function storesACredential(node){return node.arguments.some(argument=>{if(argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return false;return namesACredential(nameOf(argument))})}function expressionNamesACredential(node){return namesACredential(nameOf(node))}const CRYPTO_MODULES=new Set(["crypto","crypto-js","node-forge","tweetnacl","libsodium","libsodium-wrappers","libsodium-wrappers-sumo","sodium-native","jose","node-jose","@aws-crypto/client-node","@google-cloud/kms"]);const ENCRYPTION_APIS=new Set(["publicEncrypt","privateEncrypt","createCipheriv","createCipher","encrypt","wrapKey","CompactEncrypt","FlattenedEncrypt","GeneralEncrypt","compactEncrypt","crypto_secretbox_easy","crypto_box_easy","crypto_box_seal","crypto_aead_chacha20poly1305_ietf_encrypt","crypto_aead_xchacha20poly1305_ietf_encrypt","crypto_secretstream_xchacha20poly1305_push","secretbox","box"]);const CIPHER_OUTPUT_METHODS=new Set(["update","final"]);function localFunctionOf(node,scope){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;let current=scope;let variable;while(current&&!variable){variable=current.set?.get(node.name);current=current.upper}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?.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression||init?.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression){return init}return null}function returnedExpressions(fn){const body=fn.body;if(!body)return[];if(body.type!==eslint_devkit_1.AST_NODE_TYPES.BlockStatement)return[body];const found=[];const walk=statements=>{for(const statement of statements){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){if(statement.argument)found.push(statement.argument)}else if(statement.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){walk([statement.consequent]);if(statement.alternate)walk([statement.alternate])}else if(statement.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){walk(statement.body)}else if(statement.type===eslint_devkit_1.AST_NODE_TYPES.TryStatement){walk(statement.block.body)}}};walk(body.body);return found}function provablyEncrypts(value,sourceCode,depth,seen){if(depth>8||seen.has(value))return false;seen.add(value);const recurse=node=>provablyEncrypts(node,sourceCode,depth+1,seen);switch(value.type){case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:return recurse(value.argument);case eslint_devkit_1.AST_NODE_TYPES.ArrayExpression:return value.elements.some(element=>element!==null&&recurse(element));case eslint_devkit_1.AST_NODE_TYPES.Identifier:{let current=sourceCode.getScope(value);let variable;while(current&&!variable){variable=current.set?.get(value.name);current=current.upper}if(!variable||variable.defs.length!==1)return false;const def=variable.defs[0];if(def.type!=="Variable"||!def.node.init)return false;return recurse(def.node.init)}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{const callee=value.callee;const scope=sourceCode.getScope(value);const binding=(0,eslint_devkit_1.resolveModuleBinding)(callee,scope);if(binding&&CRYPTO_MODULES.has(binding.module)&&binding.path.length>0&&ENCRYPTION_APIS.has(binding.path[binding.path.length-1])){return true}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&CIPHER_OUTPUT_METHODS.has(callee.property.name)&&recurse(callee.object)){return true}const fn=localFunctionOf(callee,scope);if(fn&&returnedExpressions(fn).some(returned=>recurse(returned))){return true}return value.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&recurse(argument))}default:return false}}function isEncrypted(node,sourceCode){return isEncryptedExpression(node.arguments[1],sourceCode)}function isEncryptedExpression(value,sourceCode){if(value===void 0)return false;return provablyEncrypts(value,sourceCode,0,new Set)}const CLIENT_STORES=new Set(["localStorage","sessionStorage","AsyncStorage"]);function isWebStorageWrite(node){const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(callee.property.name!=="setItem")return false;const object=callee.object;if(object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return CLIENT_STORES.has(object.name)}return object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&object.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&CLIENT_STORES.has(object.property.name)}function isEnvironmentWrite(node){const target=node.left;if(target.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const object=target.object;return object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!object.computed&&object.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&object.object.name==="process"&&object.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&object.property.name==="env"}function isFileWrite(node){const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return["writeFile","writeFileSync","appendFile","appendFileSync"].includes(callee.property.name)}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.storesACredential=storesACredential;exports.expressionNamesACredential=expressionNamesACredential;exports.isEncrypted=isEncrypted;exports.isEncryptedExpression=isEncryptedExpression;exports.isWebStorageWrite=isWebStorageWrite;exports.isEnvironmentWrite=isEnvironmentWrite;exports.isFileWrite=isFileWrite;const eslint_devkit_1=require("@interlace/eslint-devkit");const CREDENTIAL_WORDS=["password","passwd","secret","token","credential","apikey","api_key","accesskey","access_key","privatekey","private_key","jwt","sessionid","session_id","refreshtoken","refresh_token","clientsecret","client_secret","authtoken","auth_token"];const CONFIG_ABOUT_A_CREDENTIAL=new Set(["alg","algo","algorithm","cipher","digest","expiry","expiration","ttl","lifetime","maxage","rotation","type","kind","format","encoding","scheme","length","len","size","count","limit","name","label","id","prefix","suffix","path","file","url","uri","endpoint","host","header","issuer","audience","realm","enabled","disabled","required","strategy","provider"]);function segmentsOf(text){return text.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map(part=>part.toLowerCase())}function namesACredential(text){const normalized=text.toLowerCase();if(!CREDENTIAL_WORDS.some(word=>normalized.includes(word)))return false;const segments=segmentsOf(text);const tail=segments[segments.length-1];if(tail!==void 0&&CONFIG_ABOUT_A_CREDENTIAL.has(tail))return false;return true}function nameOf(node){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:return node.name;case eslint_devkit_1.AST_NODE_TYPES.Literal:return typeof node.value==="string"?node.value:"";case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.quasis.map(q=>q.value.raw).join("");case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:if(node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.property.name;return node.property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.property.value==="string"?node.property.value:"";default:return""}}function storesACredential(node){return node.arguments.some(argument=>{if(argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return false;return namesACredential(nameOf(argument))})}function expressionNamesACredential(node){return namesACredential(nameOf(node))}const CRYPTO_MODULES=new Set(["crypto","crypto-js","node-forge","tweetnacl","libsodium","libsodium-wrappers","libsodium-wrappers-sumo","sodium-native","jose","node-jose","@aws-crypto/client-node","@google-cloud/kms"]);const ENCRYPTION_APIS=new Set(["publicEncrypt","privateEncrypt","createCipheriv","createCipher","encrypt","wrapKey","CompactEncrypt","FlattenedEncrypt","GeneralEncrypt","compactEncrypt","crypto_secretbox_easy","crypto_box_easy","crypto_box_seal","crypto_aead_chacha20poly1305_ietf_encrypt","crypto_aead_xchacha20poly1305_ietf_encrypt","crypto_secretstream_xchacha20poly1305_push","secretbox","box"]);const CIPHER_OUTPUT_METHODS=new Set(["update","final"]);function localFunctionOf(node,scope){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;let current=scope;let variable;while(current&&!variable){variable=current.set?.get(node.name);current=current.upper}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?.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression||init?.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression){return init}return null}function returnedExpressions(fn){const body=fn.body;if(!body)return[];if(body.type!==eslint_devkit_1.AST_NODE_TYPES.BlockStatement)return[body];const found=[];const walk=statements=>{for(const statement of statements){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){if(statement.argument)found.push(statement.argument)}else if(statement.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){walk([statement.consequent]);if(statement.alternate)walk([statement.alternate])}else if(statement.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){walk(statement.body)}else if(statement.type===eslint_devkit_1.AST_NODE_TYPES.TryStatement){walk(statement.block.body)}}};walk(body.body);return found}function provablyEncrypts(value,sourceCode,depth,seen){if(depth>8||seen.has(value))return false;seen.add(value);const recurse=node=>provablyEncrypts(node,sourceCode,depth+1,seen);switch(value.type){case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:return recurse(value.argument);case eslint_devkit_1.AST_NODE_TYPES.ArrayExpression:return value.elements.some(element=>element!==null&&recurse(element));case eslint_devkit_1.AST_NODE_TYPES.Identifier:{let current=sourceCode.getScope(value);let variable;while(current&&!variable){variable=current.set?.get(value.name);current=current.upper}if(!variable||variable.defs.length!==1)return false;const def=variable.defs[0];if(def.type!=="Variable"||!def.node.init)return false;return recurse(def.node.init)}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{const callee=value.callee;const scope=sourceCode.getScope(value);const binding=(0,eslint_devkit_1.resolveModuleBinding)(callee,scope);if(binding&&CRYPTO_MODULES.has(binding.module)&&binding.path.length>0&&ENCRYPTION_APIS.has(binding.path[binding.path.length-1])){return true}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),CIPHER_OUTPUT_METHODS)&&recurse(callee.object)){return true}const fn=localFunctionOf(callee,scope);if(fn&&returnedExpressions(fn).some(returned=>recurse(returned))){return true}return value.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&recurse(argument))}default:return false}}function isEncrypted(node,sourceCode){return isEncryptedExpression(node.arguments[1],sourceCode)}function isEncryptedExpression(value,sourceCode){if(value===void 0)return false;return provablyEncrypts(value,sourceCode,0,new Set)}const CLIENT_STORES=new Set(["localStorage","sessionStorage","AsyncStorage"]);function isWebStorageWrite(node){const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if((0,eslint_devkit_1.propertyName)(callee)!=="setItem")return false;const object=callee.object;if(object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return CLIENT_STORES.has(object.name)}return object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(object),CLIENT_STORES)}function isEnvironmentWrite(node){const target=node.left;if(target.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const object=target.object;return object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&object.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&object.object.name==="process"&&(0,eslint_devkit_1.propertyName)(object)==="env"}function isFileWrite(node){const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const method=(0,eslint_devkit_1.propertyName)(callee);if(method===null)return false;return["writeFile","writeFileSync","appendFile","appendFileSync"].includes(method)}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.findVariable=findVariable;exports.bindingInit=bindingInit;exports.constLiteralOf=constLiteralOf;exports.makeReadsTaintSource=makeReadsTaintSource;const eslint_devkit_1=require("@interlace/eslint-devkit");function findVariable(sourceCode,node){let scope=sourceCode.getScope(node);while(scope){const found=scope.set?.get(node.name);if(found)return found;scope=scope.upper}return null}function bindingInit(sourceCode,node){const variable=findVariable(sourceCode,node);if(!variable||variable.defs.length!==1)return void 0;const def=variable.defs[0];if(def.type!=="Variable")return void 0;return def.node.init?(0,eslint_devkit_1.unwrapTypeSyntax)(def.node.init):void 0}function constLiteralOf(sourceCode,node){const variable=findVariable(sourceCode,node);if(!variable||variable.defs.length!==1)return void 0;const def=variable.defs[0];if(def.type!=="Variable")return void 0;if(def.parent.kind!=="const")return void 0;if(def.node.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return void 0;const init=def.node.init;if(!init)return void 0;if(init.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return init;if(init.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral&&init.expressions.length===0){return init}return void 0}const REQUEST_PROPERTY_NAMES=new Set(["headers","query","body","params","cookies","searchparams","rawbody","querystringparameters","pathparameters","formdata"]);function makeReadsTaintSource(sourceCode,roots){const reads=(node,depth)=>{if(depth>6)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return reads(bare,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{if(roots.has(node.name.toLowerCase()))return true;const init=bindingInit(sourceCode,node);if(init!==void 0&&reads(init,depth+1))return true;const variable=findVariable(sourceCode,node);const priorWrites=(variable?.references??[]).map(ref=>ref.writeExpr).filter(write=>write!=null).filter(write=>write.range[1]<=node.range[0]).sort((a,b)=>a.range[1]-b.range[1]);const lastWrite=priorWrites.at(-1);return lastWrite!==void 0&&reads(lastWrite,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{if(!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&REQUEST_PROPERTY_NAMES.has(node.property.name.toLowerCase())){return true}return reads(node.object,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(expression=>reads(expression,depth+1));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return reads(node.left,depth+1)||reads(node.right,depth+1);case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:return reads(node.left,depth+1)||reads(node.right,depth+1);case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:return reads(node.consequent,depth+1)||reads(node.alternate,depth+1);case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:return reads(node.argument,depth+1);case eslint_devkit_1.AST_NODE_TYPES.ArrayExpression:return node.elements.some(element=>element!==null&&reads(element,depth+1));case eslint_devkit_1.AST_NODE_TYPES.SpreadElement:return reads(node.argument,depth+1);case eslint_devkit_1.AST_NODE_TYPES.CallExpression:case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(argument=>reads(argument,depth+1))||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==="process")&&reads(node.callee.object,depth+1);default:return false}};return node=>reads(node,0)}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.findVariable=findVariable;exports.bindingInit=bindingInit;exports.constLiteralOf=constLiteralOf;exports.makeReadsTaintSource=makeReadsTaintSource;const eslint_devkit_1=require("@interlace/eslint-devkit");function findVariable(sourceCode,node){let scope=sourceCode.getScope(node);while(scope){const found=scope.set?.get(node.name);if(found)return found;scope=scope.upper}return null}function bindingInit(sourceCode,node){const variable=findVariable(sourceCode,node);if(!variable||variable.defs.length!==1)return void 0;const def=variable.defs[0];if(def.type!=="Variable")return void 0;return def.node.init?(0,eslint_devkit_1.unwrapTypeSyntax)(def.node.init):void 0}function constLiteralOf(sourceCode,node){const variable=findVariable(sourceCode,node);if(!variable||variable.defs.length!==1)return void 0;const def=variable.defs[0];if(def.type!=="Variable")return void 0;if(def.parent.kind!=="const")return void 0;if(def.node.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return void 0;const init=def.node.init;if(!init)return void 0;if(init.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return init;if(init.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral&&init.expressions.length===0){return init}return void 0}const REQUEST_PROPERTY_NAMES=new Set(["headers","query","body","params","cookies","searchparams","rawbody","querystringparameters","pathparameters","formdata"]);function makeReadsTaintSource(sourceCode,roots){const reads=(node,depth)=>{if(depth>6)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return reads(bare,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{if(roots.has(node.name.toLowerCase()))return true;const init=bindingInit(sourceCode,node);if(init!==void 0&&reads(init,depth+1))return true;const variable=findVariable(sourceCode,node);const priorWrites=(variable?.references??[]).map(ref=>ref.writeExpr).filter(write=>write!=null).filter(write=>write.range[1]<=node.range[0]).sort((a,b)=>a.range[1]-b.range[1]);const lastWrite=priorWrites.at(-1);return lastWrite!==void 0&&reads(lastWrite,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{const surface=(0,eslint_devkit_1.propertyName)(node);if(surface!==null&&REQUEST_PROPERTY_NAMES.has(surface.toLowerCase())){return true}return reads(node.object,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(expression=>reads(expression,depth+1));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return reads(node.left,depth+1)||reads(node.right,depth+1);case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:return reads(node.left,depth+1)||reads(node.right,depth+1);case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:return reads(node.consequent,depth+1)||reads(node.alternate,depth+1);case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:return reads(node.argument,depth+1);case eslint_devkit_1.AST_NODE_TYPES.ArrayExpression:return node.elements.some(element=>element!==null&&reads(element,depth+1));case eslint_devkit_1.AST_NODE_TYPES.SpreadElement:return reads(node.argument,depth+1);case eslint_devkit_1.AST_NODE_TYPES.CallExpression:case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(argument=>reads(argument,depth+1))||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==="process")&&reads(node.callee.object,depth+1);default:return false}};return node=>reads(node,0)}