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.
- package/README.md +40 -1
- package/package.json +2 -2
- package/src/index.js +1 -1
- package/src/rules/detect-child-process/index.js +1 -1
- package/src/rules/detect-eval-with-expression/index.js +1 -1
- package/src/rules/detect-non-literal-fs-filename/index.js +1 -1
- package/src/rules/detect-suspicious-dependencies/index.js +1 -1
- package/src/rules/lock-file/index.js +1 -1
- package/src/rules/no-buffer-overread/index.js +1 -1
- package/src/rules/no-cryptojs/index.js +1 -1
- package/src/rules/no-cryptojs-weak-random/index.js +1 -1
- package/src/rules/no-data-in-temp-storage/index.js +1 -1
- package/src/rules/no-deprecated-buffer/index.js +1 -1
- package/src/rules/no-deprecated-cipher-method/index.js +1 -1
- package/src/rules/no-dynamic-command-string/index.js +1 -1
- package/src/rules/no-dynamic-dependency-loading/index.js +1 -1
- package/src/rules/no-dynamic-require/index.js +1 -1
- package/src/rules/no-ecb-mode/index.js +1 -1
- package/src/rules/no-env-injection/index.js +1 -1
- package/src/rules/no-insecure-http-parser/index.js +1 -1
- package/src/rules/no-insecure-key-derivation/index.js +1 -1
- package/src/rules/no-insecure-rsa-padding/index.js +1 -1
- package/src/rules/no-math-random-crypto/index.js +1 -1
- package/src/rules/no-self-signed-certs/index.js +1 -1
- package/src/rules/no-sha1-hash/index.js +1 -1
- package/src/rules/no-shell-injection/index.js +1 -1
- package/src/rules/no-ssrf/index.js +1 -1
- package/src/rules/no-static-iv/index.js +1 -1
- package/src/rules/no-timing-unsafe-compare/index.js +1 -1
- package/src/rules/no-toctou-vulnerability/index.js +1 -1
- package/src/rules/no-unbounded-decompression/index.js +1 -1
- package/src/rules/no-unsafe-buffer-alloc/index.js +1 -1
- package/src/rules/no-unsafe-dynamic-require/index.js +1 -1
- package/src/rules/no-weak-cipher-algorithm/index.js +1 -1
- package/src/rules/no-weak-hash-algorithm/index.js +1 -1
- package/src/rules/no-zip-slip/index.js +1 -1
- package/src/rules/prefer-native-crypto/index.js +1 -1
- package/src/rules/require-aead-tag-verification/index.js +1 -1
- package/src/rules/require-dependency-integrity/index.js +1 -1
- package/src/rules/require-secure-credential-storage/index.js +1 -1
- package/src/rules/require-secure-deletion/index.js +1 -1
- package/src/rules/require-storage-encryption/index.js +1 -1
- package/src/rules/require-stream-error-handler/index.js +1 -1
- package/src/utils/const-value.js +1 -0
- package/src/utils/credential-evidence.js +1 -0
- package/src/utils/provenance.js +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noBufferOverread=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const VIEW_METHODS=new Set(["slice","subarray"]);exports.noBufferOverread=(0,eslint_devkit_1.createRule)({name:"no-buffer-overread",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-buffer-overread.md",description:"Detects buffer access beyond bounds",cwe:"CWE-126"},messages:{bufferOverread:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Buffer Overread",cwe:"CWE-126",description:"Buffer access beyond allocated bounds",severity:"{{severity}}",fix:"{{safeAlternative}}",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),unsafeBufferAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Access",cwe:"CWE-126",description:"Buffer accessed without bounds validation",severity:"HIGH",fix:"Add bounds check before buffer access",documentationLink:"https://nodejs.org/api/buffer.html"}),missingBoundsCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Bounds Check",cwe:"CWE-126",description:"Buffer operation missing bounds validation",severity:"MEDIUM",fix:"Validate indices before buffer operations",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),negativeBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Negative Buffer Index",cwe:"CWE-126",description:"Negative index used for buffer access",severity:"MEDIUM",fix:"Ensure buffer indices are non-negative",documentationLink:"https://nodejs.org/api/buffer.html"}),userControlledBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User Controlled Buffer Index",cwe:"CWE-126",description:"Buffer accessed with user-controlled index",severity:"HIGH",fix:"Validate user input before using as buffer index",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),unsafeBufferSlice:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Slice",cwe:"CWE-126",description:"Buffer slice with unvalidated indices",severity:"MEDIUM",fix:"Validate slice start/end indices",documentationLink:"https://nodejs.org/api/buffer.html#bufslicestart-end"}),bufferLengthNotChecked:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Buffer Length Not Checked",cwe:"CWE-126",description:"Buffer length not validated before access",severity:"MEDIUM",fix:"Check buffer.length before operations",documentationLink:"https://nodejs.org/api/buffer.html#buflength"}),useSafeBufferAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Safe Buffer Access",description:"Use bounds-checked buffer access methods",severity:"LOW",fix:"Use buffer.read*() with offset validation or safe wrapper functions",documentationLink:"https://nodejs.org/api/buffer.html"}),validateBufferIndices:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Validate Buffer Indices",description:"Validate buffer indices before use",severity:"LOW",fix:"Check 0 <= index < buffer.length",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),checkBufferBounds:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Check Buffer Bounds",description:"Always check buffer bounds",severity:"LOW",fix:"Validate buffer operations against buffer.length",documentationLink:"https://nodejs.org/api/buffer.html#buflength"}),strategyBoundsChecking:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.STRATEGY,issueName:"Bounds Checking Strategy",description:"Implement comprehensive bounds checking",severity:"LOW",fix:"Validate all buffer indices and lengths before operations",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),strategyInputValidation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.STRATEGY,issueName:"Input Validation Strategy",description:"Validate user input used as buffer indices",severity:"LOW",fix:"Sanitize and validate all user input before buffer operations",documentationLink:"https://nodejs.org/api/buffer.html"}),strategySafeBuffers:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.STRATEGY,issueName:"Safe Buffer Strategy",description:"Use safe buffer wrapper libraries",severity:"LOW",fix:"Use libraries that provide bounds-checked buffer operations",documentationLink:"https://www.npmjs.com/package/safe-buffer"})},schema:[{type:"object",properties:{bufferMethods:{type:"array",items:{type:"string"},default:["readUInt8","readUInt16LE","readUInt32LE","readInt8","readInt16LE","readInt32LE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","subarray","copy"],description:"Buffer read/write methods checked for bounds"},boundsCheckFunctions:{type:"array",items:{type:"string"},default:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],description:"Function names that count as a bounds check"},bufferTypes:{type:"array",items:{type:"string"},default:["Buffer","Uint8Array","ArrayBuffer","DataView"],description:"Constructor names treated as buffer types"},trustedSanitizers:{type:"array",items:{type:"string"},default:[],description:"Additional function names to consider as buffer index validators"},trustedAnnotations:{type:"array",items:{type:"string"},default:[],description:"Additional JSDoc annotations to consider as safe markers"},strictMode:{type:"boolean",default:false,description:"Disable all false positive detection (strict mode)"},reportUnvalidatedIndices:{type:"boolean",default:false,description:"Report every index that cannot be proven validated. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{bufferMethods:["readUInt8","readUInt16LE","readUInt32LE","readInt8","readInt16LE","readInt32LE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","subarray","copy"],boundsCheckFunctions:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes:["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]||{};const{bufferMethods=["readUInt8","readUInt16LE","readUInt32LE","readInt8","readInt16LE","readInt32LE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","subarray","copy"],boundsCheckFunctions=["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes=["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers=[],trustedAnnotations=[],strictMode=false,reportUnvalidatedIndices=false}=options;const sourceCode=context.sourceCode;const filename=context.filename;const safetyChecker=(0,eslint_devkit_1.createSafetyChecker)({trustedSanitizers,trustedAnnotations,trustedOrmPatterns:[],strictMode});const bufferTypesSet=new Set(bufferTypes.map(t=>t.toLowerCase()));const userControlledKeywords=new Set(["req","request","query","params","input","user","offset","index","body"]);const bufferVars=new Set;const addBufferVar=id=>{const variable=(0,provenance_1.findVariable)(sourceCode,id);if(variable)bufferVars.add(variable)};const isBufferType=node=>{const variable=(0,provenance_1.findVariable)(sourceCode,node);if(variable&&bufferVars.has(variable))return true;const lowerName=node.name.toLowerCase();for(const type of bufferTypesSet){if(lowerName.includes(type))return true}if(lowerName==="buf"||lowerName==="bytes")return true;return false};const isWriteTarget=node=>{const parent=node.parent;if(!parent)return false;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression)return parent.left===node;return parent.type===eslint_devkit_1.AST_NODE_TYPES.UpdateExpression};const isLoopBounded=indexNode=>{if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=indexNode.name;let current=indexNode.parent;while(current){const test=current.type===eslint_devkit_1.AST_NODE_TYPES.ForStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.WhileStatement?current.test:null;if(test&&test.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&(test.operator==="<"||test.operator==="<=")&&test.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&test.left.name===name){return true}current=current.parent}return false};const isUserControlledIndex=indexNode=>{if(indexNode.type==="MemberExpression"){let walker=indexNode;while(walker.type==="MemberExpression"){walker=walker.object}if(walker.type==="Identifier"){const root=walker.name.toLowerCase();if(["req","request","event","ctx","context"].includes(root)){return true}for(const keyword of userControlledKeywords){if(root.includes(keyword))return true}}}if(indexNode.type==="Identifier"){const varName=indexNode.name.toLowerCase();for(const keyword of userControlledKeywords){if(varName.includes(keyword))return true}let currentScope=sourceCode.getScope(indexNode);let variable=null;while(currentScope){variable=currentScope.variables.find(v=>v.name===indexNode.name)||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){const init=def.node.init;if(init.type==="MemberExpression"){const objectText=sourceCode.getText(init.object).toLowerCase();const propertyText=sourceCode.getText(init.property).toLowerCase();const keywords=["req","request","query","params","input","user","body"];if(keywords.some(k=>objectText.includes(k)||propertyText.includes(k))){return true}}if(init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const typeConversionFunctions=["number","parseint","parsefloat","string","boolean"];let isTypeConversion=false;if(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){isTypeConversion=typeConversionFunctions.includes(init.callee.name.toLowerCase())}if(isTypeConversion&&init.arguments.length>0){return isUserControlledIndex(init.arguments[0])}}if(init.type==="Identifier"&&init.name!==indexNode.name){return isUserControlledIndex(init)}}}}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const typeConversionFunctions=["Number","parseInt","parseFloat","String","Boolean"];if(indexNode.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&typeConversionFunctions.includes(indexNode.callee.name)){for(const arg of indexNode.arguments){if(isUserControlledIndex(arg)){return true}}}}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const text=sourceCode.getText(indexNode).toLowerCase();const keywords=["req.","request.","query.","params.","body.","input.","user."];if(keywords.some(k=>text.includes(k))){return true}}return false};const isIndexValidated=indexNode=>{if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof indexNode.value==="number"){return indexNode.value>=0}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){let current=indexNode;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&¤t.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&¤t.id.name===indexNode.name&¤t.init){const init=current.init;if(init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&boundsCheckFunctions.includes(init.callee.name)){return true}if(init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&init.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.callee.object.name==="Math"&&init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(init.callee.property.name==="min"||init.callee.property.name==="max")){return true}break}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 params=current.params;for(const param of params){if(param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&¶m.name===indexNode.name){return true}}}current=current.parent}}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&indexNode.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&boundsCheckFunctions.includes(indexNode.callee.name)){return true}return false};const hasBoundsCheck=(bufferName,indexNode)=>{let current=indexNode;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){break}if(current.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){const condition=current.test;const conditionText=sourceCode.getText(condition).toLowerCase();if(conditionText.includes(`${bufferName}.length`)&&(conditionText.includes("<")||conditionText.includes("<=")||conditionText.includes(">")||conditionText.includes(">=")||conditionText.includes("&&")||conditionText.includes("||"))){return true}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration){for(const declarator of current.declarations){if(declarator.init){const initText=sourceCode.getText(declarator.init).toLowerCase();if(initText.includes(`${bufferName}.length`)&&(initText.includes("math.min")||initText.includes("math.max")||initText.includes("mathmin")||initText.includes("mathmax"))){return true}}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement&¤t.argument){const returnText=sourceCode.getText(current.argument).toLowerCase();if(returnText.includes(`${bufferName}.length`)){return true}}current=current.parent}return false};const couldBeNegative=indexNode=>{if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof indexNode.value==="number"){return indexNode.value<0}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.UnaryExpression&&indexNode.operator==="-"&&indexNode.argument.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof indexNode.argument.value==="number"){return true}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&indexNode.operator==="-"){return true}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){let current=indexNode;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&¤t.init){if(current.init.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof current.init.value==="number"&¤t.init.value<0){return true}if(current.init.type===eslint_devkit_1.AST_NODE_TYPES.UnaryExpression&¤t.init.operator==="-"&¤t.init.argument.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof current.init.argument.value==="number"){return true}}current=current.parent}}return false};return{VariableDeclarator(node){if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init){const varName=node.id.name;if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferTypes.includes(node.init.callee.name)){addBufferVar(node.id)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.init.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init.callee.object.name==="Buffer"&&node.init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&["from","alloc","allocUnsafe"].includes(node.init.callee.property.name)){addBufferVar(node.id)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=node.init.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object)){addBufferVar(node.id)}}if(bufferTypes.some(type=>varName.toLowerCase().includes(type.toLowerCase()))){addBufferVar(node.id)}}},MemberExpression(node){if(node.computed&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const bufferName=node.object.name;const indexNode=node.property;if(isWriteTarget(node))return;if(isLoopBounded(indexNode))return;if(isBufferType(node.object)){if(couldBeNegative(indexNode)){context.report({node,messageId:"negativeBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}if(isUserControlledIndex(indexNode)&&!isIndexValidated(indexNode)){if(!hasBoundsCheck(bufferName,indexNode)){if(safetyChecker.isSafe(node,context)){return}context.report({node,messageId:"userControlledBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}}if(reportUnvalidatedIndices&&!hasBoundsCheck(bufferName,indexNode)&&!isIndexValidated(indexNode)){if(safetyChecker.isSafe(node,context)){return}context.report({node,messageId:"unsafeBufferAccess",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(node.property.type==="Identifier"&&bufferMethods.includes(node.property.name)&&node.object.type==="Identifier"&&isBufferType(node.object)){}},CallExpression(node){const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&VIEW_METHODS.has(callee.property.name)&&callee.object.type==="Identifier"&&isBufferType(callee.object)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)){continue}context.report({node:arg,messageId:"unsafeBufferSlice",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&!VIEW_METHODS.has(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)){continue}context.report({node:arg,messageId:"missingBoundsCheck",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}},BinaryExpression(node){const leftText=sourceCode.getText(node.left);const rightText=sourceCode.getText(node.right);if(leftText.includes(".length")||rightText.includes(".length")){}}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noBufferOverread=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const provenance_1=require("../../utils/provenance");const DEFAULT_BUFFER_METHODS=["readUInt8","readInt8","readUInt16LE","readUInt16BE","readInt16LE","readInt16BE","readUInt32LE","readUInt32BE","readInt32LE","readInt32BE","readBigUInt64LE","readBigUInt64BE","readBigInt64LE","readBigInt64BE","readFloatLE","readFloatBE","readDoubleLE","readDoubleBE","readUIntLE","readUIntBE","readIntLE","readIntBE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","subarray","copy"];const DEFAULT_UNTRUSTED_SOURCES=["req","request","event","ctx","context"];const DEFAULT_BUFFER_PARAMETER_NAMES=["buf","buffer","bytes"];function isBufferReturningRead(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;const name=callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.name:callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?callee.property.name:null;return name==="readFileSync"&&node.arguments.length===1}const VIEW_METHODS=new Set(["slice","subarray"]);exports.noBufferOverread=(0,eslint_devkit_1.createRule)({name:"no-buffer-overread",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-buffer-overread.md",description:"Detects buffer access beyond bounds",cwe:"CWE-126"},messages:{unsafeBufferAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Access",cwe:"CWE-126",description:"Buffer accessed without bounds validation",severity:"HIGH",fix:"Add bounds check before buffer access",documentationLink:"https://nodejs.org/api/buffer.html"}),missingBoundsCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Bounds Check",cwe:"CWE-126",description:"Buffer operation missing bounds validation",severity:"MEDIUM",fix:"Validate indices before buffer operations",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),negativeBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Negative Buffer Index",cwe:"CWE-126",description:"Negative index used for buffer access",severity:"MEDIUM",fix:"Ensure buffer indices are non-negative",documentationLink:"https://nodejs.org/api/buffer.html"}),userControlledBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User Controlled Buffer Index",cwe:"CWE-126",description:"Buffer accessed with user-controlled index",severity:"HIGH",fix:"Validate user input before using as buffer index",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),unsafeBufferSlice:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Slice",cwe:"CWE-126",description:"Buffer slice with unvalidated indices",severity:"MEDIUM",fix:"Validate slice start/end indices",documentationLink:"https://nodejs.org/api/buffer.html#bufslicestart-end"})},schema:[{type:"object",properties:{bufferMethods:{type:"array",items:{type:"string"},default:[...DEFAULT_BUFFER_METHODS],description:"Buffer read/write methods checked for bounds"},boundsCheckFunctions:{type:"array",items:{type:"string"},default:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],description:"Function names that count as a bounds check"},bufferTypes:{type:"array",items:{type:"string"},default:["Buffer","Uint8Array","ArrayBuffer","DataView"],description:"Constructor names treated as buffer types"},trustedSanitizers:{type:"array",items:{type:"string"},default:[],description:"Additional function names to consider as buffer index validators"},trustedAnnotations:{type:"array",items:{type:"string"},default:[],description:"Additional JSDoc annotations to consider as safe markers"},untrustedSources:{type:"array",items:{type:"string"},default:[...DEFAULT_UNTRUSTED_SOURCES],description:"Identifier roots treated as carrying an inbound request (default: req, request, event, ctx, context). Replaces the list; [] disables root-based taint."},bufferParameterNames:{type:"array",items:{type:"string"},default:[...DEFAULT_BUFFER_PARAMETER_NAMES],description:"Parameter spellings treated as a Buffer when the type is not otherwise visible (default: buf, buffer, bytes). Whole-name match, never substring. Replaces the list; [] drops the convention."},strictMode:{type:"boolean",default:false,description:"Disable all false positive detection (strict mode)"},reportUnvalidatedIndices:{type:"boolean",default:false,description:"Report every index that cannot be proven validated. Restores the pre-inversion behaviour."}},additionalProperties:false}]},defaultOptions:[{bufferMethods:[...DEFAULT_BUFFER_METHODS],boundsCheckFunctions:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes:["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false,untrustedSources:[...DEFAULT_UNTRUSTED_SOURCES],bufferParameterNames:[...DEFAULT_BUFFER_PARAMETER_NAMES]}],create(context){const options=context.options[0]||{};const{bufferMethods=[...DEFAULT_BUFFER_METHODS],boundsCheckFunctions=["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes=["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers=[],trustedAnnotations=[],strictMode=false,reportUnvalidatedIndices=false,untrustedSources=[...DEFAULT_UNTRUSTED_SOURCES],bufferParameterNames=[...DEFAULT_BUFFER_PARAMETER_NAMES]}=options;const bufferParameterSet=new Set(bufferParameterNames.map(name=>name.toLowerCase()));const sourceCode=context.sourceCode;const filename=context.filename;const safetyChecker=(0,eslint_devkit_1.createSafetyChecker)({trustedSanitizers,trustedAnnotations,trustedOrmPatterns:[],strictMode});const passedTrustedSanitizer=indexNode=>{if(trustedSanitizers.length===0)return false;if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,provenance_1.bindingInit)(sourceCode,indexNode);if(init===void 0||init.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const calleeNode=init.callee;if(calleeNode.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return trustedSanitizers.includes(calleeNode.name)}return calleeNode.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&calleeNode.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&trustedSanitizers.includes(calleeNode.property.name)};const readsTaintSource=(0,provenance_1.makeReadsTaintSource)(sourceCode,new Set(untrustedSources.map(source=>source.toLowerCase())));const bufferVars=new Set;const addBufferVar=id=>{const variable=(0,provenance_1.findVariable)(sourceCode,id);if(variable)bufferVars.add(variable)};const isBufferType=node=>{const variable=(0,provenance_1.findVariable)(sourceCode,node);if(variable&&bufferVars.has(variable))return true;if(variable!==null&&variable.defs.length>0){return variable.defs[0].type==="Parameter"&&bufferParameterSet.has(node.name.toLowerCase())}return bufferParameterSet.has(node.name.toLowerCase())};const isWriteTarget=node=>{const parent=node.parent;if(!parent)return false;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression)return parent.left===node;return parent.type===eslint_devkit_1.AST_NODE_TYPES.UpdateExpression};const isLoopBounded=indexNode=>{if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=indexNode.name;let current=indexNode.parent;while(current){const test=current.type===eslint_devkit_1.AST_NODE_TYPES.ForStatement||current.type===eslint_devkit_1.AST_NODE_TYPES.WhileStatement?current.test:null;if(test&&test.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&(test.operator==="<"||test.operator==="<=")&&test.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&test.left.name===name){return true}current=current.parent}return false};const isUserControlledIndex=indexNode=>readsTaintSource(indexNode);const isIndexValidated=indexNode=>{if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Literal){return typeof indexNode.value==="number"&&indexNode.value>=0}if(isBoundsCheckCall(indexNode))return true;if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const variable=(0,provenance_1.findVariable)(sourceCode,indexNode);if(variable!==null&&variable.defs[0]?.type==="Parameter")return true;const init=(0,provenance_1.bindingInit)(sourceCode,indexNode);return init!==void 0&&isBoundsCheckCall(init)};const isBoundsCheckCall=node=>{if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const callee=node.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return boundsCheckFunctions.includes(callee.name)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||callee.computed||callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return false}return callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Math"&&(callee.property.name==="min"||callee.property.name==="max")||boundsCheckFunctions.includes(callee.property.name)};const hasBoundsCheck=(buffer,indexNode)=>{const guards=[];collectComparisons(enclosingBody(indexNode),guards);return guards.some(test=>mentions(test.left,indexNode)&&readsLengthOf(test.right,buffer)||mentions(test.right,indexNode)&&readsLengthOf(test.left,buffer))};const sameBinding=(a,b)=>{const left=(0,provenance_1.findVariable)(sourceCode,a);const right=(0,provenance_1.findVariable)(sourceCode,b);if(left===null&&right===null)return a.name===b.name;return left===right};const enclosingBody=node=>{let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.Program){return current}current=current.parent}return node};const collectComparisons=(node,out)=>{if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&["<","<=",">",">="].includes(node.operator)){out.push(node)}for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];for(const child of Array.isArray(value)?value:[value]){if(child!==null&&typeof child==="object"&&typeof child.type==="string"){collectComparisons(child,out)}}}};const mentions=(node,indexNode)=>{if(indexNode.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const walk=current=>{if(current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return sameBinding(current,indexNode)}if(current.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return walk(current.left)||walk(current.right)}if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){return current.arguments.some(argument=>argument.type!==eslint_devkit_1.AST_NODE_TYPES.SpreadElement&&walk(argument))}return false};return walk(node)};const readsLengthOf=(node,buffer)=>node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.computed&&node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.property.name==="length"&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&sameBinding(node.object,buffer);const couldBeNegative=indexNode=>{const value=constantNumber(indexNode,0);return value!==null&&value<0};const constantNumber=(node,depth)=>{if(depth>6)return null;if(node.type===eslint_devkit_1.AST_NODE_TYPES.UnaryExpression){const inner=constantNumber(node.argument,depth+1);if(inner===null)return null;return node.operator==="-"?-inner:node.operator==="+"?inner:null}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){if(node.operator!=="-"&&node.operator!=="+")return null;const left=constantNumber(node.left,depth+1);const right=constantNumber(node.right,depth+1);if(left===null||right===null)return null;return node.operator==="-"?left-right:left+right}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,const_value_1.constInitializerOf)(sourceCode,node);return init===null?null:constantNumber(init,depth+1)}const resolved=(0,const_value_1.resolveConstant)(sourceCode,node);return resolved!==null&&typeof resolved.value==="number"?resolved.value:null};return{VariableDeclarator(node){if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init){if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferTypes.includes(node.init.callee.name)){addBufferVar(node.id)}if(isBufferReturningRead(node.init)){addBufferVar(node.id)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.init.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init.callee.object.name==="Buffer"&&node.init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&["from","alloc","allocUnsafe"].includes(node.init.callee.property.name)){addBufferVar(node.id)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=node.init.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object)){addBufferVar(node.id)}}}},MemberExpression(node){if(node.computed&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const buffer=node.object;const indexNode=node.property;if(isWriteTarget(node))return;if(isLoopBounded(indexNode))return;if(isBufferType(node.object)){if(couldBeNegative(indexNode)){context.report({node,messageId:"negativeBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}if(isUserControlledIndex(indexNode)&&!isIndexValidated(indexNode)){if(!hasBoundsCheck(buffer,indexNode)){if(safetyChecker.isSafe(node,context)){return}if(passedTrustedSanitizer(indexNode)){return}context.report({node,messageId:"userControlledBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}}if(reportUnvalidatedIndices&&!hasBoundsCheck(buffer,indexNode)&&!isIndexValidated(indexNode)){if(safetyChecker.isSafe(node,context)){return}if(passedTrustedSanitizer(indexNode)){return}context.report({node,messageId:"unsafeBufferAccess",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(node.property.type==="Identifier"&&bufferMethods.includes(node.property.name)&&node.object.type==="Identifier"&&isBufferType(node.object)){}},CallExpression(node){const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&VIEW_METHODS.has(callee.property.name)&&callee.object.type==="Identifier"&&isBufferType(callee.object)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)||passedTrustedSanitizer(arg)){continue}if(hasBoundsCheck(callee.object,arg)){continue}context.report({node:arg,messageId:"unsafeBufferSlice",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&!VIEW_METHODS.has(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)||passedTrustedSanitizer(arg)){continue}if(hasBoundsCheck(callee.object,arg)){continue}context.report({node:arg,messageId:"missingBoundsCheck",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}},BinaryExpression(node){const leftText=sourceCode.getText(node.left);const rightText=sourceCode.getText(node.right);if(leftText.includes(".length")||rightText.includes(".length")){}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noCryptojs=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noCryptojs=(0,eslint_devkit_1.createRule)({name:"no-cryptojs",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-cryptojs.md",description:"Disallow deprecated crypto-js library (use native crypto instead)",cwe:"CWE-1104",cvss:5.3},
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noCryptojs=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])}}}exports.noCryptojs=(0,eslint_devkit_1.createRule)({name:"no-cryptojs",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-cryptojs.md",description:"Disallow deprecated crypto-js library (use native crypto instead)",cwe:"CWE-1104",cvss:5.3},messages:{deprecatedCryptojs:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Deprecated crypto-js library",cwe:"CWE-1104",description:"crypto-js is no longer maintained (last update: 2022). Future vulnerabilities will not be patched.",severity:"MEDIUM",fix:"Migrate to native Node.js crypto module or Web Crypto API",documentationLink:"https://nodejs.org/api/crypto.html"})},schema:[]},defaultOptions:[{}],create(context){return moduleSpecifierListener(context.sourceCode,(node,specifier)=>{if(specifier==="crypto-js"||specifier.startsWith("crypto-js/")){context.report({node,messageId:"deprecatedCryptojs"})}})}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noCryptojsWeakRandom=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noCryptojsWeakRandom=(0,eslint_devkit_1.createRule)({name:"no-cryptojs-weak-random",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-cryptojs-weak-random.md",description:"Disallow crypto-js WordArray.random() (CVE-2020-36732)",cwe:"CWE-338",cvss:5.3},
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noCryptojsWeakRandom=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 WEAK_RANDOM_PATH=["lib","WordArray","random"];function isCryptoJs(module){return module==="crypto-js"||module.startsWith("crypto-js/")}function endsWithWordArrayRandom(path){return path.length>=2&&path.at(-2)==="WordArray"&&path.at(-1)==="random"}function resolveBinding(sourceCode,node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const key=node.computed?(0,const_value_1.resolveConstantString)(sourceCode,node.property)?.value:node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?node.property.name:void 0;if(key===void 0)return void 0;const base=resolveBinding(sourceCode,node.object);return base&&{module:base.module,path:[...base.path,key]}}return(0,eslint_devkit_1.resolveModuleBinding)(node,sourceCode.getScope(node))}function syntacticPath(sourceCode,node){const path=[];let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const key=current.computed?(0,const_value_1.resolveConstantString)(sourceCode,current.property)?.value:current.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?current.property.name:void 0;if(key===void 0)return null;path.unshift(key);current=current.object}return{root:current,path}}function isWeakRandomCall(sourceCode,callee){const binding=resolveBinding(sourceCode,callee);if(binding!==void 0){return isCryptoJs(binding.module)&&endsWithWordArrayRandom(binding.path)}const spelled=syntacticPath(sourceCode,callee);if(spelled===null||spelled.root.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(((0,provenance_1.findVariable)(sourceCode,spelled.root)?.defs.length??0)>0)return false;return spelled.path.length===WEAK_RANDOM_PATH.length&&WEAK_RANDOM_PATH.every((segment,i)=>spelled.path[i]===segment)}exports.noCryptojsWeakRandom=(0,eslint_devkit_1.createRule)({name:"no-cryptojs-weak-random",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-cryptojs-weak-random.md",description:"Disallow crypto-js WordArray.random() (CVE-2020-36732)",cwe:"CWE-338",cvss:5.3},messages:{weakRandom:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak random in crypto-js",cwe:"CWE-338",description:"CryptoJS.lib.WordArray.random() was insecure in versions < 3.2.1 (CVE-2020-36732). Used Math.random() instead of CSPRNG.",severity:"CRITICAL",fix:"Use crypto.randomBytes() from Node.js or crypto.getRandomValues() in browsers",documentationLink:"https://nvd.nist.gov/vuln/detail/CVE-2020-36732"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow 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 checkCallExpression(node){if(isTestFile)return;if(!isWeakRandomCall(context.sourceCode,node.callee))return;context.report({node,messageId:"weakRandom"})}return{CallExpression:checkCallExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDataInTempStorage=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const DEFAULT_TEMP_PATHS=["/tmp","/var/tmp","temp/","/temp"];const FS_WRITE_FUNCTIONS=["writeFileSync","
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDataInTempStorage=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const DEFAULT_TEMP_PATHS=["/tmp","/var/tmp","temp/","/temp"];const FS_WRITE_FUNCTIONS=["writeFile","writeFileSync","appendFile","appendFileSync","createWriteStream","outputFile","outputFileSync"];const FS_MKDTEMP_FUNCTIONS=["mkdtemp","mkdtempSync"];const FS_EQUIVALENTS={"fs-extra":"fs","graceful-fs":"fs"};function containsPathSegments(haystack,needle){const split=value=>value.split(/[/\\]/).filter(segment=>segment.length>0);const target=split(needle);if(target.length===0)return false;const segments=split(haystack);for(let start=0;start+target.length<=segments.length;start+=1){if(target.every((segment,offset)=>segments[start+offset]===segment))return true}return false}function isTmpdirCall(node){return node.type==="CallExpression"&&node.callee.type==="MemberExpression"&&!node.callee.computed&&node.callee.object.type==="Identifier"&&node.callee.object.name==="os"&&node.callee.property.type==="Identifier"&&node.callee.property.name==="tmpdir"}function isStaticTmpdirJoin(node){const{callee}=node;if(callee.type!=="MemberExpression"||callee.computed)return false;if(callee.property.type!=="Identifier")return false;if(callee.property.name!=="join"&&callee.property.name!=="resolve"){return false}if(callee.object.type!=="Identifier"||callee.object.name!=="path"){return false}if(node.arguments.length<2)return false;if(!isTmpdirCall(node.arguments[0]))return false;return node.arguments.slice(1).every(arg=>arg.type==="Literal"&&typeof arg.value==="string")}function isStaticTmpdirTemplate(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral)return false;let sawTmpdir=false;for(const expression of node.expressions){if(isTmpdirCall(expression)){sawTmpdir=true;continue}if(expression.type!==eslint_devkit_1.AST_NODE_TYPES.Literal||typeof expression.value!=="string"){return false}}return sawTmpdir}function isStaticTmpdirConcat(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.BinaryExpression||node.operator!=="+")return false;const side=part=>{if(isTmpdirCall(part))return"tmpdir";if(part.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof part.value==="string")return"static";if(isStaticTmpdirConcat(part))return"tmpdir";return"other"};const left=side(node.left);const right=side(node.right);if(left==="other"||right==="other")return false;return left==="tmpdir"||right==="tmpdir"}function isPredictableTempPath(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&isStaticTmpdirJoin(node)||isStaticTmpdirTemplate(node)||isStaticTmpdirConcat(node)}exports.noDataInTempStorage=(0,eslint_devkit_1.createRule)({name:"no-data-in-temp-storage",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-data-in-temp-storage.md",description:"Prevent sensitive data in temp directories",cwe:"CWE-312",cvss:5.5},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Temp Storage Data",cwe:"CWE-312",description:"Sensitive data written to temp directory - not secure",severity:"HIGH",fix:"Use secure storage location or encrypt data before writing",documentationLink:"https://cwe.mitre.org/data/definitions/312.html"}),predictableTempPath:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Predictable Temp File Name (CWE-377)",cwe:"CWE-377",cvss:7.5,description:"path.join(os.tmpdir(), \u2026) with a constant name resolves to the same path on every run, in a directory every local user can write. An attacker who pre-creates that name \u2014 or symlinks it at a file they want clobbered \u2014 wins the race before the write happens.",severity:"HIGH",fix:"Create the file inside a fresh directory from fs.mkdtemp/mkdtempSync, or add a crypto.randomUUID() segment to the name.",documentationLink:"https://cwe.mitre.org/data/definitions/377.html"})},schema:[{type:"object",properties:{tempPaths:{type:"array",items:{type:"string"},default:DEFAULT_TEMP_PATHS,description:"Temporary path prefixes to flag. Replaces the built-in list rather than extending it."},ignoreFiles:{type:"array",items:{type:"string"},default:[],description:"List of files or patterns to ignore"}},additionalProperties:false}]},defaultOptions:[{}],create(context){const options=context.options[0]||{};const tempPaths=options.tempPaths||DEFAULT_TEMP_PATHS;const ignoreFiles=options.ignoreFiles||[];const filename=context.filename;if(ignoreFiles.some(pattern=>filename.includes(pattern))){return{}}function report(node){context.report({node,messageId:"violationDetected"})}function fsEntryPoint(node){const callee=node.callee;const scope=context.sourceCode.getScope(node);const binding=callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof callee.property.value==="string"?(base=>base&&{module:base.module,path:[...base.path,callee.property.value]})((0,eslint_devkit_1.resolveModuleBinding)(callee.object,scope,{equivalents:FS_EQUIVALENTS})):(0,eslint_devkit_1.resolveModuleBinding)(callee,scope,{equivalents:FS_EQUIVALENTS});if(binding){const fn=binding.path.at(-1);const prefix=binding.path.slice(0,-1);if(fn===void 0)return void 0;const reachable=binding.module==="fs"&&(prefix.length===0||prefix.length===1&&prefix[0]==="promises")||binding.module==="fs/promises"&&prefix.length===0;return reachable?fn:void 0}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==="fs"&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return callee.property.name}return void 0}function isFsWriteCall(node){const fn=fsEntryPoint(node);return fn!==void 0&&FS_WRITE_FUNCTIONS.includes(fn)}function flowsIntoMkdtemp(identifier){const variable=(0,provenance_1.findVariable)(context.sourceCode,identifier);if(!variable)return false;return variable.references.some(reference=>{const parent=reference.identifier.parent;if(parent?.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;if(parent.arguments[0]!==reference.identifier)return false;const fn=fsEntryPoint(parent);return fn!==void 0&&FS_MKDTEMP_FUNCTIONS.includes(fn)})}function reportIfPredictable(candidate,bound){if(!candidate)return;if(!isPredictableTempPath(candidate))return;if(bound!==null&&bound.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&flowsIntoMkdtemp(bound)){return}context.report({node:candidate,messageId:"predictableTempPath"})}function staticPathRuns(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal){return typeof node.value==="string"?[node.value]:[]}if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.quasis.map(quasi=>quasi.value.raw)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!node.callee.computed&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.callee.property.name==="join"||node.callee.property.name==="resolve")){return node.arguments.filter(arg=>arg.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof arg.value==="string").map(arg=>arg.value)}return[]}function resolvedPathRuns(node){const direct=staticPathRuns(node);if(direct.length>0)return direct;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return[];const variable=(0,provenance_1.findVariable)(context.sourceCode,node);if(!variable)return[];const priorWrites=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]);const lastWrite=priorWrites.at(-1);return lastWrite?staticPathRuns(lastWrite):[]}return{CallExpression(node){if(!isFsWriteCall(node))return;const pathArg=node.arguments[0];if(pathArg){const runs=resolvedPathRuns(pathArg);if(runs.some(run=>tempPaths.some(tp=>containsPathSegments(run,tp)))){report(pathArg);return}}reportIfPredictable(pathArg,null)},VariableDeclarator(node){reportIfPredictable(node.init,node.id)},AssignmentExpression(node){reportIfPredictable(node.right,node.left)}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDeprecatedBuffer=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noDeprecatedBuffer=(0,eslint_devkit_1.createRule)({name:"no-deprecated-buffer",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-deprecated-buffer.md",description:"Disallow the deprecated `new Buffer()` constructor and `Buffer()` factory call.",cwe:"CWE-676",cvss:7.5},fixable:"code",messages:{deprecatedBufferConstructor:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Deprecated Buffer Constructor",cwe:"CWE-676",cvss:7.5,description:"`new Buffer()` is deprecated since Node 10 and unsafe \u2014 when called with a number it returns uninitialized memory (CVE-2018-7166).",severity:"HIGH",fix:"Use `Buffer.alloc(size)` (zero-filled), `Buffer.allocUnsafe(size)` (only when you immediately overwrite the buffer), or `Buffer.from(value)` (for strings/arrays/buffers).",documentationLink:"https://nodejs.org/api/buffer.html#bufnew-buffersize"}),deprecatedBufferCall:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Deprecated Buffer() Factory Call",cwe:"CWE-676",cvss:7.5,description:"`Buffer()` (without `new`) is deprecated since Node 10 and unsafe \u2014 when called with a number it returns uninitialized memory.",severity:"HIGH",fix:"Use `Buffer.alloc(size)`, `Buffer.allocUnsafe(size)`, or `Buffer.from(value)`.",documentationLink:"https://nodejs.org/api/buffer.html#bufnew-buffersize"})},schema:[]},defaultOptions:[],create(context){function
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDeprecatedBuffer=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 BUFFER_MODULES=new Set(["buffer","node:buffer"]);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 fromBufferModule(def){const declaration=def.parent;return declaration.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration&&BUFFER_MODULES.has(declaration.source.value)}exports.noDeprecatedBuffer=(0,eslint_devkit_1.createRule)({name:"no-deprecated-buffer",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-deprecated-buffer.md",description:"Disallow the deprecated `new Buffer()` constructor and `Buffer()` factory call.",cwe:"CWE-676",cvss:7.5},fixable:"code",messages:{deprecatedBufferConstructor:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Deprecated Buffer Constructor",cwe:"CWE-676",cvss:7.5,description:"`new Buffer()` is deprecated since Node 10 and unsafe \u2014 when called with a number it returns uninitialized memory (CVE-2018-7166).",severity:"HIGH",fix:"Use `Buffer.alloc(size)` (zero-filled), `Buffer.allocUnsafe(size)` (only when you immediately overwrite the buffer), or `Buffer.from(value)` (for strings/arrays/buffers).",documentationLink:"https://nodejs.org/api/buffer.html#bufnew-buffersize"}),deprecatedBufferCall:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Deprecated Buffer() Factory Call",cwe:"CWE-676",cvss:7.5,description:"`Buffer()` (without `new`) is deprecated since Node 10 and unsafe \u2014 when called with a number it returns uninitialized memory.",severity:"HIGH",fix:"Use `Buffer.alloc(size)`, `Buffer.allocUnsafe(size)`, or `Buffer.from(value)`.",documentationLink:"https://nodejs.org/api/buffer.html#bufnew-buffersize"})},schema:[]},defaultOptions:[],create(context){const sourceCode=context.sourceCode;function resolvesToNodeBuffer(id){const variable=(0,provenance_1.findVariable)(sourceCode,id);if(variable===null||variable.defs.length===0)return id.name==="Buffer";const def=variable.defs[0];if(def.type==="ImportBinding"){return fromBufferModule(def)&&def.node.type===eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier&&def.node.imported.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&def.node.imported.name==="Buffer"}if(def.type!=="Variable")return false;const init=def.node.init;if(init===null)return false;if(init.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!init.computed&&init.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.property.name==="Buffer"){return isBufferModuleRequire(init.object)}if(!isBufferModuleRequire(init))return false;const property=def.name.parent;return property.type===eslint_devkit_1.AST_NODE_TYPES.Property&&!property.computed&&property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.key.name==="Buffer"}function isBufferNamespace(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const variable=(0,provenance_1.findVariable)(sourceCode,node);if(variable===null||variable.defs.length===0){return node.name==="global"||node.name==="globalThis"}const def=variable.defs[0];if(def.type==="ImportBinding"){return fromBufferModule(def)&&def.node.type===eslint_devkit_1.AST_NODE_TYPES.ImportNamespaceSpecifier}return def.type==="Variable"&&def.node.init!==null&&isBufferModuleRequire(def.node.init)}function deprecatedCallee(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return resolvesToNodeBuffer(callee)?callee:null}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.property.name==="Buffer"&&isBufferNamespace(callee.object)){return callee}return null}function replacementMethod(args){if(args.length!==1)return args.length===0?null:".from";const argument=args[0];if(argument.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return null;if(argument.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression||argument.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return".from"}const resolved=(0,const_value_1.resolveConstant)(sourceCode,argument);if(resolved===null)return null;return typeof resolved.value==="number"?".alloc":".from"}function fixTo(node,callee,method){return fixer=>{const start=node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression?sourceCode.getFirstToken(node).range[0]:callee.range[0];return fixer.replaceTextRange([start,callee.range[1]],`${sourceCode.getText(callee)}${method}`)}}return{NewExpression(node){const callee=deprecatedCallee(node.callee);if(callee===null)return;const method=replacementMethod(node.arguments);context.report({node,messageId:"deprecatedBufferConstructor",...method===null?{}:{fix:fixTo(node,callee,method)}})},CallExpression(node){const callee=deprecatedCallee(node.callee);if(callee===null)return;const method=replacementMethod(node.arguments);context.report({node,messageId:"deprecatedBufferCall",...method===null?{}:{fix:fixTo(node,callee,method)}})}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDeprecatedCipherMethod=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const DEPRECATED_METHODS=new Set(["createCipher","createDecipher"]);exports.noDeprecatedCipherMethod=(0,eslint_devkit_1.createRule)({name:"no-deprecated-cipher-method",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-deprecated-cipher-method.md",description:"Disallow deprecated crypto.createCipher/createDecipher methods (use createCipheriv/createDecipheriv instead)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{deprecatedCipherMethod:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Deprecated cipher method",cwe:"CWE-327",description:"crypto.{{method}}() is deprecated. It derives key from password without salt and uses no IV, making encryption deterministic and vulnerable.",severity:"HIGH",fix:"Use crypto.{{replacement}}() with explicit key and random IV",documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"}),useCipheriv:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use createCipheriv",description:"Use createCipheriv with explicit key derivation and random IV",severity:"LOW",fix:"const key = crypto.scryptSync(password, salt, 32);\nconst iv = crypto.randomBytes(16);\nconst cipher = crypto.createCipheriv(algorithm, key, iv);",documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow deprecated methods in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDeprecatedCipherMethod=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const DEPRECATED_METHODS=new Set(["createCipher","createDecipher"]);exports.noDeprecatedCipherMethod=(0,eslint_devkit_1.createRule)({name:"no-deprecated-cipher-method",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-deprecated-cipher-method.md",description:"Disallow deprecated crypto.createCipher/createDecipher methods (use createCipheriv/createDecipheriv instead)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{deprecatedCipherMethod:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Deprecated cipher method",cwe:"CWE-327",description:"crypto.{{method}}() is deprecated. It derives key from password without salt and uses no IV, making encryption deterministic and vulnerable.",severity:"HIGH",fix:"Use crypto.{{replacement}}() with explicit key and random IV",documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"}),useCipheriv:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use createCipheriv",description:"Use createCipheriv with explicit key derivation and random IV",severity:"LOW",fix:"const key = crypto.scryptSync(password, salt, 32);\nconst iv = crypto.randomBytes(16);\nconst cipher = crypto.createCipheriv(algorithm, key, iv);",documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow deprecated methods 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 checkCallExpression(node){if(isTestFile)return;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&DEPRECATED_METHODS.has(node.callee.property.name)){const callee=node.callee;const propertyNode=callee.property;const methodName=propertyNode.name;const replacementName=methodName==="createCipher"?"createCipheriv":"createDecipheriv";context.report({node:propertyNode,messageId:"deprecatedCipherMethod",data:{method:methodName,replacement:replacementName},suggest:[{messageId:"useCipheriv",fix:fixer=>{return fixer.replaceText(propertyNode,replacementName)}}]})}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&DEPRECATED_METHODS.has(node.callee.name)){const methodName=node.callee.name;const replacementName=methodName==="createCipher"?"createCipheriv":"createDecipheriv";context.report({node:node.callee,messageId:"deprecatedCipherMethod",data:{method:methodName,replacement:replacementName},suggest:[{messageId:"useCipheriv",fix:fixer=>{return fixer.replaceText(node.callee,replacementName)}}]})}}return{CallExpression:checkCallExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicCommandString=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const ARGV_FUNCTIONS=new Set(["spawn","spawnSync","execFile","execFileSync","fork"]);const POSIX_COMMAND_FLAGS=new Set(["-c"]);const CMD_COMMAND_FLAGS=new Set(["/c","/C","/k","/K"]);const POWERSHELL_COMMAND_FLAGS=new Set(["-Command","-command","-c","-EncodedCommand","-encodedcommand","-e","-ec"]);const SHELL_COMMAND_FLAGS={sh:POSIX_COMMAND_FLAGS,bash:POSIX_COMMAND_FLAGS,zsh:POSIX_COMMAND_FLAGS,dash:POSIX_COMMAND_FLAGS,ksh:POSIX_COMMAND_FLAGS,busybox:POSIX_COMMAND_FLAGS,cmd:CMD_COMMAND_FLAGS,"cmd.exe":CMD_COMMAND_FLAGS,powershell:POWERSHELL_COMMAND_FLAGS,"powershell.exe":POWERSHELL_COMMAND_FLAGS,pwsh:POWERSHELL_COMMAND_FLAGS};const COMMAND_RUNNERS=new Set(["execaCommand","execaCommandSync","$.raw"]);function basename(command){const segments=command.split(/[\\/]/);return segments[segments.length-1]}function isAssembledString(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.expressions.length>0}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return node.operator==="+"}return node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier||node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression||node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression}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)return null;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;if(callee.computed)return null;if(callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="$"){return`$.${callee.property.name}`}return callee.property.name}exports.noDynamicCommandString=(0,eslint_devkit_1.createRule)({name:"no-dynamic-command-string",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-command-string.md",description:"Disallow dynamically assembled command strings passed to a shell flag or to a command-runner that does not escape (CWE-77)",cwe:"CWE-77",cvss:9.8,confidence:"high"},messages:{shellFlagInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Command Injection Through Shell Flag (CWE-77)",cwe:"CWE-77",cvss:9.8,description:'{{fn}}("{{shell}}", ["{{flag}}", \u2026]) hands a dynamically built string to {{shell}}, which parses it as a command line. The argument array looks parameterized but everything after {{flag}} is re-parsed \u2014 `;`, `&&`, backticks and `$()` all execute.',severity:"CRITICAL",fix:'Invoke the target program directly with its own argument array \u2014 spawn("kill", [String(pid)]) \u2014 instead of routing it through a shell.',documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html"}),commandStringInterpolation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Interpolated Command Line (CWE-77)",cwe:"CWE-77",cvss:9.8,description:"{{fn}}() takes a whole command line and does NOT escape interpolated values (unlike the execa/zx tagged-template forms). Any special character in the interpolated value changes which command runs.",severity:"CRITICAL",fix:'Use the tagged-template or array form that escapes for you: execa("git", ["clone", url]) or await $`git clone ${url}`.',documentationLink:"https://cwe.mitre.org/data/definitions/77.html"})},schema:[{type:"object",properties:{extraCommandRunners:{type:"array",items:{type:"string"},description:"Extra functions that accept a full command line without escaping"}},additionalProperties:false}]},defaultOptions:[{}],create(context,[options]){const{extraCommandRunners}=options;const runners=new Set([...COMMAND_RUNNERS,...extraCommandRunners??[]]);function
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicCommandString=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const ARGV_FUNCTIONS=new Set(["spawn","spawnSync","execFile","execFileSync","fork","execa","execaSync"]);const POSIX_COMMAND_FLAGS=new Set(["-c"]);const CMD_COMMAND_FLAGS=new Set(["/c","/C","/k","/K"]);const POWERSHELL_COMMAND_FLAGS=new Set(["-Command","-command","-c","-EncodedCommand","-encodedcommand","-e","-ec"]);const SHELL_COMMAND_FLAGS={sh:POSIX_COMMAND_FLAGS,bash:POSIX_COMMAND_FLAGS,zsh:POSIX_COMMAND_FLAGS,dash:POSIX_COMMAND_FLAGS,ksh:POSIX_COMMAND_FLAGS,busybox:POSIX_COMMAND_FLAGS,cmd:CMD_COMMAND_FLAGS,"cmd.exe":CMD_COMMAND_FLAGS,powershell:POWERSHELL_COMMAND_FLAGS,"powershell.exe":POWERSHELL_COMMAND_FLAGS,pwsh:POWERSHELL_COMMAND_FLAGS};const COMMAND_RUNNERS=new Set(["execaCommand","execaCommandSync","$.raw"]);const POSIX_COMMAND_CLUSTER=/^-[a-zA-Z]*c$/;function isCommandFlag(commandFlags,flag){if(commandFlags.has(flag))return true;return commandFlags===POSIX_COMMAND_FLAGS&&POSIX_COMMAND_CLUSTER.test(flag)}function basename(command){const segments=command.split(/[\\/]/);return segments[segments.length-1]}function shellDialect(command){const shell=basename(command);const flags=SHELL_COMMAND_FLAGS[shell.toLowerCase()];return flags?{shell,flags}:null}function isAssembledString(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.expressions.length>0}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return node.operator==="+"}return node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier||node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression||node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression}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)return null;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;if(callee.computed)return null;if(callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="$"){return`$.${callee.property.name}`}return callee.property.name}exports.noDynamicCommandString=(0,eslint_devkit_1.createRule)({name:"no-dynamic-command-string",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-command-string.md",description:"Disallow dynamically assembled command strings passed to a shell flag or to a command-runner that does not escape (CWE-77)",cwe:"CWE-77",cvss:9.8,confidence:"high"},messages:{shellFlagInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Command Injection Through Shell Flag (CWE-77)",cwe:"CWE-77",cvss:9.8,description:'{{fn}}("{{shell}}", ["{{flag}}", \u2026]) hands a dynamically built string to {{shell}}, which parses it as a command line. The argument array looks parameterized but everything after {{flag}} is re-parsed \u2014 `;`, `&&`, backticks and `$()` all execute.',severity:"CRITICAL",fix:'Invoke the target program directly with its own argument array \u2014 spawn("kill", [String(pid)]) \u2014 instead of routing it through a shell.',documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html"}),commandStringInterpolation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Interpolated Command Line (CWE-77)",cwe:"CWE-77",cvss:9.8,description:"{{fn}}() takes a whole command line and does NOT escape interpolated values (unlike the execa/zx tagged-template forms). Any special character in the interpolated value changes which command runs.",severity:"CRITICAL",fix:'Use the tagged-template or array form that escapes for you: execa("git", ["clone", url]) or await $`git clone ${url}`.',documentationLink:"https://cwe.mitre.org/data/definitions/77.html"})},schema:[{type:"object",properties:{extraCommandRunners:{type:"array",items:{type:"string"},default:[],description:"Extra functions that accept a full command line without escaping. Added to the built-in runners, not a replacement for them."}},additionalProperties:false}]},defaultOptions:[{}],create(context,[options]){const{extraCommandRunners}=options;const runners=new Set([...COMMAND_RUNNERS,...extraCommandRunners??[]]);const{sourceCode}=context;function staticStringOf(node){const resolved=(0,const_value_1.resolveConstantString)(sourceCode,node);if(resolved)return resolved.value;if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;if(node.computed)return null;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;if(node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const init=(0,const_value_1.constInitializerOf)(sourceCode,node.object);if(!init)return null;const literal=init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&calleeName(init.callee)==="freeze"&&init.arguments[0]?init.arguments[0]:init;if(literal.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return null;for(const property of literal.properties){if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;if(property.computed)continue;const key=property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.key.name:property.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof property.key.value==="string"?property.key.value:null;if(key!==node.property.name)continue;return(0,const_value_1.resolveConstantString)(sourceCode,property.value)?.value??null}return null}function isAssembled(node){if(staticStringOf(node)!==null)return false;return isAssembledString(node)}function canonicalName(callee){const direct=calleeName(callee);if(direct!==null&&(ARGV_FUNCTIONS.has(direct)||runners.has(direct))){return direct}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return direct;const init=(0,const_value_1.constInitializerOf)(sourceCode,callee);if(!init)return direct;const target=init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&calleeName(init.callee)==="promisify"?init.arguments[0]:init;if(!target)return direct;const resolved=calleeName(target);if(resolved===null)return direct;return ARGV_FUNCTIONS.has(resolved)||runners.has(resolved)?resolved:direct}function shellPositions(command,argv){const positions=[];const commandText=staticStringOf(command);const outer=commandText===null?null:shellDialect(commandText);if(outer)positions.push({...outer,from:0});argv.elements.forEach((element,index)=>{if(!element)return;const text=staticStringOf(element);if(text===null)return;const nested=shellDialect(text);if(nested)positions.push({...nested,from:index+1})});return positions}function checkShellFlag(node,fn){const argvNode=node.arguments[1];const argv=argvNode?.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?(0,const_value_1.constInitializerOf)(sourceCode,argvNode):argvNode;if(!argv||argv.type!==eslint_devkit_1.AST_NODE_TYPES.ArrayExpression)return;const command=node.arguments[0];for(const{shell,flags,from}of shellPositions(command,argv)){for(let i=from;i<argv.elements.length-1;i+=1){const flag=argv.elements[i];if(!flag)continue;const flagText=staticStringOf(flag);if(flagText===null)continue;if(!isCommandFlag(flags,flagText))continue;const commandString=argv.elements[i+1];if(!commandString)continue;if(!isAssembled(commandString))continue;context.report({node:commandString,messageId:"shellFlagInjection",data:{fn,shell,flag:flagText}});return}}}function checkCommandRunner(node,fn){const commandLine=node.arguments[0];if(!commandLine)return;if(!isAssembled(commandLine))return;context.report({node:commandLine,messageId:"commandStringInterpolation",data:{fn}})}return{CallExpression(node){const fn=canonicalName(node.callee);if(!fn)return;if(ARGV_FUNCTIONS.has(fn)){checkShellFlag(node,fn);return}if(runners.has(fn)){checkCommandRunner(node,fn)}},TaggedTemplateExpression(node){const fn=calleeName(node.tag);if(!fn||!runners.has(fn))return;if(node.quasi.expressions.length===0)return;context.report({node:node.quasi,messageId:"commandStringInterpolation",data:{fn}})}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicDependencyLoading=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noDynamicDependencyLoading=(0,eslint_devkit_1.createRule)({name:"no-dynamic-dependency-loading",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-dependency-loading.md",description:"Prevent runtime dependency injection with dynamic paths",cwe:"CWE-1104",cvss:5.3},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"violation Detected",cwe:"CWE-1104",description:"Dynamic import/require detected - use static imports for security",severity:"HIGH",fix:"Review and apply secure practices",documentationLink:"https://cwe.mitre.org/data/definitions/1104.html"})},schema:[]},defaultOptions:[],create(context){
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicDependencyLoading=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");function isLoaderMember(node){if(node.computed)return false;if(node.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.property.name!=="require")return false;const{object}=node;if(object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return object.name==="module";if(object.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||object.computed)return false;if(object.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(object.property.name!=="main")return false;return object.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&object.object.name==="require"}function isLoaderExpression(node){const target=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(target.type===eslint_devkit_1.AST_NODE_TYPES.SequenceExpression){return isLoaderExpression(target.expressions[target.expressions.length-1])}if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return isLoaderMember(target);return target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&target.name==="require"}exports.noDynamicDependencyLoading=(0,eslint_devkit_1.createRule)({name:"no-dynamic-dependency-loading",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-dependency-loading.md",description:"Prevent runtime dependency injection with dynamic paths",cwe:"CWE-1104",cvss:5.3},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"violation Detected",cwe:"CWE-1104",description:"Dynamic import/require detected - use static imports for security",severity:"HIGH",fix:"Review and apply secure practices",documentationLink:"https://cwe.mitre.org/data/definitions/1104.html"})},schema:[]},defaultOptions:[],create(context){const isSteerable=node=>!(0,eslint_devkit_1.isStaticExpression)({node,scope:context.sourceCode.getScope(node)});const isModuleLoader=callee=>{if(isLoaderExpression(callee))return true;const target=(0,eslint_devkit_1.unwrapTypeSyntax)(callee);if(target.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const init=(0,const_value_1.constInitializerOf)(context.sourceCode,target);if(init===null)return false;if(init.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return isLoaderExpression(init);const binding=(0,eslint_devkit_1.resolveModuleBinding)(init.callee,context.sourceCode.getScope(init));return binding?.module==="module"&&binding.path.join(".")==="createRequire"};return{CallExpression(node){const specifier=node.arguments[0];if(specifier!==void 0&&isModuleLoader(node.callee)&&isSteerable(specifier)){context.report({node,messageId:"violationDetected"})}},ImportExpression(node){if(isSteerable(node.source)){context.report({node,messageId:"violationDetected"})}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicRequire=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noDynamicRequire=(0,eslint_devkit_1.createRule)({name:"no-dynamic-require",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-require.md",description:"Forbid `require()` calls with expressions",cwe:"CWE-94",cweJustification:"CWE-94 (Improper Control of Generation of Code) \u2014 dynamic require with attacker-influenced path can load arbitrary modules, equivalent to remote code execution.",confidence:"high"},hasSuggestions:false,messages:{dynamicRequire:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Dynamic Require",description:"Require call uses dynamic expression",severity:"HIGH",fix:"Use static string literals for require() calls",documentationLink:"https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-dynamic-require.md"})},schema:[{type:"object",properties:{allowContexts:{type:"array",items:{type:"string",enum:["test","config","build","runtime"]},description:"Allow dynamic requires in specific contexts."},allowPatterns:{type:"array",items:{type:"string"},description:"Regex patterns
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicRequire=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noDynamicRequire=(0,eslint_devkit_1.createRule)({name:"no-dynamic-require",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-require.md",description:"Forbid `require()` calls with expressions",cwe:"CWE-94",cweJustification:"CWE-94 (Improper Control of Generation of Code) \u2014 dynamic require with attacker-influenced path can load arbitrary modules, equivalent to remote code execution.",confidence:"high"},hasSuggestions:false,messages:{dynamicRequire:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Dynamic Require",cwe:"CWE-94",description:"Require call uses dynamic expression",severity:"HIGH",fix:"Use static string literals for require() calls",documentationLink:"https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-dynamic-require.md"})},schema:[{type:"object",properties:{allowContexts:{type:"array",items:{type:"string",enum:["test","config","build","runtime"]},default:[],description:"Allow dynamic requires in specific contexts."},allowPatterns:{type:"array",items:{type:"string"},default:[],description:"Regex patterns matched against the source text of the require argument; a match suppresses the report."}},additionalProperties:false}]},defaultOptions:[{allowContexts:[],allowPatterns:[]}],create(context){const[options]=context.options;const{allowContexts=[],allowPatterns=[]}=options||{};const filename=context.filename||"";const allowed=(0,eslint_devkit_1.compileUserPatterns)(allowPatterns,"");function isInAllowedContext(){if(allowContexts.includes("test")&&(filename.includes(".test.")||filename.includes(".spec.")||filename.includes("/__tests__/"))){return true}if(allowContexts.includes("config")&&(filename.includes("config")||filename.includes("webpack")||filename.includes("rollup"))){return true}if(allowContexts.includes("build")&&(filename.includes("build")||filename.includes("scripts")||filename.includes("tools"))){return true}if(allowContexts.includes("runtime")&&(filename.includes("runtime")||filename.includes("dynamic"))){return true}return false}function isFixedSpecifier(node){return(0,eslint_devkit_1.isStaticExpression)({node,scope:context.sourceCode.getScope(node)})}return{CallExpression(node){if(node.callee.type==="Identifier"&&node.callee.name==="require"&&node.arguments.length===1){const requireArg=node.arguments[0];if(isInAllowedContext()){return}if(isFixedSpecifier(requireArg)){return}if(allowed.length>0){const text=context.sourceCode.getText(requireArg);if((0,eslint_devkit_1.matchesAnyUserPattern)(allowed,text)){return}}context.report({node:requireArg,messageId:"dynamicRequire"})}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noEcbMode=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noEcbMode=(0,eslint_devkit_1.createRule)({name:"no-ecb-mode",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-ecb-mode.md",description:"Disallow ECB encryption mode (use GCM or CBC instead)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{ecbMode:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"ECB mode detected",cwe:"CWE-327",description:'ECB mode encrypts identical plaintext blocks to identical ciphertext, leaking data patterns. Famous example: the "ECB penguin".',severity:"HIGH",fix:'Use GCM mode for authenticated encryption: crypto.createCipheriv("aes-256-gcm", key, iv)',documentationLink:"https://blog.cloudflare.com/why-are-some-images-more-secure-than-others/"}),useGcm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use GCM mode",description:"GCM provides authenticated encryption (confidentiality + integrity)",severity:"LOW",fix:'crypto.createCipheriv("aes-256-gcm", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"}),useCbc:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use CBC mode",description:"CBC with HMAC provides confidentiality (add separate MAC for integrity)",severity:"LOW",fix:'crypto.createCipheriv("aes-256-cbc", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow ECB mode in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noEcbMode=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");exports.noEcbMode=(0,eslint_devkit_1.createRule)({name:"no-ecb-mode",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-ecb-mode.md",description:"Disallow ECB encryption mode (use GCM or CBC instead)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{ecbMode:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"ECB mode detected",cwe:"CWE-327",description:'ECB mode encrypts identical plaintext blocks to identical ciphertext, leaking data patterns. Famous example: the "ECB penguin".',severity:"HIGH",fix:'Use GCM mode for authenticated encryption: crypto.createCipheriv("aes-256-gcm", key, iv)',documentationLink:"https://blog.cloudflare.com/why-are-some-images-more-secure-than-others/"}),useGcm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use GCM mode",description:"GCM provides authenticated encryption (confidentiality + integrity)",severity:"LOW",fix:'crypto.createCipheriv("aes-256-gcm", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"}),useCbc:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use CBC mode",description:"CBC with HMAC provides confidentiality (add separate MAC for integrity)",severity:"LOW",fix:'crypto.createCipheriv("aes-256-cbc", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow ECB mode 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 checkCallExpression(node){if(isTestFile)return;const cipherMethods=new Set(["createCipher","createCipheriv","createDecipher","createDecipheriv"]);const isCipherCall=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)||node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&cipherMethods.has(node.callee.name);if(isCipherCall&&node.arguments.length>=1){const algorithmArg=node.arguments[0];const resolved=(0,const_value_1.resolveConstantString)(context.sourceCode,algorithmArg);if(resolved===null)return;const algorithm=resolved.value.toLowerCase();if(algorithm.includes("-ecb")||algorithm.endsWith("ecb")){const gcmReplacement=algorithm.replace(/-?ecb$/,"-gcm");const target=resolved.source;context.report({node:algorithmArg,messageId:"ecbMode",suggest:[{messageId:"useGcm",fix:fixer=>{return fixer.replaceText(target,`"${gcmReplacement}"`)}},{messageId:"useCbc",fix:fixer=>{const cbcReplacement=algorithm.replace(/-?ecb$/,"-cbc");return fixer.replaceText(target,`"${cbcReplacement}"`)}}]})}}}return{CallExpression:checkCallExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noEnvInjection=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const DEFAULT_REQUEST_ROOTS=["req","request","ctx","event"];const MAX_TRACE_DEPTH=3;function isProcessEnv(node){return node.type==="MemberExpression"&&!node.computed&&node.object.type==="Identifier"&&node.object.name==="process"&&node.property.type==="Identifier"&&node.property.name==="env"}function memberChainRoot(node){let current=node;while(current.type==="MemberExpression"){current=current.object}return current.type==="Identifier"?current:null}exports.noEnvInjection=(0,eslint_devkit_1.createRule)({name:"no-env-injection",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-env-injection.md",description:"Disallow writing process.env under a key the caller controls, which can overwrite PATH, NODE_OPTIONS or LD_PRELOAD (CWE-99)",cwe:"CWE-99",cvss:8.8,confidence:"high"},messages:{envKeyInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Environment Variable Injection (CWE-99)",cwe:"CWE-99",cvss:8.8,description:'process.env[\u2026] is written under a key that traces back to the request, so the caller decides WHICH variable is set. PATH changes which binary every later spawn/exec resolves; NODE_OPTIONS="--require ./x.js" and LD_PRELOAD run attacker code in every child process. The value being validated does not help \u2014 the name is the vulnerability.',severity:"HIGH",fix:'Map the caller\'s input through a fixed allowlist before it reaches the key: `const ALLOWED = { locale: "APP_LOCALE" }; const name = ALLOWED[input]; if (!name) return; process.env[name] = value;`. Better still, keep request-scoped settings out of the process environment entirely.',documentationLink:"https://cwe.mitre.org/data/definitions/99.html"}),envBulkInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Bulk Environment Overwrite (CWE-99)",cwe:"CWE-99",cvss:8.8,description:"Object.assign(process.env, \u2026) copies every key of a request-derived object into the environment, so the caller chooses the names AND the values wholesale \u2014 PATH, NODE_OPTIONS and LD_PRELOAD included.",severity:"HIGH",fix:"Copy only the keys you name yourself, from a fixed allowlist, instead of spreading the request object into the environment.",documentationLink:"https://cwe.mitre.org/data/definitions/99.html"})},schema:[{type:"object",properties:{extraRequestRoots:{type:"array",items:{type:"string"},description:"Extra identifiers to treat as roots of request-controlled data"}},additionalProperties:false}]},defaultOptions:[{}],create(context,[options]){const{extraRequestRoots}=options;const requestRoots=new Set([...DEFAULT_REQUEST_ROOTS,...extraRequestRoots??[]]);function findVariable(node){let scope=context.sourceCode.getScope(node);while(scope){const found=scope.variables.find(v=>v.name===node.name);if(found)return found;scope=scope.upper}return null}function isRequestDerived(node,depth){if(depth>MAX_TRACE_DEPTH)return false;if(node.type==="MemberExpression"){const root=memberChainRoot(node);return root!==null&&requestRoots.has(root.name)}if(node.type!=="Identifier")return false;if(requestRoots.has(node.name))return true;const variable=findVariable(node);if(!variable)return false;if(variable.references.filter(ref=>ref.isWrite()).length!==1){return false}const[def]=variable.defs;if(!def||def.node.type!=="VariableDeclarator")return false;const init=def.node.init;return init!=null&&isRequestDerived(init,depth+1)}return{AssignmentExpression(node){const{left}=node;if(left.type!=="MemberExpression"||!left.computed)return;if(!isProcessEnv(left.object))return;if(!isRequestDerived(left.property,0))return;context.report({node:left.property,messageId:"envKeyInjection"})},CallExpression(node){const{callee}=node;if(callee.type!=="MemberExpression"||callee.computed)return;if(callee.property.type!=="Identifier")return;if(callee.property.name!=="assign")return;if(callee.object.type!=="Identifier")return;if(callee.object.name!=="Object")return;const[target,...sources]=node.arguments;if(!target||!isProcessEnv(target))return;for(const source of sources){if(isRequestDerived(source,0)){context.report({node:source,messageId:"envBulkInjection"});return}}}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noEnvInjection=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const DEFAULT_REQUEST_ROOTS=["req","request","ctx","event"];const MAX_TRACE_DEPTH=3;function isProcessEnv(node){return node.type==="MemberExpression"&&!node.computed&&node.object.type==="Identifier"&&node.object.name==="process"&&node.property.type==="Identifier"&&node.property.name==="env"}function memberChainRoot(node){let current=node;while(current.type==="MemberExpression"){current=current.object}return current.type==="Identifier"?current:null}exports.noEnvInjection=(0,eslint_devkit_1.createRule)({name:"no-env-injection",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-env-injection.md",description:"Disallow writing process.env under a key the caller controls, which can overwrite PATH, NODE_OPTIONS or LD_PRELOAD (CWE-99)",cwe:"CWE-99",cvss:8.8,confidence:"high"},messages:{envKeyInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Environment Variable Injection (CWE-99)",cwe:"CWE-99",cvss:8.8,description:'process.env[\u2026] is written under a key that traces back to the request, so the caller decides WHICH variable is set. PATH changes which binary every later spawn/exec resolves; NODE_OPTIONS="--require ./x.js" and LD_PRELOAD run attacker code in every child process. The value being validated does not help \u2014 the name is the vulnerability.',severity:"HIGH",fix:'Map the caller\'s input through a fixed allowlist before it reaches the key: `const ALLOWED = { locale: "APP_LOCALE" }; const name = ALLOWED[input]; if (!name) return; process.env[name] = value;`. Better still, keep request-scoped settings out of the process environment entirely.',documentationLink:"https://cwe.mitre.org/data/definitions/99.html"}),envBulkInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Bulk Environment Overwrite (CWE-99)",cwe:"CWE-99",cvss:8.8,description:"Object.assign(process.env, \u2026) copies every key of a request-derived object into the environment, so the caller chooses the names AND the values wholesale \u2014 PATH, NODE_OPTIONS and LD_PRELOAD included.",severity:"HIGH",fix:"Copy only the keys you name yourself, from a fixed allowlist, instead of spreading the request object into the environment.",documentationLink:"https://cwe.mitre.org/data/definitions/99.html"})},schema:[{type:"object",properties:{extraRequestRoots:{type:"array",items:{type:"string"},default:[],description:"Extra identifiers to treat as roots of request-controlled data. Added to the built-in roots, not a replacement for them."}},additionalProperties:false}]},defaultOptions:[{}],create(context,[options]){const{extraRequestRoots}=options;const requestRoots=new Set([...DEFAULT_REQUEST_ROOTS,...extraRequestRoots??[]]);function findVariable(node){let scope=context.sourceCode.getScope(node);while(scope){const found=scope.variables.find(v=>v.name===node.name);if(found)return found;scope=scope.upper}return null}function isRequestDerived(node,depth){if(depth>MAX_TRACE_DEPTH)return false;if(node.type==="MemberExpression"){const root=memberChainRoot(node);return root!==null&&requestRoots.has(root.name)}if(node.type!=="Identifier")return false;if(requestRoots.has(node.name))return true;const variable=findVariable(node);if(!variable)return false;if(variable.references.filter(ref=>ref.isWrite()).length!==1){return false}const[def]=variable.defs;if(!def||def.node.type!=="VariableDeclarator")return false;const init=def.node.init;return init!=null&&isRequestDerived(init,depth+1)}return{AssignmentExpression(node){const{left}=node;if(left.type!=="MemberExpression"||!left.computed)return;if(!isProcessEnv(left.object))return;if(!isRequestDerived(left.property,0))return;context.report({node:left.property,messageId:"envKeyInjection"})},CallExpression(node){const{callee}=node;if(callee.type!=="MemberExpression"||callee.computed)return;if(callee.property.type!=="Identifier")return;if(callee.property.name!=="assign")return;if(callee.object.type!=="Identifier")return;if(callee.object.name!=="Object")return;const[target,...sources]=node.arguments;if(!target||!isProcessEnv(target))return;for(const source of sources){if(isRequestDerived(source,0)){context.report({node:source,messageId:"envBulkInjection"});return}}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noInsecureHttpParser=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noInsecureHttpParser=(0,eslint_devkit_1.createRule)({name:"no-insecure-http-parser",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-insecure-http-parser.md",description:"Disallow insecureHTTPParser: true on Node HTTP servers and clients",cwe:"CWE-444",cvss:7.5},hasSuggestions:true,messages:{insecureHttpParser:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Lenient HTTP parser enabled",cwe:"CWE-444",description:"insecureHTTPParser: true accepts ambiguous message framing (conflicting Content-Length/Transfer-Encoding, invalid chunk sizes). When a front-end proxy and this process disagree on request boundaries, an attacker can smuggle a request onto another user connection.",severity:"HIGH",fix:"Remove insecureHTTPParser (or set it to false) and fix the upstream that emits malformed framing",documentationLink:"https://portswigger.net/web-security/request-smuggling"}),useStrictParser:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use the strict parser",description:"Node's default llhttp parser rejects ambiguous framing",severity:"LOW",fix:"insecureHTTPParser: false (or remove the option)",documentationLink:"https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow the lenient parser in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const isTestFile=allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noInsecureHttpParser=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noInsecureHttpParser=(0,eslint_devkit_1.createRule)({name:"no-insecure-http-parser",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-insecure-http-parser.md",description:"Disallow insecureHTTPParser: true on Node HTTP servers and clients",cwe:"CWE-444",cvss:7.5},hasSuggestions:true,messages:{insecureHttpParser:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Lenient HTTP parser enabled",cwe:"CWE-444",description:"insecureHTTPParser: true accepts ambiguous message framing (conflicting Content-Length/Transfer-Encoding, invalid chunk sizes). When a front-end proxy and this process disagree on request boundaries, an attacker can smuggle a request onto another user connection.",severity:"HIGH",fix:"Remove insecureHTTPParser (or set it to false) and fix the upstream that emits malformed framing",documentationLink:"https://portswigger.net/web-security/request-smuggling"}),useStrictParser:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use the strict parser",description:"Node's default llhttp parser rejects ambiguous framing",severity:"LOW",fix:"insecureHTTPParser: false (or remove the option)",documentationLink:"https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow the lenient parser 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 isParserKey(key,computed){if(key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return!computed&&key.name==="insecureHTTPParser"}return key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&key.value==="insecureHTTPParser"}function enablesLenientParser(value){return value.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&value.value===true}function report(node,value){context.report({node,messageId:"insecureHttpParser",suggest:[{messageId:"useStrictParser",fix:fixer=>fixer.replaceText(value,"false")}]})}return{Property(node){if(isTestFile)return;if(!isParserKey(node.key,node.computed))return;if(!enablesLenientParser(node.value))return;report(node,node.value)},AssignmentExpression(node){if(isTestFile)return;if(node.left.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return;if(!isParserKey(node.left.property,node.left.computed))return;if(!enablesLenientParser(node.right))return;report(node,node.right)}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noInsecureKeyDerivation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const DEFAULT_MIN_ITERATIONS=1e5;exports.noInsecureKeyDerivation=(0,eslint_devkit_1.createRule)({name:"no-insecure-key-derivation",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-insecure-key-derivation.md",description:"Disallow PBKDF2 with insufficient iterations (< 100,000)",cwe:"CWE-916",cvss:7.5},hasSuggestions:true,messages:{insufficientIterations:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Insufficient PBKDF2 iterations",cwe:"CWE-916",description:"PBKDF2 with {{actual}} iterations is too low. Minimum recommended: {{minimum}} iterations (OWASP 2023).",severity:"HIGH",fix:"Increase iterations to at least {{minimum}}, or use scrypt/Argon2",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html"}),useMinIterations:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use minimum iterations",description:"Use at least {{minimum}} iterations for PBKDF2",severity:"LOW",fix:"crypto.pbkdf2(password, salt, {{minimum}}, keylen, digest)",documentationLink:"https://nodejs.org/api/crypto.html#cryptopbkdf2password-salt-iterations-keylen-digest-callback"})
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noInsecureKeyDerivation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const const_value_1=require("../../utils/const-value");const PBKDF2_EXPORTS=new Set(["pbkdf2","pbkdf2Sync"]);const SUBTLE_DERIVE_METHODS=new Set(["deriveBits","deriveKey"]);function foldNumber(sourceCode,node,depth=0){if(depth>6)return null;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return foldNumber(sourceCode,bare,depth+1);if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal){return typeof node.value==="number"?{value:node.value,source:node}:null}if(node.type===eslint_devkit_1.AST_NODE_TYPES.UnaryExpression&&node.operator==="-"){const inner=foldNumber(sourceCode,node.argument,depth+1);return inner===null?null:{value:-inner.value,source:node}}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){const left=foldNumber(sourceCode,node.left,depth+1);const right=foldNumber(sourceCode,node.right,depth+1);if(left===null||right===null)return null;switch(node.operator){case"*":return{value:left.value*right.value,source:node};case"+":return{value:left.value+right.value,source:node};case"-":return{value:left.value-right.value,source:node};case"**":return{value:left.value**right.value,source:node};default:return null}}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,const_value_1.constInitializerOf)(sourceCode,node);return init===null?null:foldNumber(sourceCode,init,depth+1)}if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const value=objectPropertyValue(sourceCode,node.object,propertyKey(sourceCode,node));return value===null?null:foldNumber(sourceCode,value,depth+1)}return null}function propertyKey(sourceCode,node){if(node.computed)return(0,const_value_1.resolveConstantString)(sourceCode,node.property)?.value??null;return node.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?node.property.name:null}function objectPropertyValue(sourceCode,node,key){if(key===null)return null;let object=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,const_value_1.constInitializerOf)(sourceCode,object);if(init===null)return null;object=(0,eslint_devkit_1.unwrapTypeSyntax)(init)}if(object.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return null;for(const property of object.properties){if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property||property.computed)continue;const name=property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.key.name:String(property.key.value);if(name===key)return property.value}return null}function isPromisifyCall(sourceCode,node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const binding=(0,eslint_devkit_1.resolveModuleBinding)(node.callee,sourceCode.getScope(node));return binding?.module==="util"&&binding.path.at(-1)==="promisify"}function isPbkdf2Callee(sourceCode,callee,depth=0){if(depth>4)return false;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&PBKDF2_EXPORTS.has(callee.property.name)){return true}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&PBKDF2_EXPORTS.has(callee.name))return true;const binding=(0,eslint_devkit_1.resolveModuleBinding)(callee,sourceCode.getScope(callee));if(binding!==void 0&&PBKDF2_EXPORTS.has(binding.path.at(-1)??""))return true;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){return isPromisifyCall(sourceCode,callee)&&callee.arguments.length>0&&isPbkdf2Callee(sourceCode,callee.arguments[0],depth+1)}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init=(0,const_value_1.constInitializerOf)(sourceCode,callee);return init!==null&&isPromisifyCall(sourceCode,init)&&init.arguments.length>0&&isPbkdf2Callee(sourceCode,init.arguments[0],depth+1)}return false}const DEFAULT_MIN_ITERATIONS=1e5;exports.noInsecureKeyDerivation=(0,eslint_devkit_1.createRule)({name:"no-insecure-key-derivation",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-insecure-key-derivation.md",description:"Disallow PBKDF2 with insufficient iterations (< 100,000)",cwe:"CWE-916",cvss:7.5},hasSuggestions:true,messages:{insufficientIterations:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Insufficient PBKDF2 iterations",cwe:"CWE-916",description:"PBKDF2 with {{actual}} iterations is too low. Minimum recommended: {{minimum}} iterations (OWASP 2023).",severity:"HIGH",fix:"Increase iterations to at least {{minimum}}, or use scrypt/Argon2",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html"}),useMinIterations:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use minimum iterations",description:"Use at least {{minimum}} iterations for PBKDF2",severity:"LOW",fix:"crypto.pbkdf2(password, salt, {{minimum}}, keylen, digest)",documentationLink:"https://nodejs.org/api/crypto.html#cryptopbkdf2password-salt-iterations-keylen-digest-callback"})},schema:[{type:"object",properties:{minIterations:{type:"number",default:DEFAULT_MIN_ITERATIONS,description:"Minimum required PBKDF2 iterations"}},additionalProperties:false}]},defaultOptions:[{minIterations:DEFAULT_MIN_ITERATIONS}],create(context,[options={}]){const{minIterations=DEFAULT_MIN_ITERATIONS}=options;const sourceCode=context.sourceCode;function judgeIterations(iterationsArg){if(iterationsArg===void 0||iterationsArg===null)return;const folded=foldNumber(sourceCode,iterationsArg);if(folded===null||folded.value>=minIterations)return;context.report({node:iterationsArg,messageId:"insufficientIterations",data:{actual:String(folded.value),minimum:String(minIterations)},suggest:[{messageId:"useMinIterations",data:{minimum:String(minIterations)},fix:fixer=>{return fixer.replaceText(folded.source,String(minIterations))}}]})}function checkCallExpression(node){if(isPbkdf2Callee(sourceCode,node.callee)){judgeIterations(node.arguments[2]);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==="PBKDF2"||node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="PBKDF2"){const options2=node.arguments[2];if(options2!==void 0){judgeIterations(objectPropertyValue(sourceCode,options2,"iterations"))}return}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&SUBTLE_DERIVE_METHODS.has(node.callee.property.name)){const params=node.arguments[0];if(params===void 0)return;const algorithm=objectPropertyValue(sourceCode,params,"name");if(algorithm===null)return;if((0,const_value_1.resolveConstantString)(sourceCode,algorithm)?.value!=="PBKDF2")return;judgeIterations(objectPropertyValue(sourceCode,params,"iterations"))}}return{CallExpression:checkCallExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noInsecureRsaPadding=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const PKCS1_PADDING_NAMES=new Set(["RSA_PKCS1_PADDING","constants.RSA_PKCS1_PADDING","crypto.constants.RSA_PKCS1_PADDING"]);exports.noInsecureRsaPadding=(0,eslint_devkit_1.createRule)({name:"no-insecure-rsa-padding",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-insecure-rsa-padding.md",description:"Disallow RSA PKCS#1 v1.5 padding (CVE-2023-46809 Marvin Attack)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{insecureRsaPadding:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Insecure RSA padding",cwe:"CWE-327",description:"RSA PKCS#1 v1.5 padding is vulnerable to the Marvin Attack (CVE-2023-46809). Timing side-channels allow attackers to decrypt ciphertexts or forge signatures.",severity:"HIGH",fix:"Use RSA_PKCS1_OAEP_PADDING instead",documentationLink:"https://people.redhat.com/~hkario/marvin/"}),useOaep:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use OAEP padding",description:"Use RSA-OAEP which is not vulnerable to padding oracle attacks",severity:"LOW",fix:"padding: crypto.constants.RSA_PKCS1_OAEP_PADDING",documentationLink:"https://nodejs.org/api/crypto.html#cryptopublicdecryptkey-buffer"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noInsecureRsaPadding=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const PKCS1_PADDING_NAMES=new Set(["RSA_PKCS1_PADDING","constants.RSA_PKCS1_PADDING","crypto.constants.RSA_PKCS1_PADDING"]);exports.noInsecureRsaPadding=(0,eslint_devkit_1.createRule)({name:"no-insecure-rsa-padding",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-insecure-rsa-padding.md",description:"Disallow RSA PKCS#1 v1.5 padding (CVE-2023-46809 Marvin Attack)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{insecureRsaPadding:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Insecure RSA padding",cwe:"CWE-327",description:"RSA PKCS#1 v1.5 padding is vulnerable to the Marvin Attack (CVE-2023-46809). Timing side-channels allow attackers to decrypt ciphertexts or forge signatures.",severity:"HIGH",fix:"Use RSA_PKCS1_OAEP_PADDING instead",documentationLink:"https://people.redhat.com/~hkario/marvin/"}),useOaep:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use OAEP padding",description:"Use RSA-OAEP which is not vulnerable to padding oracle attacks",severity:"LOW",fix:"padding: crypto.constants.RSA_PKCS1_OAEP_PADDING",documentationLink:"https://nodejs.org/api/crypto.html#cryptopublicdecryptkey-buffer"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow 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);const sourceCode=context.sourceCode;function checkCallExpression(node){if(isTestFile)return;const rsaMethods=new Set(["privateDecrypt","publicDecrypt","privateEncrypt","publicEncrypt"]);const isRsaCall=node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&rsaMethods.has(node.callee.property.name)||node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&rsaMethods.has(node.callee.name);if(isRsaCall&&node.arguments.length>=1){const keyArg=node.arguments[0];if(keyArg.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){for(const prop of keyArg.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&prop.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&prop.key.name==="padding"){const paddingText=sourceCode.getText(prop.value);if(PKCS1_PADDING_NAMES.has(paddingText)||paddingText.includes("RSA_PKCS1_PADDING")){context.report({node:prop,messageId:"insecureRsaPadding",suggest:[{messageId:"useOaep",fix:fixer=>{return fixer.replaceText(prop.value,"crypto.constants.RSA_PKCS1_OAEP_PADDING")}}]})}}}}}}return{CallExpression:checkCallExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMathRandomCrypto=void 0;const names_1=require("../../utils/names");const eslint_devkit_1=require("@interlace/eslint-devkit");const CRYPTO_WORDS=["token","tokens","key","keys","secret","secrets","password","passwd","salt","iv","nonce","seed","hash","cipher","auth","session","csrf","otp","pin","code","codes","verify","signature","credential","jwt","encryption","apikey"];const
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noMathRandomCrypto=void 0;const names_1=require("../../utils/names");const provenance_1=require("../../utils/provenance");const eslint_devkit_1=require("@interlace/eslint-devkit");const CRYPTO_WORDS=["token","tokens","key","keys","secret","secrets","password","passwd","salt","iv","nonce","seed","hash","cipher","auth","session","csrf","otp","pin","code","codes","verify","signature","credential","jwt","encryption","apikey"];const CRYPTO_WORD_SET=new Set(CRYPTO_WORDS);const DURATION_TAILS=new Set(["delay","timeout","interval","jitter","backoff","ms","millis","milliseconds","seconds","duration","wait","sleep","ttl","deadline","elapsed","latency","budget"]);const QUANTITY_TAILS=new Set(["count","counts","length","size","total","limit","quota","offset","index","rank","score","percent","ratio","rate","version","page"]);const NON_SECURITY_QUALIFIERS=new Map([["code",new Set(["http","status","error","err","exit","country","zip","postal","area","language","lang","locale","currency","promo","coupon","discount","color","colour","qr","bar","region","iso","mime","media","sort","source","char","unicode","ascii","airport"])],["key",new Set(["cache","map","object","row","index","partition","primary","foreign","sort","storage","translation","locale","i18n","react","idempotency","shortcut","keyboard","press","modifier"])]]);const baseNameSuggestsCrypto=(0,names_1.makeNameTest)(CRYPTO_WORDS);function nameSuggestsCrypto(name){if(!baseNameSuggestsCrypto(name))return false;const words=(0,names_1.identifierWords)(name);const tail=words[words.length-1];if(DURATION_TAILS.has(tail)||QUANTITY_TAILS.has(tail))return false;const matched=words.filter(word=>CRYPTO_WORD_SET.has(word));if(matched.length===0)return true;return!matched.every(word=>{const qualifiers=NON_SECURITY_QUALIFIERS.get(word);return qualifiers!==void 0&&words.some(other=>qualifiers.has(other))})}function isMathRandomProperty(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;if(node.object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(node.object.name!=="Math")return false;const property=node.property;if(!node.computed){return property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.name==="random"}return property.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&property.value==="random"}const CRYPTO_FUNCTION_PATTERNS=[/generate.*token/i,/generate.*key/i,/generate.*id/i,/create.*secret/i,/create.*token/i,/random.*string/i,/get.*random.*(string|bytes|token|key|secret|value|id)/i,/make.*salt/i,/gen.*password/i];exports.noMathRandomCrypto=(0,eslint_devkit_1.createRule)({name:"no-math-random-crypto",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-math-random-crypto.md",description:"Disallow Math.random() for cryptographic purposes",cwe:"CWE-338",cvss:5.3,confidence:"medium"},messages:{mathRandomCrypto:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Math.random() used for crypto",cwe:"CWE-338",description:"Math.random() is not cryptographically secure. It uses a PRNG that can be predicted. Never use it for tokens, keys, passwords, or any security-sensitive values.",severity:"CRITICAL",fix:"Use crypto.randomBytes() or crypto.randomUUID() instead",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#secure-random-number-generation"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow Math.random() in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const sourceCode=context.sourceCode;const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);function stableDeclarator(id){const variable=(0,provenance_1.findVariable)(sourceCode,id);if(!variable||variable.defs.length!==1)return void 0;const def=variable.defs[0];if(def.type!=="Variable")return void 0;const reassigned=variable.references.some(reference=>reference.writeExpr!=null&&!reference.init);return reassigned?void 0:def.node}function isMathRandomCallee(callee){if(isMathRandomProperty(callee))return true;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&!callee.computed&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const init2=stableDeclarator(callee.object)?.init;if(!init2||init2.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return false;const wanted=callee.property.name;return init2.properties.some(property=>property.type===eslint_devkit_1.AST_NODE_TYPES.Property&&!property.computed&&property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&property.key.name===wanted&&isMathRandomProperty(property.value))}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const declarator=stableDeclarator(callee);const init=declarator?.init;if(!declarator||!init)return false;if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return isMathRandomProperty(init)}if(declarator.id.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectPattern)return false;if(init.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||init.name!=="Math")return false;return declarator.id.properties.some(property=>{if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property||property.computed)return false;if(property.value.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(property.value.name!==callee.name)return false;const key=property.key;if(key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return key.name==="random";return key.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&key.value==="random"})}const MAX_BINDING_HOPS=2;function usedInCryptoContext(id,depth){const variable=(0,provenance_1.findVariable)(sourceCode,id);if(!variable)return false;return variable.references.some(reference=>!reference.init&&isCryptoContext(reference.identifier,depth))}function isCryptoContext(node,depth=0){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator){if(current.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const varName=current.id.name;if(nameSuggestsCrypto(varName)){return true}if(depth<MAX_BINDING_HOPS&&usedInCryptoContext(current.id,depth+1)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&¤t.id){const funcName=current.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))){return true}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression){if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&¤t.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.left.property.name;if(nameSuggestsCrypto(propName)){return true}}if(current.left.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){if(nameSuggestsCrypto(current.left.name)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.Property){if(current.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const propName=current.key.name;if(nameSuggestsCrypto(propName)){return true}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){const func=findContainingFunction(current);if(func){if((func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||func.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression)&&func.id?.name){const funcName=func.id.name;if(CRYPTO_FUNCTION_PATTERNS.some(p=>p.test(funcName))||nameSuggestsCrypto(funcName)){return true}}}}current=current.parent}return false}function findContainingFunction(node){let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){return current}current=current.parent}return null}return{CallExpression(node){if(isTestFile)return;if(isMathRandomCallee(node.callee)){if(isCryptoContext(node)){context.report({node,messageId:"mathRandomCrypto"})}}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSelfSignedCerts=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noSelfSignedCerts=(0,eslint_devkit_1.createRule)({name:"no-self-signed-certs",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-self-signed-certs.md",description:"Disallow rejectUnauthorized: false in TLS options",cwe:"CWE-295",cvss:
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSelfSignedCerts=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noSelfSignedCerts=(0,eslint_devkit_1.createRule)({name:"no-self-signed-certs",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-self-signed-certs.md",description:"Disallow rejectUnauthorized: false in TLS options",cwe:"CWE-295",cvss:7.4},hasSuggestions:true,messages:{insecureTls:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"TLS certificate validation disabled",cwe:"CWE-295",description:"rejectUnauthorized: false disables TLS certificate validation, enabling man-in-the-middle attacks. Any certificate will be accepted, including self-signed and expired ones.",severity:"CRITICAL",fix:"Remove rejectUnauthorized: false or set it to true",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html"}),enableValidation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Enable certificate validation",description:"Enable proper TLS certificate validation",severity:"LOW",fix:"rejectUnauthorized: true (or remove the property)",documentationLink:"https://nodejs.org/api/tls.html#tlsconnectoptions-callback"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow 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 checkProperty(node){if(isTestFile)return;if(node.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.key.name==="rejectUnauthorized"&&node.value.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&node.value.value===false){context.report({node,messageId:"insecureTls",suggest:[{messageId:"enableValidation",fix:fixer=>{return fixer.replaceText(node.value,"true")}}]})}}function checkAssignmentExpression(node){if(isTestFile)return;if(node.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.left.object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.left.object.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.left.object.object.name==="process"&&node.left.object.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.left.object.property.name==="env"&&node.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.left.property.name==="NODE_TLS_REJECT_UNAUTHORIZED"){if(node.right.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&(node.right.value==="0"||node.right.value===0)){context.report({node,messageId:"insecureTls"})}}}return{Property:checkProperty,AssignmentExpression:checkAssignmentExpression}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSha1Hash=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noSha1Hash=(0,eslint_devkit_1.createRule)({name:"no-sha1-hash",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-sha1-hash.md",description:"Disallow sha1() from crypto-hash package (use sha256 or sha512)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{sha1Deprecated:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"SHA-1 hash detected",cwe:"CWE-327",description:"SHA-1 is cryptographically broken and should not be used. The crypto-hash package itself warns against using sha1().",severity:"HIGH",fix:"Use sha256() or sha512() instead",documentationLink:"https://shattered.io/"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use sha256",description:"SHA-256 provides adequate security for most use cases",severity:"LOW",fix:"import { sha256 } from 'crypto-hash'; await sha256(data)",documentationLink:"https://www.npmjs.com/package/crypto-hash"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use sha512",description:"SHA-512 provides stronger security with longer hash",severity:"LOW",fix:"import { sha512 } from 'crypto-hash'; await sha512(data)",documentationLink:"https://www.npmjs.com/package/crypto-hash"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow SHA1 in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSha1Hash=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noSha1Hash=(0,eslint_devkit_1.createRule)({name:"no-sha1-hash",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-sha1-hash.md",description:"Disallow sha1() from crypto-hash package (use sha256 or sha512)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{sha1Deprecated:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"SHA-1 hash detected",cwe:"CWE-327",description:"SHA-1 is cryptographically broken and should not be used. The crypto-hash package itself warns against using sha1().",severity:"HIGH",fix:"Use sha256() or sha512() instead",documentationLink:"https://shattered.io/"}),useSha256:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use sha256",description:"SHA-256 provides adequate security for most use cases",severity:"LOW",fix:"import { sha256 } from 'crypto-hash'; await sha256(data)",documentationLink:"https://www.npmjs.com/package/crypto-hash"}),useSha512:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use sha512",description:"SHA-512 provides stronger security with longer hash",severity:"LOW",fix:"import { sha512 } from 'crypto-hash'; await sha512(data)",documentationLink:"https://www.npmjs.com/package/crypto-hash"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow SHA1 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);let sha1ImportedFromCryptoHash=false;return{ImportDeclaration(node){if(isTestFile)return;if(node.source.value==="crypto-hash"){for(const specifier of node.specifiers){if(specifier.type===eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier&&specifier.imported.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&specifier.imported.name==="sha1"){sha1ImportedFromCryptoHash=true;context.report({node:specifier,messageId:"sha1Deprecated",suggest:[{messageId:"useSha256",fix:fixer=>{return fixer.replaceText(specifier.imported,"sha256")}},{messageId:"useSha512",fix:fixer=>{return fixer.replaceText(specifier.imported,"sha512")}}]})}}}},CallExpression(node){if(isTestFile)return;if(sha1ImportedFromCryptoHash&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="sha1"){context.report({node,messageId:"sha1Deprecated",suggest:[{messageId:"useSha256",fix:fixer=>{return fixer.replaceText(node.callee,"sha256")}}]})}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noShellInjection=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const constant_folding_1=require("../../utils/constant-folding");const SHELL_EXEC_FUNCTIONS=new Set(["exec","execSync"]);function isStringConcatOrTemplate(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral&&node.expressions.length>0)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&node.operator==="+"&&(node.left.type===eslint_devkit_1.AST_NODE_TYPES.Literal||node.left.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral||node.left.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression||node.right.type===eslint_devkit_1.AST_NODE_TYPES.Literal||node.right.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral))return true;return false}exports.noShellInjection=(0,eslint_devkit_1.createRule)({name:"no-shell-injection",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-shell-injection.md",description:"Disallow string concatenation or template expressions in shell command arguments (CWE-78)",cwe:"CWE-78",cvss:9.8},messages:{shellInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"OS Command Injection (CWE-78)",cwe:"CWE-78",description:"Shell command built via string concatenation or template literal. An attacker who controls any interpolated value can execute arbitrary OS commands.",severity:"CRITICAL",fix:"Use spawn(cmd, [arg1, arg2]) with separate arguments instead of exec(cmd + args). Never build shell commands via string interpolation.",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html"})},schema:[]},defaultOptions:[],create(context){const isLiteralConstant=(0,constant_folding_1.makeIsLiteralConstant)(context.sourceCode);return{CallExpression(node){const callee=node.callee;let fnName=null;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){fnName=callee.name}else if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){fnName=callee.property.name}if(!fnName||!SHELL_EXEC_FUNCTIONS.has(fnName))return;const firstArg=node.arguments[0];if(!firstArg||firstArg.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return;if(!isStringConcatOrTemplate(firstArg))return;if(isLiteralConstant(firstArg))return;context.report({node:firstArg,messageId:"shellInjection"})}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noShellInjection=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const constant_folding_1=require("../../utils/constant-folding");const SHELL_EXEC_FUNCTIONS=new Set(["exec","execSync"]);function isStringConcatOrTemplate(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral&&node.expressions.length>0)return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&node.operator==="+"&&(node.left.type===eslint_devkit_1.AST_NODE_TYPES.Literal||node.left.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral||node.left.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression||node.right.type===eslint_devkit_1.AST_NODE_TYPES.Literal||node.right.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral))return true;return false}exports.noShellInjection=(0,eslint_devkit_1.createRule)({name:"no-shell-injection",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-shell-injection.md",description:"Disallow string concatenation or template expressions in shell command arguments (CWE-78)",cwe:"CWE-78",cvss:9.8},messages:{shellInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"OS Command Injection (CWE-78)",cwe:"CWE-78",description:"Shell command built via string concatenation or template literal. An attacker who controls any interpolated value can execute arbitrary OS commands.",severity:"CRITICAL",fix:"Use spawn(cmd, [arg1, arg2]) with separate arguments instead of exec(cmd + args). Never build shell commands via string interpolation.",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html"})},schema:[{type:"object",properties:{requireModuleEvidence:{type:"boolean",default:true,description:"Only report when the callee resolves to child_process. Turning this off restores the pre-2026-08 behaviour, where any callee named exec/execSync/spawn was treated as a shell sink \u2014 which reported better-sqlite3 db.exec(sql) as CWE-78 at CVSS 9.8."}},additionalProperties:false}]},defaultOptions:[{requireModuleEvidence:true}],create(context,[options={}]){const isLiteralConstant=(0,constant_folding_1.makeIsLiteralConstant)(context.sourceCode);const{requireModuleEvidence=true}=options;return{CallExpression(node){const callee=node.callee;let fnName=null;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){fnName=callee.name}else if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){fnName=callee.property.name}if(!fnName||!SHELL_EXEC_FUNCTIONS.has(fnName))return;if(requireModuleEvidence&&!(0,eslint_devkit_1.isModuleBinding)(callee,context.sourceCode.getScope(node),"child_process")){return}const firstArg=node.arguments[0];if(!firstArg||firstArg.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return;if(!isStringConcatOrTemplate(firstArg))return;if(isLiteralConstant(firstArg))return;context.report({node:firstArg,messageId:"shellInjection"})}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSsrf=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const HTTP_CLIENT_FUNCTIONS=new Set(["fetch","got","nodeFetch","undici"]);const HTTP_CLIENT_METHODS=new Set(["get","post","put","patch","delete","head","options","request"]);const HTTP_CLIENT_OBJECTS=new Set(["axios","got","superagent","request","http","https","undici","needle"]);const VALIDATION_FUNCTION_NAMES=new Set(["validateUrl","validateURL","isValidUrl","isSafeUrl","isAllowed","isValidURL","checkUrl","checkURL","sanitizeUrl","sanitizeURL"]);const USER_INPUT_SUBSTRINGS=["url","endpoint","uri","href","link","target","dest","source","host","user","input","param"];function isUserInputParamName(name){const lower=name.toLowerCase();return USER_INPUT_SUBSTRINGS.some(sub=>lower.includes(sub))}const REQUEST_ROOT_NAMES=new Set(["req","request","ctx","event"]);const URL_OPTION_KEYS=new Set(["url","href","uri"]);function isRequestSourced(node){let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object}return current!==node&¤t.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&REQUEST_ROOT_NAMES.has(current.name.toLowerCase())}function makeCarriesUntrustedUrl(sourceCode,reportUnresolvedUrls){const carries=(node,depth)=>{if(depth>6)return false;switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const init=(0,provenance_1.bindingInit)(sourceCode,node);if(init!==void 0)return carries(init,depth+1);return reportUnresolvedUrls&&isUserInputParamName(node.name)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return isRequestSourced(node)||carries(node.object,depth+1);case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(expression=>carries(expression,depth+1));case eslint_devkit_1.AST_NODE_TYPES.CallExpression:case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(argument=>carries(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return carries(node.left,depth+1)||carries(node.right,depth+1);case eslint_devkit_1.AST_NODE_TYPES.ObjectExpression:return node.properties.some(property=>{if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property)return false;const value=property.value;if(isRequestSourced(value))return true;const key=property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.key.name:property.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal?String(property.key.value):"";return URL_OPTION_KEYS.has(key.toLowerCase())&&carries(value,depth+1)});default:return false}};return node=>carries(node,0)}function nodeContainsValidation(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="URL"){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&VALIDATION_FUNCTION_NAMES.has(node.callee.name)){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const method=node.callee.property.name;if(method==="includes"||method==="has"||method==="startsWith"||method==="test"||method==="some"){return true}}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&(node.operator==="==="||node.operator==="==")&&(node.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.left.property.name==="hostname"||node.left.property.name==="host")||node.right.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.right.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.right.property.name==="hostname"||node.right.property.name==="host"))){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement){return true}const SKIP_KEYS=new Set(["parent","range","loc","tokens","comments","start","end"]);for(const key of Object.keys(node)){if(SKIP_KEYS.has(key))continue;const value=node[key];if(value&&typeof value==="object"&&"type"in value){if(nodeContainsValidation(value))return true}if(Array.isArray(value)){for(const item of value){if(item&&typeof item==="object"&&"type"in item){if(nodeContainsValidation(item))return true}}}}return false}function hasValidationBefore(node){let current=node.parent;while(current){const parent=current.parent;if(!parent)break;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement||parent.type===eslint_devkit_1.AST_NODE_TYPES.Program){const body=parent.body;const idx=body.indexOf(current);for(let i=idx-1;i>=0&&i>=idx-10;i--){if(nodeContainsValidation(body[i])){return true}}}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement&&parent.test){if(nodeContainsValidation(parent.test)){return true}}current=parent}return false}exports.noSsrf=(0,eslint_devkit_1.createRule)({name:"no-ssrf",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-ssrf.md",description:"Flags HTTP calls whose URL argument is a user-input-named identifier or reads off a request object \u2014 a heuristic prompt for code review, not a proof of SSRF",cwe:"CWE-918",cvss:9.1},messages:{ssrfVulnerability:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Possible SSRF \u2014 heuristic (CWE-918)",cwe:"CWE-918",description:"HTTP call whose URL argument name suggests user input. This is a naming heuristic, not data-flow analysis \u2014 review whether the URL could originate from an untrusted source at runtime.",severity:"LOW",fix:"If the URL comes from user input, validate it against an allowlist of permitted hosts before making the request.",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:true},reportUnresolvedUrls:{type:"boolean",default:false,description:"Report a URL argument that is a user-input-named identifier with no traceable request source. Restores the pre-inversion naming heuristic."}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context,[options={}]){const{allowInTests=true,reportUnresolvedUrls=false}=options||{};const carriesUntrustedUrl=makeCarriesUntrustedUrl(context.sourceCode,reportUnresolvedUrls);const filename=context.filename;const isTestFile=allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSsrf=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const provenance_1=require("../../utils/provenance");const HTTP_CLIENT_FUNCTIONS=new Set(["fetch","got","nodeFetch","undici"]);const HTTP_CLIENT_METHODS=new Set(["get","post","put","patch","delete","head","options","request"]);const HTTP_CLIENT_OBJECTS=new Set(["axios","got","superagent","request","http","https","undici","needle"]);const VALIDATION_FUNCTION_NAMES=new Set(["validateUrl","validateURL","isValidUrl","isSafeUrl","isAllowed","isValidURL","checkUrl","checkURL","sanitizeUrl","sanitizeURL"]);const USER_INPUT_SUBSTRINGS=["url","endpoint","uri","href","link","target","dest","source","host","user","input","param"];function isUserInputParamName(name){const lower=name.toLowerCase();return USER_INPUT_SUBSTRINGS.some(sub=>lower.includes(sub))}const REQUEST_ROOT_NAMES=new Set(["req","request","ctx","event"]);const URL_OPTION_KEYS=new Set(["url","href","uri"]);function isRequestSourced(node){let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object}return current!==node&¤t.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&REQUEST_ROOT_NAMES.has(current.name.toLowerCase())}function makeCarriesUntrustedUrl(sourceCode,reportUnresolvedUrls){const carries=(node,depth)=>{if(depth>6)return false;const bare=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(bare!==node)return carries(bare,depth+1);switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const init=(0,provenance_1.bindingInit)(sourceCode,node);if(init!==void 0)return carries(init,depth+1);return reportUnresolvedUrls&&isUserInputParamName(node.name)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return isRequestSourced(node)||carries(node.object,depth+1);case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(expression=>carries(expression,depth+1));case eslint_devkit_1.AST_NODE_TYPES.CallExpression:case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(argument=>carries(argument,depth+1));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return carries(node.left,depth+1)||carries(node.right,depth+1);case eslint_devkit_1.AST_NODE_TYPES.ObjectExpression:return node.properties.some(property=>{if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property)return false;const value=property.value;if(isRequestSourced(value))return true;const key=property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.key.name:property.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal?String(property.key.value):"";return URL_OPTION_KEYS.has(key.toLowerCase())&&carries(value,depth+1)});default:return false}};return node=>carries(node,0)}function nodeContainsValidation(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="URL"){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&VALIDATION_FUNCTION_NAMES.has(node.callee.name)){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const method=node.callee.property.name;if(method==="includes"||method==="has"||method==="startsWith"||method==="test"||method==="some"){return true}}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&(node.operator==="==="||node.operator==="==")&&(node.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.left.property.name==="hostname"||node.left.property.name==="host")||node.right.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.right.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.right.property.name==="hostname"||node.right.property.name==="host"))){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement){return true}const SKIP_KEYS=new Set(["parent","range","loc","tokens","comments","start","end"]);for(const key of Object.keys(node)){if(SKIP_KEYS.has(key))continue;const value=node[key];if(value&&typeof value==="object"&&"type"in value){if(nodeContainsValidation(value))return true}if(Array.isArray(value)){for(const item of value){if(item&&typeof item==="object"&&"type"in item){if(nodeContainsValidation(item))return true}}}}return false}function hasValidationBefore(node){let current=node.parent;while(current){const parent=current.parent;if(!parent)break;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement||parent.type===eslint_devkit_1.AST_NODE_TYPES.Program){const body=parent.body;const idx=body.indexOf(current);for(let i=idx-1;i>=0&&i>=idx-10;i--){if(nodeContainsValidation(body[i])){return true}}}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement&&parent.test){if(nodeContainsValidation(parent.test)){return true}}current=parent}return false}exports.noSsrf=(0,eslint_devkit_1.createRule)({name:"no-ssrf",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-ssrf.md",description:"Flags HTTP calls whose URL argument is a user-input-named identifier or reads off a request object \u2014 a heuristic prompt for code review, not a proof of SSRF",cwe:"CWE-918",cvss:9.1},messages:{ssrfVulnerability:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Possible SSRF \u2014 heuristic (CWE-918)",cwe:"CWE-918",description:"HTTP call whose URL argument name suggests user input. This is a naming heuristic, not data-flow analysis \u2014 review whether the URL could originate from an untrusted source at runtime.",severity:"LOW",fix:"If the URL comes from user input, validate it against an allowlist of permitted hosts before making the request.",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:true},reportUnresolvedUrls:{type:"boolean",default:false,description:"Report a URL argument that is a user-input-named identifier with no traceable request source. Restores the pre-inversion naming heuristic."}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context,[options={}]){const{allowInTests=true,reportUnresolvedUrls=false}=options||{};const carriesUntrustedUrl=makeCarriesUntrustedUrl(context.sourceCode,reportUnresolvedUrls);const filename=context.filename;const isTestFile=allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename);if(isTestFile)return{};return{CallExpression(node){let isHttpCall=false;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&HTTP_CLIENT_FUNCTIONS.has(node.callee.name)){isHttpCall=true}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&HTTP_CLIENT_OBJECTS.has(node.callee.object.name)&&HTTP_CLIENT_METHODS.has(node.callee.property.name)){isHttpCall=true}if(!isHttpCall)return;const urlArg=node.arguments[0];if(!urlArg)return;if(hasValidationBefore(node)){return}if(!carriesUntrustedUrl(urlArg)){return}context.report({node,messageId:"ssrfVulnerability"})}}}});
|