eslint-plugin-node-security 4.13.0 → 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 (46) hide show
  1. package/README.md +40 -1
  2. package/package.json +2 -2
  3. package/src/index.js +1 -1
  4. package/src/rules/detect-child-process/index.js +1 -1
  5. package/src/rules/detect-eval-with-expression/index.js +1 -1
  6. package/src/rules/detect-non-literal-fs-filename/index.js +1 -1
  7. package/src/rules/detect-suspicious-dependencies/index.js +1 -1
  8. package/src/rules/lock-file/index.js +1 -1
  9. package/src/rules/no-buffer-overread/index.js +1 -1
  10. package/src/rules/no-cryptojs/index.js +1 -1
  11. package/src/rules/no-cryptojs-weak-random/index.js +1 -1
  12. package/src/rules/no-data-in-temp-storage/index.js +1 -1
  13. package/src/rules/no-deprecated-buffer/index.js +1 -1
  14. package/src/rules/no-deprecated-cipher-method/index.js +1 -1
  15. package/src/rules/no-dynamic-command-string/index.js +1 -1
  16. package/src/rules/no-dynamic-dependency-loading/index.js +1 -1
  17. package/src/rules/no-dynamic-require/index.js +1 -1
  18. package/src/rules/no-ecb-mode/index.js +1 -1
  19. package/src/rules/no-env-injection/index.js +1 -1
  20. package/src/rules/no-insecure-http-parser/index.js +1 -1
  21. package/src/rules/no-insecure-key-derivation/index.js +1 -1
  22. package/src/rules/no-insecure-rsa-padding/index.js +1 -1
  23. package/src/rules/no-math-random-crypto/index.js +1 -1
  24. package/src/rules/no-self-signed-certs/index.js +1 -1
  25. package/src/rules/no-sha1-hash/index.js +1 -1
  26. package/src/rules/no-shell-injection/index.js +1 -1
  27. package/src/rules/no-ssrf/index.js +1 -1
  28. package/src/rules/no-static-iv/index.js +1 -1
  29. package/src/rules/no-timing-unsafe-compare/index.js +1 -1
  30. package/src/rules/no-toctou-vulnerability/index.js +1 -1
  31. package/src/rules/no-unbounded-decompression/index.js +1 -1
  32. package/src/rules/no-unsafe-buffer-alloc/index.js +1 -1
  33. package/src/rules/no-unsafe-dynamic-require/index.js +1 -1
  34. package/src/rules/no-weak-cipher-algorithm/index.js +1 -1
  35. package/src/rules/no-weak-hash-algorithm/index.js +1 -1
  36. package/src/rules/no-zip-slip/index.js +1 -1
  37. package/src/rules/prefer-native-crypto/index.js +1 -1
  38. package/src/rules/require-aead-tag-verification/index.js +1 -1
  39. package/src/rules/require-dependency-integrity/index.js +1 -1
  40. package/src/rules/require-secure-credential-storage/index.js +1 -1
  41. package/src/rules/require-secure-deletion/index.js +1 -1
  42. package/src/rules/require-storage-encryption/index.js +1 -1
  43. package/src/rules/require-stream-error-handler/index.js +1 -1
  44. package/src/utils/const-value.js +1 -0
  45. package/src/utils/credential-evidence.js +1 -0
  46. package/src/utils/provenance.js +1 -1
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noStaticIv=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const STATIC_IV_PATTERNS=[/^[0-9a-f]+$/i,/^[A-Za-z0-9+/]+=*$/];exports.noStaticIv=(0,eslint_devkit_1.createRule)({name:"no-static-iv",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-static-iv.md",description:"Disallow static or hardcoded initialization vectors (IVs)",cwe:"CWE-329",cvss:7.5},hasSuggestions:false,messages:{staticIv:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Static IV detected",cwe:"CWE-329",description:"Hardcoded IV detected. Using static IVs makes encryption deterministic, allowing attackers to detect repeated plaintexts.",severity:"HIGH",fix:"Generate IV dynamically using crypto.randomBytes(16)",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#initialization-vectors"}),useRandomBytes:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use randomBytes",description:"Generate IV dynamically for each encryption operation",severity:"LOW",fix:"const iv = crypto.randomBytes(16);",documentationLink:"https://nodejs.org/api/crypto.html#cryptorandombytessize-callback"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow static IVs in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);const randomIvVariables=new Set;function checkVariableDeclarator(node){if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const init=node.init;if(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.callee.property.name==="randomBytes"){randomIvVariables.add(node.id.name)}if(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.callee.name==="randomBytes"){randomIvVariables.add(node.id.name)}}}function checkCallExpression(node){if(isTestFile)return;const isCipherivCall=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==="createCipheriv"||node.callee.property.name==="createDecipheriv")||node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.callee.name==="createCipheriv"||node.callee.name==="createDecipheriv");if(isCipherivCall&&node.arguments.length>=3){const ivArg=node.arguments[2];checkIvArgument(ivArg)}}function checkIvArgument(ivArg){if(ivArg.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof ivArg.value==="string"){const value=ivArg.value;if(STATIC_IV_PATTERNS.some(p=>p.test(value))||value.length>=8){reportStaticIv(ivArg)}}if(ivArg.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&ivArg.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&ivArg.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&ivArg.callee.object.name==="Buffer"&&ivArg.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(ivArg.callee.property.name==="from"||ivArg.callee.property.name==="alloc")){const firstArg=ivArg.arguments[0];if(firstArg?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof firstArg.value==="string"){reportStaticIv(ivArg)}if(firstArg?.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression){const allLiterals=firstArg.elements.every(el=>el?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof el.value==="number");if(allLiterals){reportStaticIv(ivArg)}}}if(ivArg.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){}}function reportStaticIv(node){context.report({node,messageId:"staticIv"})}return{VariableDeclarator:checkVariableDeclarator,CallExpression:checkCallExpression}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noStaticIv=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 STATIC_IV_PATTERNS=[/^[0-9a-f]+$/i,/^[A-Za-z0-9+/]+=*$/];const CIPHERIV_FACTORIES=["createCipheriv","createDecipheriv"];const TYPED_ARRAY_CONSTRUCTORS=new Set(["Uint8Array","Uint8ClampedArray","Int8Array"]);const RANDOM_FILL_FUNCTIONS=new Set(["randomFill","randomFillSync","getRandomValues"]);function calleeMemberName(callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;if(callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&!callee.computed){return callee.property.name}return callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof callee.property.value==="string"?callee.property.value:null}exports.noStaticIv=(0,eslint_devkit_1.createRule)({name:"no-static-iv",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-static-iv.md",description:"Disallow static or hardcoded initialization vectors (IVs)",cwe:"CWE-329",cvss:7.5},hasSuggestions:false,messages:{staticIv:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Static IV detected",cwe:"CWE-329",description:"Hardcoded IV detected. Using static IVs makes encryption deterministic, allowing attackers to detect repeated plaintexts.",severity:"HIGH",fix:"Generate IV dynamically using crypto.randomBytes(16)",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#initialization-vectors"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow static IVs in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function isCipherivCall(node){const callee=node.callee;const spelled=calleeMemberName(callee)??(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.name:null);if(spelled!==null&&CIPHERIV_FACTORIES.includes(spelled))return true;const scope=context.sourceCode.getScope(node);return CIPHERIV_FACTORIES.some(fn=>(0,eslint_devkit_1.isModuleBinding)(callee,scope,"crypto",[fn]))}function checkCallExpression(node){if(isTestFile)return;if(isCipherivCall(node)&&node.arguments.length>=3){const ivArg=node.arguments[2];checkIvArgument(ivArg)}}function isStaticByteArray(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression&&node.elements.every(el=>el?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof el.value==="number")}function isRandomlyFilled(variable){return variable.references.some(ref=>{const parent=ref.identifier.parent;if(parent?.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;if(!parent.arguments.includes(ref.identifier))return false;const callee=parent.callee;const name=calleeMemberName(callee)??(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.name:null);return name!==null&&RANDOM_FILL_FUNCTIONS.has(name)})}function isStaticIvValue(value,binding){const constant=(0,const_value_1.resolveConstantString)(context.sourceCode,value);if(constant!==null){const text=constant.value;return STATIC_IV_PATTERNS.some(p=>p.test(text))||text.length>=8}if(value.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression){return value.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&TYPED_ARRAY_CONSTRUCTORS.has(value.callee.name)&&isStaticBufferSource(value.arguments[0],binding)}if(value.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&value.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&value.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&value.callee.object.name==="Buffer"&&(calleeMemberName(value.callee)==="from"||calleeMemberName(value.callee)==="alloc")){return isStaticBufferSource(value.arguments[0],binding)}return false}function isStaticBufferSource(source,binding){if(source===void 0)return false;if((0,const_value_1.resolveConstantString)(context.sourceCode,source)!==null)return true;if(isStaticByteArray(source))return true;const size=(0,const_value_1.resolveConstant)(context.sourceCode,source);if(size===null||typeof size.value!=="number")return false;return binding===null||!isRandomlyFilled(binding)}function ivCandidates(identifier,variable){const constInit=(0,const_value_1.constInitializerOf)(context.sourceCode,identifier);if(constInit!==null)return[constInit];if(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;const candidates=def.node.init?[def.node.init]:[];for(const ref of variable.references){if(!ref.isWrite())continue;if(!ref.writeExpr)return null;if(ref.writeExpr!==def.node.init)candidates.push(ref.writeExpr)}return candidates.length>0?candidates:null}function checkIvArgument(ivArg){const unwrapped=(0,eslint_devkit_1.unwrapTypeSyntax)(ivArg);if(unwrapped.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const binding=(0,provenance_1.findVariable)(context.sourceCode,unwrapped);if(binding===null)return;const candidates=ivCandidates(unwrapped,binding);if(candidates===null)return;if(candidates.every(c=>isStaticIvValue((0,eslint_devkit_1.unwrapTypeSyntax)(c),binding))){reportStaticIv(unwrapped)}return}if(isStaticIvValue(unwrapped,null))reportStaticIv(unwrapped)}function reportStaticIv(node){context.report({node,messageId:"staticIv"})}return{CallExpression:checkCallExpression}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimingUnsafeCompare=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const DEFAULT_SECRET_PATTERNS=["token","secret","password","hash","signature","mac","hmac","digest","apiKey","api_key","api-key","auth","credential","bearer","jwt","csrf","nonce","ssn","social_security","social-security","pii","private_key","private-key","privateKey","access_token","access-token","accessToken","refresh_token","refresh-token","refreshToken","session_id","session-id","sessionId","auth_token","auth-token","authToken","encryption_key","encryption-key","encryptionKey"];function isSourceConstant(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.name==="undefined")return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral)return node.expressions.length===0;return false}const BOOLEAN_PREDICATE_NAME=/^(?:is|has|should|can|did|was|will|does)[A-Z]/;const NAMED_CONSTANT=/^[A-Z][A-Z0-9_]*$/;const DEFAULT_UNTRUSTED_SOURCES=["req","request","ctx","event"];const NAMESPACE_NAME=/^(?:[A-Z][A-Z0-9_]*|[A-Z][a-zA-Z0-9]*)$/;function isNamedConstant(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.computed)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return NAMESPACE_NAME.test(node.object.name)&&NAMED_CONSTANT.test(node.property.name)}function memberRoot(node){let current=node;for(;;){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object;continue}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){current=current.callee;continue}break}return current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?current:null}function isSelfComparison(left,right){const pair=(bare,derived)=>{if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(derived.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return memberRoot(derived)?.name===bare.name};return pair(left,right)||pair(right,left)}const NON_SECRET_MEMBERS=new Map([["hash",new Set(["location"])]]);function isNonSecretMember(node){if(node.computed||node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const receivers=NON_SECRET_MEMBERS.get(node.property.name.toLowerCase());if(!receivers)return false;const owner=node.object;const ownerName=owner.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.name:owner.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!owner.computed&&owner.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.property.name:"";return receivers.has(ownerName.toLowerCase())}exports.noTimingUnsafeCompare=(0,eslint_devkit_1.createRule)({name:"no-timing-unsafe-compare",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md",description:"Disallow timing-unsafe comparison of secrets",cwe:"CWE-208",cvss:7.5},hasSuggestions:true,messages:{timingUnsafeCompare:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timing-unsafe comparison",cwe:"CWE-208",description:"Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.",severity:"HIGH",fix:"Use crypto.timingSafeEqual() for constant-time comparison",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"}),useTimingSafeEqual:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use timingSafeEqual",description:"Use constant-time comparison to prevent timing attacks",severity:"LOW",fix:"crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"})},schema:[{type:"object",properties:{secretPatterns:{type:"array",items:{type:"string"},default:DEFAULT_SECRET_PATTERNS,description:"Variable name patterns that indicate secrets"},untrustedSources:{type:"array",items:{type:"string"},default:DEFAULT_UNTRUSTED_SOURCES,description:"Identifier roots treated as attacker-controlled (default: req, request, ctx, event)"},reportUnverifiedComparisons:{type:"boolean",default:false,description:"Report on a secret-looking name alone, without an attacker-controlled operand. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{secretPatterns:DEFAULT_SECRET_PATTERNS}],create(context,[options={}]){const{secretPatterns=DEFAULT_SECRET_PATTERNS,untrustedSources=DEFAULT_UNTRUSTED_SOURCES,reportUnverifiedComparisons=false}=options;const sourceCode=context.sourceCode;const readsUntrusted=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const patterns=secretPatterns.map(p=>new RegExp(p,"i"));function nameLooksSecret(name){if(BOOLEAN_PREDICATE_NAME.test(name))return false;return patterns.some(p=>p.test(name))}function isSecretIdentifier(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return nameLooksSecret(node.name)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(isNonSecretMember(node))return false;const prop=node.property;if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return nameLooksSecret(prop.name)}}return false}function isResolvedConstant(node){if(isSourceConstant(node))return true;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,provenance_1.constLiteralOf)(sourceCode,node)!==void 0}function checkBinaryExpression(node){if(node.operator!=="==="&&node.operator!=="=="&&node.operator!=="!=="&&node.operator!=="!="){return}if(isResolvedConstant(node.left)||isResolvedConstant(node.right)){return}if(isNamedConstant(node.left)||isNamedConstant(node.right)){return}if(isSelfComparison(node.left,node.right)){return}const leftIsSecret=isSecretIdentifier(node.left);const rightIsSecret=isSecretIdentifier(node.right);if(leftIsSecret||rightIsSecret){if(!reportUnverifiedComparisons){const leftUntrusted=readsUntrusted(node.left);const rightUntrusted=readsUntrusted(node.right);if(leftUntrusted===rightUntrusted)return}context.report({node,messageId:"timingUnsafeCompare",suggest:[{messageId:"useTimingSafeEqual",fix:()=>null}]})}}return{BinaryExpression:checkBinaryExpression}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimingUnsafeCompare=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const names_1=require("../../utils/names");const DEFAULT_SECRET_PATTERNS=["token","secret","password","hash","signature","mac","hmac","digest","apiKey","api_key","api-key","auth","credential","bearer","jwt","csrf","nonce","ssn","social_security","social-security","pii","private_key","private-key","privateKey","access_token","access-token","accessToken","refresh_token","refresh-token","refreshToken","session_id","session-id","sessionId","auth_token","auth-token","authToken","encryption_key","encryption-key","encryptionKey"];function isSourceConstant(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.name==="undefined")return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral)return node.expressions.length===0;return false}const BOOLEAN_PREDICATE_NAME=/^(?:is|has|should|can|did|was|will|does)[A-Z]/;const NAMED_CONSTANT=/^[A-Z][A-Z0-9_]*$/;const DEFAULT_UNTRUSTED_SOURCES=["req","request","ctx","event"];const NAMESPACE_NAME=/^(?:[A-Z][A-Z0-9_]*|[A-Z][a-zA-Z0-9]*)$/;function isNamedConstant(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.computed)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return NAMESPACE_NAME.test(node.object.name)&&NAMED_CONSTANT.test(node.property.name)}function memberRoot(node){let current=node;for(;;){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object;continue}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){current=current.callee;continue}break}return current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?current:null}function isSelfComparison(left,right){const pair=(bare,derived)=>{if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(derived.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return memberRoot(derived)?.name===bare.name};return pair(left,right)||pair(right,left)}const NON_SECRET_MEMBERS=new Map([["hash",new Set(["location"])]]);const DEFAULT_NON_SECRET_WORDS=["author","authors","authored","authoring","authorship","hashtag","hashtags"];const DEFAULT_NON_SECRET_TAILS=["count","counts","limit","limits","usage","total","size","length","price","cost","quota","address","addresses","index","rank","percent"];const CRYPTO_DERIVATIONS=new Set(["createHmac","createHash","createSign","pbkdf2Sync","scryptSync","hkdfSync","digest","sign","hmac"]);const RECEIVER_COMPARE_METHODS=new Set(["equals","startsWith","endsWith","localeCompare"]);const BINARY_EQUALITY_FUNCTIONS=new Set(["isEqual","isEqualWith","deepEqual","fastDeepEqual","shallowEqual"]);const SERVER_STATE_REQUEST_PROPERTIES=new Set(["session","user","locals","app","state"]);function unwrapChain(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.ChainExpression?node.expression:node}function memberPropertyName(node){const property=node.property;if(!node.computed){return property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.name:null}return property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof property.value==="string"?property.value:null}function isNonSecretMember(node){const propertyName=memberPropertyName(node);if(propertyName===null)return false;const receivers=NON_SECRET_MEMBERS.get(propertyName.toLowerCase());if(!receivers)return false;const owner=node.object;const ownerName=owner.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.name:owner.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!owner.computed&&owner.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?owner.property.name:"";return receivers.has(ownerName.toLowerCase())}exports.noTimingUnsafeCompare=(0,eslint_devkit_1.createRule)({name:"no-timing-unsafe-compare",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md",description:"Disallow timing-unsafe comparison of secrets",cwe:"CWE-208",cvss:5.9},messages:{timingUnsafeCompare:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timing-unsafe comparison",cwe:"CWE-208",description:"Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.",severity:"HIGH",fix:"Use crypto.timingSafeEqual() for constant-time comparison",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"})},schema:[{type:"object",properties:{secretPatterns:{type:"array",items:{type:"string"},default:DEFAULT_SECRET_PATTERNS,description:"Variable name patterns that indicate secrets"},untrustedSources:{type:"array",items:{type:"string"},default:DEFAULT_UNTRUSTED_SOURCES,description:"Identifier roots treated as attacker-controlled (default: req, request, ctx, event)"},reportUnverifiedComparisons:{type:"boolean",default:false,description:"Report on a secret-looking name alone, without an attacker-controlled operand. Restores the pre-inversion behaviour."},nonSecretWords:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_WORDS],description:"Whole words that mean a secretPatterns match was a collision (default: author, authors, authored, authoring, authorship, hashtag, hashtags). Replaces the list."},nonSecretTails:{type:"array",items:{type:"string"},default:[...DEFAULT_NON_SECRET_TAILS],description:"Trailing words that make the value a measurement or location rather than a credential (default: count, limit, usage, total, size, length, price, cost, quota, address, index, rank, percent). Replaces the list."}},additionalProperties:false}]},defaultOptions:[{secretPatterns:DEFAULT_SECRET_PATTERNS,nonSecretWords:[...DEFAULT_NON_SECRET_WORDS],nonSecretTails:[...DEFAULT_NON_SECRET_TAILS]}],create(context,[options={}]){const{secretPatterns=DEFAULT_SECRET_PATTERNS,untrustedSources=DEFAULT_UNTRUSTED_SOURCES,reportUnverifiedComparisons=false,nonSecretWords=[...DEFAULT_NON_SECRET_WORDS],nonSecretTails=[...DEFAULT_NON_SECRET_TAILS]}=options;const nonSecretWordSet=new Set(nonSecretWords.map(word=>word.toLowerCase()));const nonSecretTailSet=new Set(nonSecretTails.map(word=>word.toLowerCase()));const sourceCode=context.sourceCode;const readsUntrusted=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const patterns=(0,eslint_devkit_1.compileUserPatterns)(secretPatterns,"i");function nameLooksSecret(name){if(BOOLEAN_PREDICATE_NAME.test(name))return false;const words=(0,names_1.identifierWords)(name);if(words.some(word=>nonSecretWordSet.has(word)))return false;if(words.length>0&&nonSecretTailSet.has(words[words.length-1])){return false}return patterns.some(p=>p.test(name))}function isCryptoDerivation(node){const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isCryptoDerivation(bare);if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return CRYPTO_DERIVATIONS.has(callee.name)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return false;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(CRYPTO_DERIVATIONS.has(callee.property.name))return true;return isCryptoDerivation(callee.object)}function isCryptoSecret(node){if(isCryptoDerivation(node))return true;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,provenance_1.bindingInit)(sourceCode,bare);return init!==void 0&&containsCryptoDerivation(init)}function containsCryptoDerivation(node){if(isCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.expressions.some(expression=>containsCryptoDerivation(expression))}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return containsCryptoDerivation(node.left)||containsCryptoDerivation(node.right)}return false}function isServerDerived(node,depth=0){if(depth>4)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return isServerDerived(bare,depth+1);if(node.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression)return true;if(containsCryptoDerivation(node))return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const propertyName=memberPropertyName(node);if(propertyName!==null&&SERVER_STATE_REQUEST_PROPERTIES.has(propertyName)){return true}return isServerDerived(node.object,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,provenance_1.bindingInit)(sourceCode,node);return init!==void 0&&isServerDerived(init,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=unwrapChain(node.callee);if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;return!readsUntrusted(callee.object)||isServerDerived(callee.object,depth+1)}return false}function isSecretIdentifier(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return nameLooksSecret(node.name)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){if(isNonSecretMember(node))return false;const propertyName=memberPropertyName(node);if(propertyName!==null){return nameLooksSecret(propertyName)}}return false}function isResolvedConstant(node){if(isSourceConstant(node))return true;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,provenance_1.constLiteralOf)(sourceCode,node)!==void 0}function resolveLocalFunction(callee){const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type==="FunctionName")return def.node;if(def.type!=="Variable")return null;const init=def.node.init;if(!init)return null;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(init);if(bare.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression||bare.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression){return bare}return null}function comparesLengths(node){const isLength=operand=>operand.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&memberPropertyName(operand)==="length";return isLength(node.left)||isLength(node.right)}function equalityWrapperParams(callee){const fn=resolveLocalFunction(callee);if(!fn||fn.params.length<2)return null;const paramIndex=new Map;fn.params.forEach((param,index)=>{if(param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)paramIndex.set(param.name,index)});let found=null;const visit=node=>{if(found||!node||typeof node.type!=="string")return;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){const isEquality=node.operator==="==="||node.operator==="!=="||node.operator==="=="||node.operator==="!=";if(isEquality&&!comparesLengths(node)){const leftIndex=paramIndex.get(memberRoot(node.left)?.name??"");const rightIndex=paramIndex.get(memberRoot(node.right)?.name??"");if(leftIndex!==void 0&&rightIndex!==void 0&&leftIndex!==rightIndex){found=[leftIndex,rightIndex];return}}}for(const[key,value]of Object.entries(node)){if(key==="parent")continue;if(Array.isArray(value)){for(const entry of value)visit(entry)}else if(value&&typeof value==="object"&&"type"in value){visit(value)}}};visit(fn.body);return found}function comparisonOperands(node){const args=node.arguments;if(args.some(argument=>argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)){return null}const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(BINARY_EQUALITY_FUNCTIONS.has(callee.name)&&args.length===2){return[args[0],args[1]]}const wrapped=equalityWrapperParams(callee);if(!wrapped)return null;const[leftIndex,rightIndex]=wrapped;const leftArg=args[leftIndex];const rightArg=args[rightIndex];if(!leftArg||!rightArg)return null;return[leftArg,rightArg]}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed)return null;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const method=callee.property.name;if(method==="compare"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"&&args.length===2){return[args[0],args[1]]}if(BINARY_EQUALITY_FUNCTIONS.has(method)&&args.length===2){return[args[0],args[1]]}if(RECEIVER_COMPARE_METHODS.has(method)&&args.length===1){return[callee.object,args[0]]}return null}function checkComparison(node,rawLeft,rawRight){const left=unwrapChain(rawLeft);const right=unwrapChain(rawRight);if(isResolvedConstant(left)||isResolvedConstant(right)){return}if(isNamedConstant(left)||isNamedConstant(right)){return}if(isSelfComparison(left,right)){return}const leftIsSecret=isSecretIdentifier(left)||isCryptoSecret(left);const rightIsSecret=isSecretIdentifier(right)||isCryptoSecret(right);if(leftIsSecret||rightIsSecret){if(!reportUnverifiedComparisons){const leftUntrusted=readsUntrusted(left);const rightUntrusted=readsUntrusted(right);if(leftUntrusted===rightUntrusted){if(!leftUntrusted)return;if(!isServerDerived(left)&&!isServerDerived(right))return}}context.report({node,messageId:"timingUnsafeCompare"})}}function checkBinaryExpression(node){if(node.operator!=="==="&&node.operator!=="=="&&node.operator!=="!=="&&node.operator!=="!="){return}checkComparison(node,node.left,node.right)}function checkCallExpression(node){const operands=comparisonOperands(node);if(!operands)return;checkComparison(node,operands[0],operands[1])}return{BinaryExpression:checkBinaryExpression,CallExpression:checkCallExpression}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noToctouVulnerability=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const PER_USER_ROOT_FUNCTIONS=new Set(["homedir","userInfo"]);const PER_USER_ENV_VARS=new Set(["HOME","USERPROFILE","LOCALAPPDATA","APPDATA","XDG_CACHE_HOME","XDG_CONFIG_HOME","XDG_DATA_HOME","XDG_STATE_HOME"]);exports.noToctouVulnerability=(0,eslint_devkit_2.createRule)({name:"no-toctou-vulnerability",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-toctou-vulnerability.md",description:"Detects Time-of-Check-Time-of-Use vulnerabilities",cwe:"CWE-367",cvss:7.5},hasSuggestions:true,messages:{toctouVulnerability:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"TOCTOU vulnerability",cwe:"CWE-367",description:"Time-of-check Time-of-use race condition detected",severity:"HIGH",fix:"Use atomic operations or fs.promises for file operations",documentationLink:"https://cwe.mitre.org/data/definitions/367.html"}),useAtomicOperations:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Atomic Operations",description:"Use atomic file operations",severity:"LOW",fix:"fs.promises.access() then fs.promises.readFile()",documentationLink:"https://nodejs.org/api/fs.html#fspromisesaccesspath-mode"}),useFsPromises:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use fs.promises",description:"Use fs.promises API",severity:"LOW",fix:"await fs.promises.readFile() instead of sync operations",documentationLink:"https://nodejs.org/api/fs.html#promises-api"}),addProperLocking:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add File Locking",description:"Add proper locking mechanism",severity:"LOW",fix:"Use proper-lockfile or similar for concurrent access",documentationLink:"https://github.com/moxystudio/node-proper-lockfile"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true},fsMethods:{type:"array",items:{type:"string"},default:["fs.existsSync","fs.statSync","fs.accessSync"],description:"Filesystem check calls that create a time-of-check window"}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,fsMethods:["fs.existsSync","fs.statSync","fs.accessSync"]}],create(context,[options={}]){const{ignoreInTests=true}=options||{};const filename=context.filename;const isTestFile=ignoreInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function returnedExpression(callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type!=="FunctionName")return null;const body=def.node.body;if(body?.type!==eslint_devkit_1.AST_NODE_TYPES.BlockStatement)return null;for(const statement of body.body){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement&&statement.argument){return statement.argument}}return null}function reachesPerUserRoot(node,depth=0){if(depth>8)return false;const next=child=>reachesPerUserRoot(child,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const variable=(0,provenance_1.findVariable)(sourceCode,node);const def=variable?.defs.length===1?variable.defs[0]:void 0;if(def?.type!=="Variable"||!def.node.init)return false;return next(def.node.init)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{if(!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&PER_USER_ENV_VARS.has(node.property.name)){return true}return next(node.object)}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{const callee=node.callee;const name=callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.name:callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property.name:null;if(name!==null&&PER_USER_ROOT_FUNCTIONS.has(name))return true;if(node.arguments.some(argument=>next(argument)))return true;const returned=returnedExpression(callee);return returned!==null&&next(returned)}case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:return next(node.consequent)||next(node.alternate);case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:return next(node.left)||next(node.right);case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(expression=>next(expression));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return next(node.left)||next(node.right);default:return false}}const EXISTENCE_CHECKS=new Set(["existsSync","exists"]);const READ_ONLY_USES=new Set(["readFileSync","readFile"]);function isSecurityRelevantWindow(checkMethod,useMethod){if(!EXISTENCE_CHECKS.has(checkMethod))return true;return!READ_ONLY_USES.has(useMethod)}function checkCallExpression(node){let useMethodName="";if(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"){const objectName=node.callee.object.type==="Identifier"?node.callee.object.name:"";if(objectName==="fs"||objectName==="fsPromises"){useMethodName=node.callee.property.name}}else if(node.callee.type==="Identifier"){useMethodName=node.callee.name}const riskyUseMethods=["readFileSync","writeFileSync","readFile","writeFile","openSync","open","unlinkSync","unlink"];if(!riskyUseMethods.includes(useMethodName)){return}const useArg=node.arguments[0];if(!useArg)return;if(reachesPerUserRoot(useArg))return;let current=node.parent;while(current){if(current.type==="IfStatement"){let condition=current.test;if(condition.type==="UnaryExpression"&&condition.operator==="!"){condition=condition.argument}if(condition.type==="CallExpression"){let checkMethodName="";if(condition.callee.type==="MemberExpression"&&condition.callee.property.type==="Identifier"){checkMethodName=condition.callee.property.name}else if(condition.callee.type==="Identifier"){checkMethodName=condition.callee.name}const checkMethods=["existsSync","statSync","accessSync","exists","stat","access"];if(checkMethods.includes(checkMethodName)&&isSecurityRelevantWindow(checkMethodName,useMethodName)){const checkArg=condition.arguments[0];if(checkArg){if(checkArg.type==="Identifier"&&useArg.type==="Identifier"&&checkArg.name===useArg.name){reportToctou(node);return}const checkArgText=sourceCode.getText(checkArg).replace(/\s/g,"");const useArgText=sourceCode.getText(useArg).replace(/\s/g,"");if(checkArgText===useArgText){reportToctou(node);return}}}if(condition.callee.type==="MemberExpression"&&condition.callee.property.type==="Identifier"&&["isFile","isDirectory"].includes(condition.callee.property.name)&&condition.callee.object.type==="Identifier"){const statsVarName=condition.callee.object.name;let currentScope=sourceCode.getScope(condition);let variable=null;while(currentScope){variable=currentScope.variables.find(v=>v.name===statsVarName)||null;if(variable)break;currentScope=currentScope.upper}if(variable&&variable.defs.length>0){const def=variable.defs[0];if(def.type==="Variable"&&def.node.init&&def.node.init.type==="CallExpression"){const init=def.node.init;if(init.callee.type==="MemberExpression"&&init.callee.property.type==="Identifier"&&["statSync","lstatSync","stat","lstat"].includes(init.callee.property.name)){const statArg=init.arguments[0];if(statArg){const checkArgText=sourceCode.getText(statArg).replace(/\s/g,"");const useArgText=sourceCode.getText(useArg).replace(/\s/g,"");if(checkArgText===useArgText){reportToctou(node);return}}}}}}}}current=current.parent}}function reportToctou(node){context.report({node,messageId:"toctouVulnerability",suggest:[{messageId:"useAtomicOperations",fix:()=>null},{messageId:"useFsPromises",fix:()=>null},{messageId:"addProperLocking",fix:()=>null}]})}return{CallExpression:checkCallExpression}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noToctouVulnerability=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const provenance_1=require("../../utils/provenance");const PER_USER_ROOT_FUNCTIONS=new Set(["homedir","userInfo"]);const PER_USER_ENV_VARS=new Set(["HOME","USERPROFILE","LOCALAPPDATA","APPDATA","XDG_CACHE_HOME","XDG_CONFIG_HOME","XDG_DATA_HOME","XDG_STATE_HOME"]);const DEFAULT_FS_METHODS=["fs.existsSync","fs.statSync","fs.accessSync","fs.exists","fs.stat","fs.access"];const RISKY_USE_METHODS=new Set(["readFileSync","readFile","openSync","open","createReadStream","writeFileSync","writeFile","appendFileSync","appendFile","createWriteStream","unlinkSync","unlink","rmSync","rm","rmdirSync","rmdir","mkdirSync","mkdir","renameSync","rename","copyFileSync","copyFile","truncateSync","truncate","chmodSync","chmod","chownSync","chown","symlinkSync","symlink","linkSync","link"]);function memberName(callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;if(callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&!callee.computed){return callee.property.name}return callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof callee.property.value==="string"?callee.property.value:null}exports.noToctouVulnerability=(0,eslint_devkit_2.createRule)({name:"no-toctou-vulnerability",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-toctou-vulnerability.md",description:"Detects Time-of-Check-Time-of-Use vulnerabilities",cwe:"CWE-367",cvss:7},messages:{toctouVulnerability:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"TOCTOU vulnerability",cwe:"CWE-367",description:"Time-of-check Time-of-use race condition detected",severity:"HIGH",fix:"Act on the result and handle the failure \u2014 open/unlink and catch ENOENT \u2014 instead of checking first. Not a finding if the path is inside a directory only this user can write",documentationLink:"https://cwe.mitre.org/data/definitions/367.html"})},schema:[{type:"object",properties:{ignoreInTests:{type:"boolean",default:true},fsMethods:{type:"array",items:{type:"string"},default:DEFAULT_FS_METHODS,description:"Filesystem check calls that create a time-of-check window. Replaces the built-in list. Only the final dotted segment is compared, so `fs.existsSync` and `existsSync` are the same entry."}},additionalProperties:false}]},defaultOptions:[{ignoreInTests:true,fsMethods:DEFAULT_FS_METHODS}],create(context,[options={}]){const{ignoreInTests=true,fsMethods=DEFAULT_FS_METHODS}=options||{};const checkMethods=new Set(fsMethods.map(entry=>entry.split(".").at(-1)));const filename=context.filename;const isTestFile=ignoreInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);if(isTestFile){return{}}const sourceCode=context.sourceCode;function returnedExpression(callee){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const variable=(0,provenance_1.findVariable)(sourceCode,callee);if(!variable||variable.defs.length!==1)return null;const def=variable.defs[0];if(def.type!=="FunctionName")return null;const body=def.node.body;if(body?.type!==eslint_devkit_1.AST_NODE_TYPES.BlockStatement)return null;for(const statement of body.body){if(statement.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement&&statement.argument){return statement.argument}}return null}function reachesPerUserRoot(node,depth=0){if(depth>8)return false;const next=child=>reachesPerUserRoot(child,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const variable=(0,provenance_1.findVariable)(sourceCode,node);const def=variable?.defs.length===1?variable.defs[0]:void 0;if(def?.type!=="Variable"||!def.node.init)return false;return next(def.node.init)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{if(!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&PER_USER_ENV_VARS.has(node.property.name)){return true}return next(node.object)}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{const callee=node.callee;const name=callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.name:callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property.name:null;if(name!==null&&PER_USER_ROOT_FUNCTIONS.has(name))return true;if(node.arguments.some(argument=>next(argument)))return true;const returned=returnedExpression(callee);return returned!==null&&next(returned)}case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:return next(node.consequent)||next(node.alternate);case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:return next(node.left)||next(node.right);case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(expression=>next(expression));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return next(node.left)||next(node.right);default:return false}}const EXISTENCE_CHECKS=new Set(["existsSync","exists"]);const READ_ONLY_USES=new Set(["readFileSync","readFile"]);function isSecurityRelevantWindow(checkMethod,useMethod){if(!EXISTENCE_CHECKS.has(checkMethod))return true;return!READ_ONLY_USES.has(useMethod)}function resolvesToNonFsLocal(callee,at){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const scope=sourceCode.getScope(at);if((0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs")||(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs/promises")){return false}const variable=(0,provenance_1.findVariable)(sourceCode,callee);return!!variable&&variable.defs.length>0}function usedMethodName(node){const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return resolvesToNonFsLocal(callee,node)?"":callee.name}const name=memberName(callee);if(name===null)return"";const objectName=callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.object.name:"";if(objectName==="fs"||objectName==="fsPromises")return name;const scope=sourceCode.getScope(node);return(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs")||(0,eslint_devkit_1.isModuleBinding)(callee,scope,"fs/promises")?name:""}function collectConditionCalls(node,out,depth=0){if(depth>8)return;const inner=(0,eslint_devkit_1.unwrapTypeSyntax)(node);switch(inner.type){case eslint_devkit_1.AST_NODE_TYPES.CallExpression:out.push(inner);return;case eslint_devkit_1.AST_NODE_TYPES.UnaryExpression:case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:collectConditionCalls(inner.argument,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:collectConditionCalls(inner.left,out,depth+1);collectConditionCalls(inner.right,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:collectConditionCalls(inner.test,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.ChainExpression:collectConditionCalls(inner.expression,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:collectConditionCalls(inner.object,out,depth+1);return;case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const init=(0,const_value_1.constInitializerOf)(sourceCode,inner);if(init!==null)collectConditionCalls(init,out,depth+1);return}default:return}}function sameTarget(checkArg,useArg){const check=(0,eslint_devkit_1.unwrapTypeSyntax)(checkArg);const use=(0,eslint_devkit_1.unwrapTypeSyntax)(useArg);if(check.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&use.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return check.name===use.name}return sourceCode.getText(check).replace(/\s/g,"")===sourceCode.getText(use).replace(/\s/g,"")}function checkCallExpression(node){const useMethodName=usedMethodName(node);if(!RISKY_USE_METHODS.has(useMethodName)){return}const rawUseArg=node.arguments[0];if(!rawUseArg)return;const useArg=(0,eslint_devkit_1.unwrapTypeSyntax)(rawUseArg);if(reachesPerUserRoot(useArg))return;let current=node.parent;while(current){if(current.type==="IfStatement"){const conditionCalls=[];collectConditionCalls(current.test,conditionCalls);for(const condition of conditionCalls){const checkMethodName=resolvesToNonFsLocal(condition.callee,condition)?"":memberName(condition.callee)??(condition.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?condition.callee.name:"");if(checkMethods.has(checkMethodName)&&isSecurityRelevantWindow(checkMethodName,useMethodName)){const checkArg=condition.arguments[0];if(checkArg&&sameTarget(checkArg,useArg)){reportToctou(node);return}}if(condition.callee.type==="MemberExpression"&&condition.callee.property.type==="Identifier"&&["isFile","isDirectory"].includes(condition.callee.property.name)&&condition.callee.object.type==="Identifier"){const statsVarName=condition.callee.object.name;let currentScope=sourceCode.getScope(condition);let variable=null;while(currentScope){variable=currentScope.variables.find(v=>v.name===statsVarName)||null;if(variable)break;currentScope=currentScope.upper}if(variable&&variable.defs.length>0){const def=variable.defs[0];if(def.type==="Variable"&&def.node.init&&def.node.init.type==="CallExpression"){const init=def.node.init;const statMethod=memberName(init.callee)??(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?init.callee.name:"");if(["statSync","lstatSync","stat","lstat"].includes(statMethod)){const statArg=init.arguments[0];if(statArg&&sameTarget(statArg,useArg)){reportToctou(node);return}}}}}}}current=current.parent}}function reportToctou(node){context.report({node,messageId:"toctouVulnerability"})}return{CallExpression:checkCallExpression}}});
@@ -1 +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");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:7.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(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&["setItem","writeFile"].includes(node.callee.property.name)){const hasEncryption=node.arguments.some(arg=>arg.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&arg.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&arg.callee.name.includes("encrypt"));if(!hasEncryption){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},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");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:7.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(node.callee.type==="MemberExpression"&&node.callee.property.type==="Identifier"&&["setItem","writeFile"].includes(node.callee.property.name)){const hasEncryption=node.arguments.some(arg=>arg.type==="CallExpression"&&arg.callee.type==="Identifier"&&arg.callee.name.includes("encrypt"));if(!hasEncryption){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"})}}}});