eslint-plugin-node-security 4.13.1 → 5.0.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 (45) hide show
  1. package/package.json +2 -2
  2. package/src/index.js +1 -1
  3. package/src/rules/detect-child-process/index.js +1 -1
  4. package/src/rules/detect-eval-with-expression/index.js +1 -1
  5. package/src/rules/detect-non-literal-fs-filename/index.js +1 -1
  6. package/src/rules/detect-suspicious-dependencies/index.js +1 -1
  7. package/src/rules/lock-file/index.js +1 -1
  8. package/src/rules/no-buffer-overread/index.js +1 -1
  9. package/src/rules/no-cryptojs/index.js +1 -1
  10. package/src/rules/no-cryptojs-weak-random/index.js +1 -1
  11. package/src/rules/no-data-in-temp-storage/index.js +1 -1
  12. package/src/rules/no-deprecated-buffer/index.js +1 -1
  13. package/src/rules/no-deprecated-cipher-method/index.js +1 -1
  14. package/src/rules/no-dynamic-command-string/index.js +1 -1
  15. package/src/rules/no-dynamic-dependency-loading/index.js +1 -1
  16. package/src/rules/no-dynamic-require/index.js +1 -1
  17. package/src/rules/no-ecb-mode/index.js +1 -1
  18. package/src/rules/no-env-injection/index.js +1 -1
  19. package/src/rules/no-insecure-http-parser/index.js +1 -1
  20. package/src/rules/no-insecure-key-derivation/index.js +1 -1
  21. package/src/rules/no-insecure-rsa-padding/index.js +1 -1
  22. package/src/rules/no-math-random-crypto/index.js +1 -1
  23. package/src/rules/no-self-signed-certs/index.js +1 -1
  24. package/src/rules/no-sha1-hash/index.js +1 -1
  25. package/src/rules/no-shell-injection/index.js +1 -1
  26. package/src/rules/no-ssrf/index.js +1 -1
  27. package/src/rules/no-static-iv/index.js +1 -1
  28. package/src/rules/no-timing-unsafe-compare/index.js +1 -1
  29. package/src/rules/no-toctou-vulnerability/index.js +1 -1
  30. package/src/rules/no-unbounded-decompression/index.js +1 -1
  31. package/src/rules/no-unsafe-buffer-alloc/index.js +1 -1
  32. package/src/rules/no-unsafe-dynamic-require/index.js +1 -1
  33. package/src/rules/no-weak-cipher-algorithm/index.js +1 -1
  34. package/src/rules/no-weak-hash-algorithm/index.js +1 -1
  35. package/src/rules/no-zip-slip/index.js +1 -1
  36. package/src/rules/prefer-native-crypto/index.js +1 -1
  37. package/src/rules/require-aead-tag-verification/index.js +1 -1
  38. package/src/rules/require-dependency-integrity/index.js +1 -1
  39. package/src/rules/require-secure-credential-storage/index.js +1 -1
  40. package/src/rules/require-secure-deletion/index.js +1 -1
  41. package/src/rules/require-storage-encryption/index.js +1 -1
  42. package/src/rules/require-stream-error-handler/index.js +1 -1
  43. package/src/utils/const-value.js +1 -0
  44. package/src/utils/credential-evidence.js +1 -1
  45. 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&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(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;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 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeBufferAlloc=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const UNSAFE_ALLOCATORS=new Set(["allocUnsafe","allocUnsafeSlow"]);const SIZED_ALLOCATORS=new Set(["Array","Buffer","Uint8Array","Uint16Array","Uint32Array","Int8Array","Int16Array","Int32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array","ArrayBuffer","SharedArrayBuffer"]);function looksNumeric(node){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Literal:return typeof node.value==="number";case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return["+","-","*","/","%","<<",">>",">>>"].includes(node.operator);case eslint_devkit_1.AST_NODE_TYPES.Identifier:return COUNT_NAMES.has(node.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&COUNT_NAMES.has(node.property.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.object.name==="Math"){return true}const name=calleeName(node.callee);return name!==null&&/^(read|len|size|count|decode|parse)/i.test(name)}default:return false}}const COUNT_NAMES=new Set(["length","len","size","count","n","num","total","capacity","bytelength"]);const BUFFER_ALLOCATORS=new Set(["alloc","allocUnsafe","allocUnsafeSlow"]);const WIRE_NAMES=new Set(["chunk","chunks","buffer","buf","data","payload","frame","packet","bytes","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"]);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;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 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"}),clampAllocation:"Clamp the size against the maximum the protocol permits."},schema:[]},defaultOptions:[],create(context){const wireParams=new Map;const pendingAllocations=[];const pendingCallSites=[];const bindings=new Map;function readsWire(node,depth=0){if(depth>10)return false;switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const lower=node.name.toLowerCase();if(WIRE_NAMES.has(lower)||REQUEST_ROOTS.has(lower))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 bound=bindings.get(node.name);return bound!==void 0&&readsWire(bound,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return readsWire(node.object,depth+1);case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&readsWire(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.CallExpression:return readsWire(node.callee,depth+1)||node.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&readsWire(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return readsWire(node.left,depth+1)||readsWire(node.right,depth+1);default:return false}}function enclosingFunction(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){return current}current=current.parent}return null}function paramIndexOf(node,owner){const params=owner.params;const index=params.findIndex(param=>param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&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 true}if(size.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=size.name;let current=size.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){if(mentionsInComparison(current.test,name))return true}if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){const body=current.body;if(body.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){for(const statement of body.body){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement&&mentionsInComparison(statement.test,name)){return true}}}return false}current=current.parent}return false}function mentionsInComparison(test,name){if(test.type===eslint_devkit_1.AST_NODE_TYPES.LogicalExpression){return mentionsInComparison(test.left,name)||mentionsInComparison(test.right,name)}if(test.type!==eslint_devkit_1.AST_NODE_TYPES.BinaryExpression)return false;if(!["<","<=",">",">="].includes(test.operator))return false;const named=side=>side.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&side.name===name;return named(test.left)||named(test.right)}function allocationSize(node){const size=node.arguments[0];if(size===void 0||size.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return null;const callee=node.callee;if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&SIZED_ALLOCATORS.has(callee.name)){return looksNumeric(size)?size:null}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&BUFFER_ALLOCATORS.has(callee.property.name)){return size}return null}function recordCallSite(node){const name=calleeName(node.callee);if(name===null)return;node.arguments.forEach((argument,index)=>{if(argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return;if(!readsWire(argument))return;const indices=wireParams.get(name)??new Set;indices.add(index);wireParams.set(name,indices)})}function judgeAllocation(node,size){if(isClamped(size))return;if(!readsWire(size))return;context.report({node,messageId:"unboundedAllocation",suggest:[{messageId:"clampAllocation",fix:()=>null}]})}function classifyUse(identifier){const parent=identifier.parent;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===identifier){if(parent.computed){const grandparent2=parent.parent;return grandparent2.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&grandparent2.left===parent?"partial":"read"}if(parent.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return"read";const name=parent.property.name;if(METADATA_PROPERTIES.has(name))return"metadata";const grandparent=parent.parent;if(grandparent.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||grandparent.callee!==parent||!isWriteMethod(name)){return"read"}return coversWholeBuffer(grandparent,name)?"covering":"partial"}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.arguments[0]===identifier){const callee=parent.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"read"}return coversWholeBuffer(parent,callee.property.name)?"covering":"partial"}return"read"}function isCoveredBeforeRead(call){const declarator=call.parent;if(declarator.type!==eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator||declarator.init!==call||declarator.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return false}const variable=context.sourceCode.getDeclaredVariables(declarator)[0];const uses=variable.references.filter(reference=>reference.identifier!==declarator.id).sort((a,b)=>a.identifier.range[0]-b.identifier.range[0]);for(const use of uses){const kind=classifyUse(use.identifier);if(kind==="metadata"||kind==="partial")continue;return kind==="covering"}return false}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{VariableDeclarator(node){if(node.init!==null&&node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){bindings.set(node.id.name,node.init)}},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;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||!UNSAFE_ALLOCATORS.has(callee.property.name)){return}if(isFilledInPlace(node))return;if(isCoveredBeforeRead(node))return;context.report({node,messageId:callee.property.name==="allocUnsafe"?"unsafeAlloc":"unsafeAllocSlow",suggest:[{messageId:"useSafeAlloc",fix:fixer=>fixer.replaceText(callee.property,"alloc")}]})}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeBufferAlloc=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const provenance_1=require("../../utils/provenance");const UNSAFE_ALLOCATORS=new Set(["allocUnsafe","allocUnsafeSlow"]);const SIZED_ALLOCATORS=new Set(["Array","Buffer","Uint8Array","Uint16Array","Uint32Array","Int8Array","Int16Array","Int32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array","ArrayBuffer","SharedArrayBuffer"]);function looksNumeric(node){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Literal:return typeof node.value==="number";case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return["+","-","*","/","%","<<",">>",">>>"].includes(node.operator);case eslint_devkit_1.AST_NODE_TYPES.Identifier:return COUNT_NAMES.has(node.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&COUNT_NAMES.has(node.property.name.toLowerCase());case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.object.name==="Math"){return true}const name=calleeName(node.callee);return name!==null&&/^(read|len|size|count|decode|parse)/i.test(name)}default:return false}}const COUNT_NAMES=new Set(["length","len","size","count","n","num","total","capacity","bytelength"]);const BUFFER_ALLOCATORS=new Set(["alloc","allocUnsafe","allocUnsafeSlow"]);const WIRE_NAMES=new Set(["chunk","chunks","buffer","buf","data","payload","frame","packet","raw","message","msg"]);const REQUEST_ROOTS=new Set(["req","request","ctx","event"]);function isWriteMethod(name){return name==="fill"||name==="set"||name.startsWith("write")}const DESTINATION_ARGUMENT_CALLS=new Set(["copy","randomFill","randomFillSync"]);const METADATA_PROPERTIES=new Set(["length","byteLength","byteOffset","buffer"]);const WRITE_WIDTHS=new Map([["writeUInt8",1],["writeInt8",1],["writeUInt16LE",2],["writeUInt16BE",2],["writeInt16LE",2],["writeInt16BE",2],["writeUInt32LE",4],["writeUInt32BE",4],["writeInt32LE",4],["writeInt32BE",4],["writeFloatLE",4],["writeFloatBE",4],["writeDoubleLE",8],["writeDoubleBE",8],["writeBigInt64LE",8],["writeBigInt64BE",8],["writeBigUInt64LE",8],["writeBigUInt64BE",8]]);const MAX_TRACKED_ALLOCATION=4096;const BUFFER_MODULES=new Set(["buffer","node:buffer"]);const CRYPTO_MODULES=new Set(["crypto","node:crypto"]);const RANDOM_FILL_CALLS=new Set(["randomFill","randomFillSync"]);function isCryptoModuleRequire(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"&&node.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.arguments[0].value==="string"&&CRYPTO_MODULES.has(node.arguments[0].value)}function isBufferModuleRequire(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"&&node.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.arguments[0].value==="string"&&BUFFER_MODULES.has(node.arguments[0].value)}function isBufferObject(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name==="Buffer";return node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.property.name==="Buffer"&&isBufferModuleRequire(node.object)}function isInsideLoop(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.ForStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.ForOfStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.ForInStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.WhileStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.DoWhileStatement){return true}current=current.parent}return false}function coversWholeBuffer(call,method){if(method==="fill")return true;const width=WRITE_WIDTHS.get(method);if(width!==void 0){const offset2=call.arguments[1];return offset2!==void 0&&offset2.type!==eslint_devkit_1.AST_NODE_TYPES.Literal&&isInsideLoop(call)}if(call.arguments.length===1)return true;const offset=call.arguments[1];return offset.type!==eslint_devkit_1.AST_NODE_TYPES.Literal&&isInsideLoop(call)}function fixedWriteSpan(call,method){const width=WRITE_WIDTHS.get(method);if(width===void 0)return null;const offset=call.arguments[1];if(offset===void 0)return{start:0,end:width};if(offset.type!==eslint_devkit_1.AST_NODE_TYPES.Literal||typeof offset.value!=="number"){return null}return{start:offset.value,end:offset.value+width}}function calleeName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed){return callee.property.name}return null}function declaredName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&&node.id)return node.id.name;const parent=node.parent;if(parent?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if((parent?.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition||parent?.type===eslint_devkit_1.AST_NODE_TYPES.Property)&&!parent.computed){const key=parent.key;return key.name}return null}exports.noUnsafeBufferAlloc=(0,eslint_devkit_1.createRule)({name:"no-unsafe-buffer-alloc",meta:{type:"problem",hasSuggestions:true,docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-unsafe-buffer-alloc.md",description:"Disallow `Buffer.allocUnsafe()` and `Buffer.allocUnsafeSlow()`, which return uninitialized memory",cwe:"CWE-908",cweJustification:"CWE-908 (Use of Uninitialized Resource) \u2014 allocUnsafe returns a view over non-zeroed heap memory; any byte not overwritten before the buffer is read or transmitted discloses prior process memory.",cvss:7.5,confidence:"high"},messages:{unsafeAlloc:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Uninitialized Buffer Allocation",cwe:"CWE-908",cvss:7.5,description:"`Buffer.allocUnsafe(size)` returns memory that has not been zeroed. Every byte not overwritten before the buffer is read or sent leaks whatever the allocator previously stored there.",severity:"HIGH",fix:"Use `Buffer.alloc(size)` (zero-filled), or keep `allocUnsafe` only where the very next statement overwrites the whole buffer \u2014 `Buffer.allocUnsafe(size).fill(0)` is accepted by this rule.",documentationLink:"https://nodejs.org/api/buffer.html#static-method-bufferallocunsafesize"}),unsafeAllocSlow:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Uninitialized Buffer Allocation (allocUnsafeSlow)",cwe:"CWE-908",cvss:7.5,description:"`Buffer.allocUnsafeSlow(size)` allocates outside the shared pool but is equally uninitialized \u2014 the returned bytes are whatever was last in that memory.",severity:"HIGH",fix:"Use `Buffer.alloc(size)`, or append `.fill(0)` to zero the allocation at the call site.",documentationLink:"https://nodejs.org/api/buffer.html#static-method-bufferallocunsafeslowsize"}),useSafeAlloc:"Replace with `Buffer.alloc()` (zero-filled).",unboundedAllocation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Allocation Sized By Untrusted Input",cwe:"CWE-789",cvss:7.5,description:'The allocation size is read off the wire, so the peer picks it. For `new Array(n)` the hazard is a narrow band, not "large n": V8 keeps a packed backing store only up to ~33.5M elements, and a length just under that turns a 12-byte length prefix into a 229MB allocation (measured: `new Array(3e7)` = 228.9MB in 20.7ms). Past the threshold V8 switches the array to dictionary mode and the allocation costs nothing at all (`new Array(4e7)` and `new Array(1e9)` are both 0.0MB in ~0.005ms). A typed allocation \u2014 `Buffer.alloc(n)`, `new Uint8Array(n)` \u2014 has no such threshold and commits n bytes at any n.',severity:"HIGH",fix:'Clamp the length against the maximum the protocol actually permits, before allocating: `if (length > MAX_LENGTH) throw new Error("too long")`, or `new Array(Math.min(length, MAX_LENGTH))`. A guard that only rejects implausibly huge values is not a fix for `new Array` \u2014 those are the sizes V8 makes free. The damaging lengths are the plausible ones just below the packed-elements limit.',documentationLink:"https://cwe.mitre.org/data/definitions/789.html"})},schema:[]},defaultOptions:[],create(context){const sourceCode=context.sourceCode;const wireParams=new Map;const pendingAllocations=[];const pendingCallSites=[];function readsWire(node,depth=0){if(depth>10)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return readsWire(bare,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{if(node.name==="Buffer"){const bound=(0,provenance_1.findVariable)(sourceCode,node);if(bound===null||bound.defs.length===0)return false;if(bound.defs[0].type==="ImportBinding")return false}const lower=node.name.toLowerCase();if(WIRE_NAMES.has(lower)||REQUEST_ROOTS.has(lower))return true;const owner=enclosingFunction(node);if(owner!==null){const name=declaredName(owner);const indices=name===null?void 0:wireParams.get(name);if(indices!==void 0){const index=paramIndexOf(node,owner);if(index!==null&&indices.has(index))return true}}const variable=(0,provenance_1.findVariable)(sourceCode,node);const lastWrite=(variable?.references??[]).map(reference=>reference.writeExpr).filter(write=>write!=null).filter(write=>write.range[1]<=node.range[0]).sort((a,b)=>a.range[1]-b.range[1]).at(-1);return lastWrite!==void 0&&readsWire(lastWrite,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return readsWire(node.object,depth+1);case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&readsWire(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.CallExpression:return readsWire(node.callee,depth+1)||node.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&readsWire(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return readsWire(node.left,depth+1)||readsWire(node.right,depth+1);default:return false}}function enclosingFunction(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){return current}current=current.parent}return null}function paramIndexOf(node,owner){const params=owner.params;const index=params.findIndex(param=>param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&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)?size:null}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&BUFFER_ALLOCATORS.has(callee.property.name)){return size}return null}function recordCallSite(node){const name=calleeName(node.callee);if(name===null)return;node.arguments.forEach((argument,index)=>{if(argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return;if(!readsWire(argument))return;const indices=wireParams.get(name)??new Set;indices.add(index);wireParams.set(name,indices)})}function judgeAllocation(node,size){if(isClamped(size))return;if(!readsWire(size))return;context.report({node,messageId:"unboundedAllocation"})}function classifyUse(identifier){const parent=identifier.parent;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===identifier){if(parent.computed){const grandparent2=parent.parent;return grandparent2.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&grandparent2.left===parent?"partial":"read"}if(parent.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return"read";const name=parent.property.name;if(METADATA_PROPERTIES.has(name))return"metadata";const grandparent=parent.parent;if(grandparent.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||grandparent.callee!==parent||!isWriteMethod(name)){return"read"}return coversWholeBuffer(grandparent,name)?"covering":"partial"}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.arguments[0]===identifier){const method=destinationArgumentCallee(parent.callee);if(method===null)return"read";return coversWholeBuffer(parent,method)?"covering":"partial"}return"read"}function destinationArgumentCallee(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&DESTINATION_ARGUMENT_CALLS.has(callee.property.name)){return callee.property.name}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||!RANDOM_FILL_CALLS.has(callee.name)){return null}const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(variable===null||variable.defs.length===0)return null;const def=variable.defs[0];if(def.type==="ImportBinding"){const declaration=def.parent;return declaration.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration&&CRYPTO_MODULES.has(declaration.source.value)?callee.name:null}return def.type==="Variable"&&def.node.init!==null&&isCryptoModuleRequire(def.node.init)?callee.name:null}function isCoveredBeforeRead(call){const declarator=call.parent;if(declarator.type!==eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator||declarator.init!==call||declarator.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return false}const variable=context.sourceCode.getDeclaredVariables(declarator)[0];const uses=variable.references.filter(reference=>reference.identifier!==declarator.id).sort((a,b)=>a.identifier.range[0]-b.identifier.range[0]);const covered=byteMapFor(call);let remaining=covered===null?-1:covered.length;for(const use of uses){const kind=classifyUse(use.identifier);if(kind==="metadata")continue;if(kind==="partial"){if(covered!==null){remaining-=markFixedWrite(covered,use.identifier);if(remaining===0)return true}continue}return kind==="covering"}return false}function byteMapFor(call){const argument=call.arguments[0];if(argument===void 0||argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement){return null}const resolved=(0,const_value_1.resolveConstant)(sourceCode,argument);if(resolved===null||typeof resolved.value!=="number")return null;const size=resolved.value;if(!Number.isInteger(size)||size<=0||size>MAX_TRACKED_ALLOCATION){return null}return new Uint8Array(size)}function markFixedWrite(covered,identifier){const member=identifier.parent;if(member.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||member.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return 0}const call=member.parent;if(call.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return 0;const span=fixedWriteSpan(call,member.property.name);if(span===null||span.start<0||span.end>covered.length)return 0;let added=0;for(let index=span.start;index<span.end;index+=1){if(covered[index]===0){covered[index]=1;added+=1}}return added}function unsafeAllocator(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(!isBufferObject(callee.object))return null;const name=callee.computed?(0,const_value_1.resolveConstantString)(sourceCode,callee.property)?.value:callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property.name:void 0;return name!==void 0&&UNSAFE_ALLOCATORS.has(name)?name:null}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(variable===null||variable.defs.length===0)return null;const def=variable.defs[0];if(def.type!=="Variable"||def.node.init===null)return null;if(!isBufferObject(def.node.init))return null;const property=def.name.parent;if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property||property.computed||property.key.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return null}return UNSAFE_ALLOCATORS.has(property.key.name)?property.key.name:null}function isFilledInPlace(node){const parent=node.parent;if(parent?.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||parent.object!==node||parent.computed||parent.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||parent.property.name!=="fill"){return false}const grandparent=parent.parent;return grandparent?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&grandparent.callee===parent}return{NewExpression(node){pendingCallSites.push(node);const size=allocationSize(node);if(size!==null)pendingAllocations.push({node,size})},"Program:exit"(){for(const call of pendingCallSites)recordCallSite(call);for(const{node,size}of pendingAllocations)judgeAllocation(node,size)},CallExpression(node){pendingCallSites.push(node);const size=allocationSize(node);if(size!==null)pendingAllocations.push({node,size});const callee=node.callee;const allocator=unsafeAllocator(callee);if(allocator===null)return;if(isFilledInPlace(node))return;if(isCoveredBeforeRead(node))return;const rewritable=callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property:null;context.report({node,messageId:allocator==="allocUnsafe"?"unsafeAlloc":"unsafeAllocSlow",...rewritable===null?{}:{suggest:[{messageId:"useSafeAlloc",fix:fixer=>fixer.replaceText(rewritable,"alloc")}]}})}}}});
@@ -1 +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 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"})},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 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 false};const isDangerousSpecifier=arg=>{if(arg.type==="Literal")return false;if(arg.type==="TemplateLiteral"&&arg.expressions.length===0)return false;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)}}},CallExpression(node){if(node.callee.type!=="Identifier"){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.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 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakCipherAlgorithm=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");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&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(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?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof firstArg.value==="string"){const weakPattern=findWeakCipher(firstArg.value,additionalWeakCiphers);if(weakPattern){context.report({node:firstArg,messageId:"weakCipherAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useAes256Gcm",fix:fixer=>fixer.replaceText(firstArg,`"aes-256-gcm"`)},{messageId:"useChaCha20",fix:fixer=>fixer.replaceText(firstArg,`"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&&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 +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 WEAK_HASH_PATTERNS=[{pattern:/\bmd5\b/i,name:"MD5",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bmd4\b/i,name:"MD4",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bsha1\b/i,name:"SHA-1",alternatives:["SHA-256","SHA-512","SHA-3"],replacement:"sha256"},{pattern:/\bripemd\b/i,name:"RIPEMD",alternatives:["SHA-256","SHA-512"],replacement:"sha256"}];const DEFAULT_NON_CRYPTOGRAPHIC_NAMES=["sha","etag","cachekey","cachebuster"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!target.computed&&target.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return target.property.name}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return node.property.name}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&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 filename=context.filename;const isTestFile=allowInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);const nonCryptoNames=new Set(nonCryptographicNames.map(normalizeName));function isNonCryptographicUse(node){const name=assignedName(node);return name!==null&&nonCryptoNames.has(normalizeName(name))}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function checkCallExpression(node){if(isTestFile)return;if(isNonCryptographicUse(node))return;if(!reportUnclassifiedHashes&&!hasSecurityUse(node))return;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.property.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const funcName=node.callee.name.toLowerCase();if(funcName==="sha1"||funcName==="md5"||funcName==="md4"){const weakPattern=findWeakHash(funcName,additionalWeakAlgorithms);context.report({node,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return fixer.replaceText(node.callee,"sha256")}return null}}]})}}}function checkHashArgument(node){for(const arg of node.arguments){if(arg.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof arg.value==="string"){const weakPattern=findWeakHash(arg.value,additionalWeakAlgorithms);if(weakPattern){context.report({node:arg,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>fixer.replaceText(arg,`"sha256"`)},{messageId:"useSha512",fix:fixer=>fixer.replaceText(arg,`"sha512"`)},{messageId:"useSha3",fix:fixer=>fixer.replaceText(arg,`"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"];const DEFAULT_SECURITY_USE_NAMES=["password","passwd","secret","secrets","token","tokens","signature","signing","signed","sign","hmac","credential","credentials","certificate","cert","certs","apikey","privatekey","secretkey","signingkey","encryptionkey","session","csrf","salt","jwt","nonce","integrity","auth","authorization","authenticate"];function normalizeName(name){return name.replaceAll(/[_-]/g,"").toLowerCase()}function assignedName(node){let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){current=parent;parent=current.parent;continue}break}if(!parent)return null;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.init===current){return parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id.name:null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&parent.right===current){const target=parent.left;if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return target.name;if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!target.computed&&target.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return target.property.name}return null}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===current){if(parent.computed)return null;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return parent.key.name;if(parent.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof parent.key.value==="string"){return parent.key.value}return null}return null}function expressionName(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return node.name;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return node.property.name}return null}function hashInputNames(node){const names=[];for(const argument of node.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}let current=node;let parent=current.parent;while(parent){if(parent.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&parent.object===current){current=parent;parent=current.parent;continue}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&parent.callee===current){for(const argument of parent.arguments){const name=expressionName(argument);if(name!==null)names.push(name)}current=parent;parent=current.parent;continue}break}return names}function enclosingFunctionName(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){if(current.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&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 filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);const nonCryptoNames=new Set(nonCryptographicNames.map(normalizeName));function isNonCryptographicUse(node){const name=assignedName(node);return name!==null&&nonCryptoNames.has(normalizeName(name))}function hasSecurityUse(node){const stored=assignedName(node);if(stored!==null&&isSecurityUse(stored))return true;for(const argument of hashInputNames(node)){if(isSecurityUse(argument))return true}const enclosing=enclosingFunctionName(node);return enclosing!==null&&isSecurityUse(enclosing)}function checkCallExpression(node){if(isTestFile)return;if(isNonCryptographicUse(node))return;if(!reportUnclassifiedHashes&&!hasSecurityUse(node))return;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.property.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="createHash"){checkHashArgument(node)}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const funcName=node.callee.name.toLowerCase();if(funcName==="sha1"||funcName==="md5"||funcName==="md4"){const weakPattern=findWeakHash(funcName,additionalWeakAlgorithms);context.report({node,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>{if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return fixer.replaceText(node.callee,"sha256")}return null}}]})}}}function checkHashArgument(node){for(const arg of node.arguments){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,arg);if(resolved===null)continue;const weakPattern=findWeakHash(resolved.value,additionalWeakAlgorithms);if(!weakPattern)continue;const target=resolved.source;context.report({node:arg,messageId:"weakHashAlgorithm",data:{algorithm:weakPattern.name,replacement:weakPattern.replacement},suggest:[{messageId:"useSha256",fix:fixer=>fixer.replaceText(target,`"sha256"`)},{messageId:"useSha512",fix:fixer=>fixer.replaceText(target,`"sha512"`)},{messageId:"useSha3",fix:fixer=>fixer.replaceText(target,`"sha3-256"`)}]})}}return{CallExpression:checkCallExpression}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noZipSlip=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const DEFAULT_ARCHIVE_MODULES=["adm-zip","unzipper","yauzl","yazl","tar","tar-fs","tar-stream","extract-zip","node-stream-zip","jszip","archiver","decompress","unzip-stream","zip-stream","gunzip-maybe","7zip-min","node-7z"];const containsPathTraversal=pathText=>{return/\.\.\//.test(pathText)||/\.\.\\/.test(pathText)||pathText.startsWith("..")||/\/\.\./.test(pathText)};const isDangerousDestination=destText=>{if(destText.startsWith("/tmp")||destText.includes("os.tmpdir")||destText.includes("TMPDIR")){return false}return destText.includes("/var")||destText.includes("/usr")||destText.includes("/etc")||destText.includes("/root")||destText.includes("/home")||destText.includes("C:\\Windows")||destText.includes("C:\\Program Files")||destText.includes("C:\\Users")};exports.noZipSlip=(0,eslint_devkit_1.createRule)({name:"no-zip-slip",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-zip-slip.md",description:"Detects zip slip/archive extraction vulnerabilities",cwe:"CWE-22"},hasSuggestions:true,messages:{zipSlipVulnerability:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Zip Slip Vulnerability",cwe:"CWE-22",description:"Archive extraction vulnerable to path traversal",severity:"{{severity}}",fix:"{{safeAlternative}}",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"}),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"}),useSafeArchiveExtraction:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Use Safe Archive Extraction",description:"Use libraries with built-in path validation",severity:"LOW",fix:"Use yauzl, safe-archive-extract, or similar safe libraries",documentationLink:"https://www.npmjs.com/package/yauzl"}),validateArchivePaths:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Validate Archive Paths",description:"Validate all archive entry paths",severity:"LOW",fix:"Check paths don't contain ../ and are within destination directory",documentationLink:"https://snyk.io/research/zip-slip-vulnerability"}),sanitizeArchiveNames:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.INFO,issueName:"Sanitize Archive Names",description:"Sanitize archive entry names",severity:"LOW",fix:"Use path.basename() or custom sanitization",documentationLink:"https://nodejs.org/api/path.html#pathbasenamepath-ext"}),strategyPathValidation:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.STRATEGY,issueName:"Path Validation Strategy",description:"Validate paths before any file operations",severity:"LOW",fix:"Check path.startsWith(destination) and no ../ sequences",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"}),strategySafeLibraries:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.STRATEGY,issueName:"Safe Libraries Strategy",description:"Use archive libraries with built-in safety",severity:"LOW",fix:"Use yauzl, adm-zip with validation, or safe-archive-extract",documentationLink:"https://www.npmjs.com/package/safe-archive-extract"}),strategySandboxing:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.STRATEGY,issueName:"Sandboxing Strategy",description:"Extract archives in sandboxed environment",severity:"LOW",fix:"Use temporary directories and restrict permissions",documentationLink:"https://nodejs.org/api/fs.html#fsopentempdirprefix-options-callback"})},schema:[{type:"object",properties:{archiveFunctions:{type:"array",items:{type:"string"},default:["extract","extractAll","extractAllTo","unzip","untar","extractArchive"]},pathValidationFunctions:{type:"array",items:{type:"string"},default:["validatePath","sanitizePath","checkPath","safePath"]},safeLibraries:{type:"array",items:{type:"string"},default:["yauzl","safe-archive-extract","tar-stream","unzipper"]},archiveModules:{type:"array",items:{type:"string"},default:DEFAULT_ARCHIVE_MODULES,description:"Module specifiers that mean this file works with archives"},reportWithoutArchiveContext:{type:"boolean",default:false,description:"Report entry-name and traversal shapes in files with no archive. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{archiveFunctions:["extract","extractAll","extractAllTo","unzip","untar","extractArchive"],pathValidationFunctions:["validatePath","sanitizePath","checkPath","safePath"],safeLibraries:["yauzl","safe-archive-extract","tar-stream","unzipper"]}],create(context){const options=context.options[0]||{};const{archiveFunctions=["extract","extractAll","extractAllTo","unzip","untar","extractArchive"],pathValidationFunctions=["validatePath","sanitizePath","checkPath","safePath"],safeLibraries=["yauzl","safe-archive-extract","tar-stream","unzipper"],archiveModules=DEFAULT_ARCHIVE_MODULES,reportWithoutArchiveContext=false}=options;const filename=context.filename;const archiveModuleSet=new Set(archiveModules.map(name=>name.toLowerCase()));let hasArchiveContext=reportWithoutArchiveContext;const isArchiveModule=specifier=>typeof specifier==="string"&&archiveModuleSet.has(specifier.toLowerCase());const ARCHIVE_NAME=/zip|tarball|archive|gzip|gunzip|untar|tarstream|\btar\b/i;const namesArchive=node=>{if(node.type==="Identifier")return ARCHIVE_NAME.test(node.name);if(node.type==="MemberExpression"){return namesArchive(node.object)||node.property.type==="Identifier"&&ARCHIVE_NAME.test(node.property.name)}return false};const pending=[];const isArchiveExtraction=node=>{const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&archiveFunctions.includes(callee.property.name)){return true}if(callee.type==="Identifier"&&archiveFunctions.includes(callee.name)){return true}return false};const isPathValidated=pathNode=>{let current=pathNode;while(current){if(current.type==="CallExpression"&&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(isArchiveExtraction(node)||namesArchive(node.callee))hasArchiveContext=true;if(isArchiveExtraction(node)&&!isSafeLibrary(node)){const sourceCode=context.sourceCode;let hasSafeAnnotation=false;const allComments=sourceCode.getAllComments();for(const comment of allComments){if(comment.type==="Block"&&comment.value.includes("@safe")){hasSafeAnnotation=true;break}}if(hasSafeAnnotation){return}const args=node.arguments;let destArg;if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const methodName=node.callee.property.name;if(["extractAllTo","unzip"].includes(methodName)){destArg=args[0]}else{destArg=args.length>=2?args[1]:void 0}}else{destArg=args.length>=2?args[1]:void 0}const destText=destArg&&destArg.type==="Literal"&&typeof destArg.value==="string"?destArg.value:"";const isDestDangerous=isDangerousDestination(destText);const isMethodCall=node.callee.type==="MemberExpression";if(isMethodCall){const isSafeRelativePath=destText.startsWith("./")||destText.startsWith("../");if(!isSafeRelativePath){context.report({node,messageId:"unsafeArchiveExtraction",data:{filePath:filename,line:String(node.loc?.start.line??0)},suggest:[{messageId:"useSafeArchiveExtraction",fix:()=>null}]})}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)},suggest:[{messageId:"useSafeArchiveExtraction",fix:()=>null}]})}}}const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&["join","resolve","relative","normalize"].includes(callee.property.name)){const args=node.arguments;for(const arg of args){if(arg.type==="MemberExpression"&&arg.property.type==="Identifier"&&["name","path","fileName","entryName","relativePath","filename","pathname"].includes(arg.property.name)){if(!isPathValidated(arg)){pending.push(()=>{if(!hasArchiveContext)return;context.report({node:arg,messageId:"unvalidatedArchivePath",data:{filePath:filename,line:String(node.loc?.start.line??0)}})})}}}}},Literal(node){if(typeof node.value!=="string"){return}const text=node.value;if(!(text.includes("/")||text.includes("\\"))||!containsPathTraversal(text)){return}let current=node;while(current){if(current.type==="CallExpression"&&isArchiveExtraction(current)){context.report({node,messageId:"pathTraversalInArchive",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}current=current.parent}},VariableDeclarator(node){if(!node.init||node.id.type!=="Identifier"){return}const varName=node.id.name.toLowerCase();if(varName.includes("entry")||varName.includes("file")||varName.includes("path")){if(node.init.type==="MemberExpression"&&node.init.property.type==="Identifier"&&["name","path"].includes(node.init.property.name)){}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noZipSlip=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const DEFAULT_ARCHIVE_MODULES=["adm-zip","unzipper","yauzl","yazl","tar","tar-fs","tar-stream","extract-zip","node-stream-zip","jszip","archiver","decompress","unzip-stream","zip-stream","gunzip-maybe","7zip-min","node-7z"];const containsPathTraversal=pathText=>{return/\.\.\//.test(pathText)||/\.\.\\/.test(pathText)||pathText.startsWith("..")||/\/\.\./.test(pathText)};const isDangerousDestination=destText=>{if(destText.startsWith("/tmp")||destText.includes("os.tmpdir")||destText.includes("TMPDIR")){return false}return destText.includes("/var")||destText.includes("/usr")||destText.includes("/etc")||destText.includes("/root")||destText.includes("/home")||destText.includes("C:\\Windows")||destText.includes("C:\\Program Files")||destText.includes("C:\\Users")};exports.noZipSlip=(0,eslint_devkit_1.createRule)({name:"no-zip-slip",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-zip-slip.md",description:"Detects zip slip/archive extraction vulnerabilities",cwe:"CWE-22"},messages:{unsafeArchiveExtraction:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Unsafe Archive Extraction",cwe:"CWE-22",description:"Archive extraction without path validation",severity:"HIGH",fix:"Use safe extraction libraries or validate all paths",documentationLink:"https://snyk.io/research/zip-slip-vulnerability"}),pathTraversalInArchive:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Path Traversal in Archive",cwe:"CWE-22",description:"Archive contains path traversal sequences",severity:"CRITICAL",fix:"Reject archives with path traversal or sanitize paths",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"}),unvalidatedArchivePath:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Unvalidated Archive Path",cwe:"CWE-22",description:"Archive entry path used without validation",severity:"HIGH",fix:"Validate paths before extraction",documentationLink:"https://snyk.io/research/zip-slip-vulnerability"}),dangerousArchiveDestination:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.SECURITY,issueName:"Dangerous Archive Destination",cwe:"CWE-22",description:"Archive extracted to sensitive location",severity:"MEDIUM",fix:"Extract to safe temporary directory",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"})},schema:[{type:"object",properties:{archiveFunctions:{type:"array",items:{type:"string"},default:["extract","extractAll","extractAllTo","unzip","untar","extractArchive"]},pathValidationFunctions:{type:"array",items:{type:"string"},default:["validatePath","sanitizePath","checkPath","safePath"]},safeLibraries:{type:"array",items:{type:"string"},default:["yauzl","safe-archive-extract","tar-stream","unzipper"]},archiveModules:{type:"array",items:{type:"string"},default:DEFAULT_ARCHIVE_MODULES,description:"Module specifiers that mean this file works with archives"},reportWithoutArchiveContext:{type:"boolean",default:false,description:"Report entry-name and traversal shapes in files with no archive. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{archiveFunctions:["extract","extractAll","extractAllTo","unzip","untar","extractArchive"],pathValidationFunctions:["validatePath","sanitizePath","checkPath","safePath"],safeLibraries:["yauzl","safe-archive-extract","tar-stream","unzipper"]}],create(context){const options=context.options[0]||{};const{archiveFunctions=["extract","extractAll","extractAllTo","unzip","untar","extractArchive"],pathValidationFunctions=["validatePath","sanitizePath","checkPath","safePath"],safeLibraries=["yauzl","safe-archive-extract","tar-stream","unzipper"],archiveModules=DEFAULT_ARCHIVE_MODULES,reportWithoutArchiveContext=false}=options;const filename=context.filename;const archiveModuleSet=new Set(archiveModules.map(name=>name.toLowerCase()));let hasArchiveContext=reportWithoutArchiveContext;const isArchiveModule=specifier=>typeof specifier==="string"&&archiveModuleSet.has(specifier.toLowerCase());const ARCHIVE_NAME=/zip|tarball|archive|gzip|gunzip|untar|tarstream|\btar\b/i;const namesArchive=node=>{if(node.type==="Identifier")return ARCHIVE_NAME.test(node.name);if(node.type==="MemberExpression"){return namesArchive(node.object)||node.property.type==="Identifier"&&ARCHIVE_NAME.test(node.property.name)}return false};const pending=[];const isArchiveExtraction=node=>{const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&archiveFunctions.includes(callee.property.name)){return true}if(callee.type==="Identifier"&&archiveFunctions.includes(callee.name)){return true}return false};const isPathValidated=pathNode=>{let current=pathNode;while(current){if(current.type==="CallExpression"&&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(isArchiveExtraction(node)||namesArchive(node.callee))hasArchiveContext=true;if(isArchiveExtraction(node)&&!isSafeLibrary(node)){const sourceCode=context.sourceCode;let hasSafeAnnotation=false;const allComments=sourceCode.getAllComments();for(const comment of allComments){if(comment.type==="Block"&&comment.value.includes("@safe")){hasSafeAnnotation=true;break}}if(hasSafeAnnotation){return}const args=node.arguments;let destArg;if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const methodName=node.callee.property.name;if(["extractAllTo","unzip"].includes(methodName)){destArg=args[0]}else{destArg=args.length>=2?args[1]:void 0}}else{destArg=args.length>=2?args[1]:void 0}const destText=destArg&&destArg.type==="Literal"&&typeof destArg.value==="string"?destArg.value:"";const isDestDangerous=isDangerousDestination(destText);const isMethodCall=node.callee.type==="MemberExpression";if(isMethodCall){const isSafeRelativePath=destText.startsWith("./")||destText.startsWith("../");if(!isSafeRelativePath){context.report({node,messageId:"unsafeArchiveExtraction",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}if(isDestDangerous&&destArg){context.report({node:destArg,messageId:"dangerousArchiveDestination",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}else{if(isDestDangerous){context.report({node,messageId:"dangerousArchiveDestination",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}else{context.report({node,messageId:"unsafeArchiveExtraction",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&["join","resolve","relative","normalize"].includes(callee.property.name)){const args=node.arguments;for(const arg of args){if(arg.type==="MemberExpression"&&arg.property.type==="Identifier"&&["name","path","fileName","entryName","relativePath","filename","pathname"].includes(arg.property.name)){if(!isPathValidated(arg)){pending.push(()=>{if(!hasArchiveContext)return;context.report({node:arg,messageId:"unvalidatedArchivePath",data:{filePath:filename,line:String(node.loc?.start.line??0)}})})}}}}},Literal(node){if(typeof node.value!=="string"){return}const text=node.value;if(!(text.includes("/")||text.includes("\\"))||!containsPathTraversal(text)){return}let current=node;while(current){if(current.type==="CallExpression"&&isArchiveExtraction(current)){context.report({node,messageId:"pathTraversalInArchive",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}current=current.parent}},VariableDeclarator(node){if(!node.init||node.id.type!=="Identifier"){return}const varName=node.id.name.toLowerCase();if(varName.includes("entry")||varName.includes("file")||varName.includes("path")){if(node.init.type==="MemberExpression"&&node.init.property.type==="Identifier"&&["name","path"].includes(node.init.property.name)){}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.preferNativeCrypto=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const THIRD_PARTY_CRYPTO_LIBS=new Set(["crypto-js","cryptojs","sjcl","forge","node-forge","jsencrypt","bcryptjs","js-sha256","js-sha512","js-sha3","js-md5","blueimp-md5","aes-js"]);exports.preferNativeCrypto=(0,eslint_devkit_1.createRule)({name:"prefer-native-crypto",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/prefer-native-crypto.md",description:"Prefer native crypto over third-party libraries",cwe:"CWE-1104",cvss:5.3},hasSuggestions:true,messages:{preferNative:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Third-party crypto library",cwe:"CWE-1104",description:"{{library}} is a third-party crypto library. Native crypto (Node.js crypto or Web Crypto API) is faster, more secure, and always maintained.",severity:"MEDIUM",fix:"Migrate to native crypto module",documentationLink:"https://nodejs.org/api/crypto.html"}),useNodeCrypto:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Node.js crypto",description:"Node.js crypto module is built-in and maintained",severity:"LOW",fix:'import crypto from "node:crypto"',documentationLink:"https://nodejs.org/api/crypto.html"}),useWebCrypto:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Web Crypto API",description:"Web Crypto API is built into browsers and Node.js 15+",severity:"LOW",fix:"globalThis.crypto.subtle",documentationLink:"https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API"})},schema:[{type:"object",properties:{severity:{type:"string",enum:["error","warn"],default:"warn",description:"Severity level"}},additionalProperties:false}]},defaultOptions:[{severity:"warn"}],create(context){function reportThirdPartyLib(node,library){context.report({node,messageId:"preferNative",data:{library},suggest:[{messageId:"useNodeCrypto",fix:()=>null},{messageId:"useWebCrypto",fix:()=>null}]})}return{ImportDeclaration(node){if(typeof node.source.value==="string"){const lib=node.source.value.split("/")[0];if(THIRD_PARTY_CRYPTO_LIBS.has(lib)){reportThirdPartyLib(node,lib)}}},CallExpression(node){if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"&&node.arguments.length===1&&node.arguments[0].type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.arguments[0].value==="string"){const lib=node.arguments[0].value.split("/")[0];if(THIRD_PARTY_CRYPTO_LIBS.has(lib)){reportThirdPartyLib(node,lib)}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.preferNativeCrypto=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const provenance_1=require("../../utils/provenance");function isCreateRequireCall(sourceCode,node){if(!node||node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;return(0,eslint_devkit_1.isModuleBinding)(node.callee,sourceCode.getScope(node),"module",["createRequire"])}function isRequireCallee(sourceCode,callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const def=(0,provenance_1.findVariable)(sourceCode,callee)?.defs[0];if(def?.type==="Variable"&&isCreateRequireCall(sourceCode,def.node.init))return true;if(callee.name!=="require")return false;if(def===void 0)return true;return def.type==="Variable"&&def.node.init?.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&def.node.init?.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionExpression}function moduleSpecifierListener(sourceCode,judge){const fromExpression=(report,source)=>{if(!source)return;const resolved=(0,const_value_1.resolveConstantString)(sourceCode,source);if(resolved!==null)judge(report,resolved.value)};return{ImportDeclaration:node=>fromExpression(node,node.source),ImportExpression:node=>fromExpression(node,node.source),ExportNamedDeclaration:node=>fromExpression(node,node.source),ExportAllDeclaration:node=>fromExpression(node,node.source),TSImportEqualsDeclaration:node=>{if(node.moduleReference.type===eslint_devkit_1.AST_NODE_TYPES.TSExternalModuleReference){fromExpression(node,node.moduleReference.expression)}},CallExpression:node=>{if(node.arguments.length===0)return;if(!isRequireCallee(sourceCode,node.callee))return;fromExpression(node,node.arguments[0])}}}const THIRD_PARTY_CRYPTO_LIBS=new Set(["crypto-js","cryptojs","sjcl","forge","node-forge","jsencrypt","js-sha256","js-sha512","js-sha3","js-md5","js-sha1","blueimp-md5","aes-js","md5","sha.js","hash.js"]);const PURE_JS_PASSWORD_HASH_LIBS=new Set(["bcryptjs","bcrypt-nodejs"]);exports.preferNativeCrypto=(0,eslint_devkit_1.createRule)({name:"prefer-native-crypto",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/prefer-native-crypto.md",description:"Prefer native crypto over third-party libraries",cwe:"CWE-1104",cvss:5.3},messages:{preferNative:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Third-party crypto library",cwe:"CWE-1104",description:"{{library}} is a third-party crypto library. Native crypto (Node.js crypto or Web Crypto API) is faster, more secure, and always maintained.",severity:"MEDIUM",fix:"Migrate to native crypto module",documentationLink:"https://nodejs.org/api/crypto.html"}),preferNativePasswordHash:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Pure-JS password hashing library",cwe:"CWE-1104",description:"{{library}} is a pure-JavaScript password hash. It is orders of magnitude slower than a native implementation, so the same cost factor buys far less protection, and its maintenance has repeatedly lagged the native bindings.",severity:"MEDIUM",fix:"Use the native `bcrypt` binding, or `argon2` (Argon2id) for new code. Do NOT reach for node:crypto's createHash here \u2014 it has no bcrypt, and a general-purpose digest is not a password hash (CWE-916). crypto.scrypt is the only node:crypto function in this category.",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html"})},schema:[]},defaultOptions:[{}],create(context){function checkSpecifier(node,specifier){const lib=specifier.split("/")[0];if(PURE_JS_PASSWORD_HASH_LIBS.has(lib)){context.report({node,messageId:"preferNativePasswordHash",data:{library:lib}});return}if(THIRD_PARTY_CRYPTO_LIBS.has(lib)){context.report({node,messageId:"preferNative",data:{library:lib}})}}return moduleSpecifierListener(context.sourceCode,checkSpecifier)}});
@@ -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&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(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&&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 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireDependencyIntegrity=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const RESOURCE_TAG=/<(script|link)\b[^>]*>/gi;const CDN_HOSTS=["cdn.","cdnjs.","unpkg.","jsdelivr."];function hasUnprotectedCdnTag(text){for(const match of text.matchAll(RESOURCE_TAG)){const tag=match[0].toLowerCase();const urlAttribute=match[1].toLowerCase()==="script"?"src=":"href=";if(!tag.includes(urlAttribute))continue;if(!CDN_HOSTS.some(host=>tag.includes(host)))continue;if(tag.includes("integrity="))continue;return true}return false}exports.requireDependencyIntegrity=(0,eslint_devkit_1.createRule)({name:"require-dependency-integrity",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-dependency-integrity.md",description:"Require SRI (Subresource Integrity) for CDN resources",cwe:"CWE-494",cvss:8.1},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing SRI",cwe:"CWE-494",description:"External resource loaded without integrity hash - supply chain risk",severity:"HIGH",fix:'Add integrity="sha384-..." and crossorigin="anonymous" attributes',documentationLink:"https://cwe.mitre.org/data/definitions/494.html"})},schema:[]},defaultOptions:[],create(context){function report(node){context.report({node,messageId:"violationDetected"})}return{Literal(node){if(typeof node.value!=="string")return;if(hasUnprotectedCdnTag(node.value))report(node)},TemplateLiteral(node){if(hasUnprotectedCdnTag(context.sourceCode.getText(node)))report(node)}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireDependencyIntegrity=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const RESOURCE_TAG=/<(script|link)\b(?:"[^"]*"|'[^']*'|[^>"'])*>/gi;const TAG_ATTRIBUTE=/(?:^|[\s/])([a-z][a-z0-9-]*)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;const CDN_HOSTS=["cdn.","cdnjs.","unpkg.","jsdelivr."];const SRI_LINK_RELS=new Set(["stylesheet","modulepreload"]);const SRI_PRELOAD_DESTINATIONS=new Set(["style","script"]);function attributesOf(tag){const attributes=new Map;for(const match of tag.matchAll(TAG_ATTRIBUTE)){const raw=match[2];const quoted=raw.startsWith('"')||raw.startsWith("'");attributes.set(match[1].toLowerCase(),quoted?raw.slice(1,-1):raw)}return attributes}function hostOf(url){const match=/^\s*(?:[a-z][a-z0-9+.-]*:)?\/\/([^/?#]*)/i.exec(url);return match?.[1].toLowerCase()}function linkTakesIntegrity(attributes){const rel=(attributes.get("rel")??"").toLowerCase().trim();if(rel==="")return true;const relations=rel.split(/\s+/);if(relations.some(value=>SRI_LINK_RELS.has(value)))return true;if(!relations.includes("preload"))return false;return SRI_PRELOAD_DESTINATIONS.has((attributes.get("as")??"").toLowerCase())}function hasUnprotectedCdnTag(text){for(const match of text.matchAll(RESOURCE_TAG)){const isScript=match[1].toLowerCase()==="script";const attributes=attributesOf(match[0]);const url=attributes.get(isScript?"src":"href");if(url===void 0)continue;const host=hostOf(url);if(host===void 0)continue;if(!CDN_HOSTS.some(fragment=>host.includes(fragment)))continue;if(!isScript&&!linkTakesIntegrity(attributes))continue;if(attributes.has("integrity"))continue;return true}return false}exports.requireDependencyIntegrity=(0,eslint_devkit_1.createRule)({name:"require-dependency-integrity",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-dependency-integrity.md",description:"Require SRI (Subresource Integrity) for CDN resources",cwe:"CWE-494",cvss:8.1},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing SRI",cwe:"CWE-494",description:"External resource loaded without integrity hash - supply chain risk",severity:"HIGH",fix:'Add integrity="sha384-..." and crossorigin="anonymous" attributes',documentationLink:"https://cwe.mitre.org/data/definitions/494.html"})},schema:[]},defaultOptions:[],create(context){function report(node){context.report({node,messageId:"violationDetected"})}const renderTemplate=node=>{let text=node.quasis[0].value.cooked;for(const[index,expression]of node.expressions.entries()){const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,expression);text+=resolved===null?"\0":resolved.value;text+=node.quasis[index+1].value.cooked}return text};return{Literal(node){if(typeof node.value!=="string")return;if(hasUnprotectedCdnTag(node.value))report(node)},TemplateLiteral(node){if(hasUnprotectedCdnTag(renderTemplate(node)))report(node)}}}});
@@ -1 +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");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"})},schema:[]},defaultOptions:[],create(context){return{CallExpression(node){if(!(0,credential_evidence_1.isWebStorageWrite)(node))return;if(!(0,credential_evidence_1.storesACredential)(node)||(0,credential_evidence_1.isEncrypted)(node))return;context.report({node,messageId:"violationDetected"})}}}});
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 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireSecureDeletion=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const SENSITIVE_PROPERTY_FRAGMENTS=["password","passwd","pwd","passphrase","secret","apikey","api_key","token","jwt","bearer","credential","privatekey","private_key","signingkey","signing_key","sessionid","session_id","refreshtoken","refresh_token","ssn","creditcard","credit_card","cardnumber","card_number","cvv"];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:{additionalSensitiveProperties:{type:"array",items:{type:"string"},default:[],description:"Extra property-name fragments to treat as sensitive"}},additionalProperties:false}]},defaultOptions:[{additionalSensitiveProperties:[]}],create(context,[options={}]){const{additionalSensitiveProperties=[]}=options;const fragments=[...SENSITIVE_PROPERTY_FRAGMENTS,...additionalSensitiveProperties.map(f=>f.toLowerCase())];function deletedPropertyName(node){const argument=node.type==="ChainExpression"?node.expression:node;if(argument.type!=="MemberExpression")return void 0;const property=argument.property;if(!argument.computed&&property.type==="Identifier")return property.name;if(argument.computed&&property.type==="Literal"&&typeof property.value==="string"){return property.value}return void 0}return{UnaryExpression(node){if(node.operator!=="delete")return;const property=deletedPropertyName(node.argument);if(!property)return;const normalized=property.toLowerCase().replace(/[^a-z0-9_]/g,"");if(!fragments.some(fragment=>normalized.includes(fragment)))return;context.report({node,messageId:"violationDetected",data:{property}})}}}});
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:{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:[]}],create(context,[options={}]){const{additionalSensitiveProperties=[]}=options;const phrases=[...SENSITIVE_PROPERTY_NAMES,...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 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireStorageEncryption=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const credential_evidence_1=require("../../utils/credential-evidence");exports.requireStorageEncryption=(0,eslint_devkit_1.createRule)({name:"require-storage-encryption",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-storage-encryption.md",description:"Require encryption for persistent storage",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:"Require encryption for persistent storage detected - Storage without encryption",severity:"HIGH",fix:"Review and apply secure practices",documentationLink:"https://cwe.mitre.org/data/definitions/312.html"})},schema:[]},defaultOptions:[],create(context){return{CallExpression(node){if(!(0,credential_evidence_1.isFileWrite)(node))return;if(!(0,credential_evidence_1.storesACredential)(node)||(0,credential_evidence_1.isEncrypted)(node))return;context.report({node,messageId:"violationDetected"})}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireStorageEncryption=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const credential_evidence_1=require("../../utils/credential-evidence");exports.requireStorageEncryption=(0,eslint_devkit_1.createRule)({name:"require-storage-encryption",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/require-storage-encryption.md",description:"Require encryption for persistent storage",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:"Require encryption for persistent storage detected - Storage without encryption",severity:"HIGH",fix:"Review and apply secure practices",documentationLink:"https://cwe.mitre.org/data/definitions/312.html"})},schema:[]},defaultOptions:[],create(context){return{CallExpression(node){if(!(0,credential_evidence_1.isFileWrite)(node))return;if(!(0,credential_evidence_1.storesACredential)(node)||(0,credential_evidence_1.isEncrypted)(node,context.sourceCode))return;context.report({node,messageId:"violationDetected"})}}}});
@@ -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},hasSuggestions:true,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"}),attachErrorListener:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Attach an 'error' listener",description:"Name the stream and handle 'error' before piping",severity:"LOW",fix:"const s = fs.createReadStream(p); s.on('error', next); s.pipe(res);",documentationLink:"https://nodejs.org/api/stream.html#readablepipedestination-options"}),usePipeline:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use pipeline()",description:"pipeline() propagates errors and destroys every stream",severity:"LOW",fix:"await pipeline(fs.createReadStream(p), res) // 'stream/promises'",documentationLink:"https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-callback"})},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&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(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",suggest:[{messageId:"attachErrorListener",fix:()=>null},{messageId:"usePipeline",fix:()=>null}]})}}}}});
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"})}}}}});
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.resolveConstant=resolveConstant;exports.constInitializerOf=constInitializerOf;exports.resolveConstantString=resolveConstantString;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("./provenance");function literalValue(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal){return typeof node.value==="string"||typeof node.value==="number"?node.value:null}if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral&&node.expressions.length===0){return node.quasis[0].value.cooked}return null}function resolveConstant(sourceCode,node){const direct=literalValue(node);if(direct!==null)return{value:direct,source:node,viaAlias:false};if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const init=(0,provenance_1.constLiteralOf)(sourceCode,node);if(init===void 0)return null;const value=literalValue(init);return value===null?null:{value,source:init,viaAlias:true}}function constInitializerOf(sourceCode,node){const variable=(0,provenance_1.findVariable)(sourceCode,node);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type!=="Variable"||def.parent.kind!=="const")return null;if(def.node.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;return def.node.init??null}function resolveConstantString(sourceCode,node){const resolved=resolveConstant(sourceCode,node);return resolved!==null&&typeof resolved.value==="string"?resolved:null}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.storesACredential=storesACredential;exports.isEncrypted=isEncrypted;exports.isWebStorageWrite=isWebStorageWrite;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"];function namesACredential(text){const normalized=text.toLowerCase();return CREDENTIAL_WORDS.some(word=>normalized.includes(word))}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:return node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?node.property.name:"";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 calleeEncrypts(name){return name.split(/(?=[A-Z])|_/).some(token=>token.toLowerCase().startsWith("encrypt"))}function isEncrypted(node){const value=node.arguments[1];if(value?.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=value.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return calleeEncrypts(callee.name)}return callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&calleeEncrypts(callee.property.name)}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 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"];function namesACredential(text){const normalized=text.toLowerCase();return CREDENTIAL_WORDS.some(word=>normalized.includes(word))}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 +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??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;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.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:{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)}