UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

534 lines (498 loc) 29.3 kB
/** * @file Security pattern matcher * @description Matches code patterns against known security vulnerability signatures * This module implements the core vulnerability detection logic using regular expression * patterns that identify potentially dangerous code constructs. The pattern-based approach * enables fast static analysis across large codebases while providing configurable * detection rules that can be updated as new vulnerability patterns emerge. */ // Vulnerability detection patterns organized by security issue type // Rationale: Regular expressions provide fast static analysis without requiring full // AST parsing, enabling efficient scanning of large codebases. Each pattern targets // specific vulnerability indicators based on common insecure coding patterns. const VULNERABILITY_PATTERNS = { 'sql_injection': [ /['"][^'"]*SELECT[^'"]*['"]\s*\+/i, // SELECT statement with string concatenation /['"][^'"]*INSERT[^'"]*['"]\s*\+/i, // INSERT statement with string concatenation /['"][^'"]*UPDATE[^'"]*['"]\s*\+/i, // UPDATE statement with string concatenation /['"][^'"]*DELETE[^'"]*['"]\s*\+/i, // DELETE statement with string concatenation /\.query\([^)]*\+[^)]*\)(?!.*,\s*\[[^\]]*\])/i, // Database query with concatenation but no parameter array /\.execute\([^)]*\+[^)]*\)/i, // Execute method with concatenation /\.exec\([^)]*\+[^)]*\)/i, // Exec method with concatenation /sql\s*=\s*[^;]*\+/i, // SQL variable assignment with concatenation /query\s*=\s*[^;]*\+/i, // Query variable assignment with concatenation /=\s*['"][^'"]*SQL[^'"]*['"]\s*\+/i, // Any SQL assignment with concatenation /WHERE\s+[^=]*=\s*[^+]*\+(?!.*,\s*\[[^\]]*\])/i, // WHERE clause with concatenation but no parameterization /VALUES\s*\([^)]*\+[^)]*\)/i // VALUES clause with concatenation ], 'xss': [ /innerHTML\s*=\s*[^;]*[+]/i, // Dynamic HTML content creation with concatenation /innerHTML\s*=\s*[^;=]*[a-zA-Z_$][a-zA-Z0-9_$]*[^;]*$/m, // innerHTML assignment with variables (potential XSS) /innerHTML\s*=\s*(?:userInput|user[A-Z]\w*|input\w*|data\w*|content\w*)/i, // innerHTML with common user data variables /innerHTML\s*\+=\s*[^;]*$/m, // innerHTML concatenation assignment /document\.write\s*\(/i, // Direct DOM writing - classic XSS vector /outerHTML\s*=\s*[^;]*[+]/i, // Outer HTML manipulation /insertAdjacentHTML\s*\(/i, // Direct HTML insertion /\.html\([^)]*\+[^)]*\)/i // jQuery html() with concatenation ], 'code_injection': [ /(?<!['"`])eval\s*\(\s*(?:[a-zA-Z_$][a-zA-Z0-9_$.]*|.*req\.|.*user\.|.*input\.|.*param\.)/i, // eval() with actual variables, not in strings /\bnew\s+Function\s*\(\s*(?:[a-zA-Z_$][a-zA-Z0-9_$.]*|.*req\.|.*user\.|.*input\.|.*param\.)/i, // Constructor function with variables /setTimeout\s*\(\s*['"][^'"]*\+\s*(?:[a-zA-Z_$][a-zA-Z0-9_$.]*|.*req\.|.*user\.|.*input\.|.*param\.)/i, // setTimeout with actual user input concatenation /setTimeout\s*\(\s*(?:[a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:,|\))/i, // setTimeout with variable (potential code injection) /setInterval\s*\(\s*['"][^'"]*\+\s*(?:[a-zA-Z_$][a-zA-Z0-9_$.]*|.*req\.|.*user\.|.*input\.|.*param\.)/i, // setInterval with actual user input concatenation /setInterval\s*\(\s*(?:[a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:,|\))/i, // setInterval with variable (potential code injection) /execSync\s*\([^)]*\+.*(?:req\.|user\.|input\.|param\.)/i, // Synchronous command execution with user input /exec\s*\([^)]*\+.*(?:req\.|user\.|input\.|param\.)/i, // Command execution with user input concatenation /spawn\s*\([^)]*\+.*(?:req\.|user\.|input\.|param\.)/i, // Process spawning with user input concatenation /(?:^|[\s\[\(\{;,=])Function\s*\(\s*['"][^'"]*\+.*(?:req\.|user\.|input\.|param\.)[^'"]*['"][^)]*\)/i // Function constructor with actual user input ], 'eval_usage': [ /(?<!['"`])\beval\s*\(/i // Any eval() usage - even with static strings can be risky in security contexts ], 'path_traversal': [ /\.\.\/\.\.\//g, // Directory traversal patterns /\.\.\\\.\.\\/, // Windows directory traversal /['"][^'"]*\/[^'"]*['"]\s*\+\s*(?:req\.|user\.|input\.|param\.)[a-zA-Z_$][a-zA-Z0-9_$]*(?!.*path\.(basename|resolve|normalize))/i, // String concatenation with user input without safe path methods /\`[^\/`]*\/[^`]*\${[^}]*(?:req\.|user\.|input\.|param\.)[^}]*(?:filename|path|file|dir|directory)[^}]*}[^`]*\`/i, // Template literal with actual user input file path variables only /(?:readFile|writeFile|readFileSync|writeFileSync)\s*\([^)]*\+.*(?:req\.|user\.|input\.|param\.)/i, // File operations with user input concatenation /require\s*\([^)]*\+.*(?:req\.|user\.|input\.|param\.)[^)]*\)/i, // Dynamic require with user input concatenation /import\s*\([^)]*\+.*(?:req\.|user\.|input\.|param\.)/i, // Dynamic import with user input concatenation /fs\.[a-zA-Z]+\s*\([^)]*\+.*(?:req\.|user\.|input\.|param\.)/i, // File system operations with user input concatenation /path\.join\s*\([^)]*(?:req\.|user\.|input\.|param\.)/i, // Path joining with user input /sendFile\s*\([^)]*\+.*(?:req\.|user\.|input\.|param\.)/i // Express sendFile with user input concatenation ], 'insecure_random': [ /(?:token|key|secret|password|salt|nonce|id).*Math\.random/i, // Math.random for security purposes /(?:token|key|secret|password|salt|nonce|id).*Date\.now/i, // Date.now for security purposes /Math\.random.*(?:token|key|secret|password|salt|nonce|id)/i, // Security variable using Math.random /Date\.now.*(?:token|key|secret|password|salt|nonce|id)/i, // Security variable using Date.now /Math\.floor\s*\(\s*Math\.random.*(?:token|key|secret|password|salt|nonce|id)/i, // Math.random floor for security /parseInt\s*\(\s*Math\.random.*(?:token|key|secret|password|salt|nonce|id)/i // Parsed random for security ], 'hardcoded_secret': [ /password\s*[=:]\s*['"][^'"]{6,}/i, // Password literals in code /api[_-]?key\s*[=:]\s*['"][^'"]+/i, // API key assignments /secret\s*[=:]\s*['"][^'"]+/i, // Generic secret assignments /token\s*[=:]\s*['"][^'"]{20,}/i, // Authentication tokens /private[_-]?key\s*[=:]\s*['"][^'"]+/i, // Private key assignments /access[_-]?key\s*[=:]\s*['"][^'"]+/i, // Access key assignments /auth[_-]?token\s*[=:]\s*['"][^'"]+/i, // Auth token assignments /client[_-]?secret\s*[=:]\s*['"][^'"]+/i, // Client secret assignments /database[_-]?password\s*[=:]\s*['"][^'"]+/i, // Database password assignments /db[_-]?pass\s*[=:]\s*['"][^'"]+/i // Database password short form ], 'unsafe_parsing': [ /parseInt\s*\([^)]*(?:req\.|user\.|input\.|param\.)[^,)]+\)(?![^,)]*,)/i, // parseInt without radix on user input (security risk) /parseFloat\s*\([^)]*(?:req\.|user\.|input\.|param\.)/i, // parseFloat with request data /Number\s*\([^)]*(?:req\.|user\.|input\.|param\.)/i // Number constructor with request data ], 'unsafe_file_operations': [ /fs\.(readFile|writeFile|unlink|rmdir)\s*\([^)]*req\./i, // File operations with request data /fs\.(readFileSync|writeFileSync|unlinkSync|rmdirSync)\s*\([^)]*req\./i, // Sync file operations with request data /fs\.(createReadStream|createWriteStream)\s*\([^)]*req\./i, // Stream operations with request data /path\.join\s*\([^)]*req\./i, // Path joining with request data /\.\.\//g // Directory traversal attempts ], 'ssrf': [ /(?:http|https|ftp):\/\/.*\$\{[^}]*(?:req\.|user\.|input\.|param\.)/i, // URL with user input in template literal /fetch\s*\(\s*['"`][^'"`]*\$\{[^}]*(?:req\.|user\.|input\.|param\.)/i, // Fetch with user input URL /axios\.[a-z]+\s*\(\s*['"`][^'"`]*\$\{[^}]*(?:req\.|user\.|input\.|param\.)/i, // Axios with user input URL /request\s*\(\s*\{[^}]*url\s*:\s*[^,}]*(?:req\.|user\.|input\.|param\.)/i, // Request library with user URL /(?:http|https)\.(?:get|request)\s*\([^)]*(?:req\.|user\.|input\.|param\.)/i, // Node HTTP with user input /new\s+URL\s*\([^)]*(?:req\.|user\.|input\.|param\.)/i // URL constructor with user input ], 'nosql_injection': [ /\$where\s*:\s*['"`][^'"`]*\+/i, // MongoDB $where with concatenation /find\s*\(\s*\{[^}]*\$\{[^}]*(?:req\.|user\.|input\.|param\.)/i, // MongoDB find with user input /aggregate\s*\(\s*\[[^\]]*\$\{[^}]*(?:req\.|user\.|input\.|param\.)/i, // MongoDB aggregate with user input /\$regex\s*:\s*['"`][^'"`]*\+/i, // MongoDB regex with concatenation /\$ne\s*:\s*(?:req\.|user\.|input\.|param\.)/i, // MongoDB $ne with user input /findOne\s*\(\s*\{[^}]*(?:req\.|user\.|input\.|param\.)/i // MongoDB findOne with direct user input ], 'json_vulnerabilities': [ /JSON\.parse\s*\([^)]*(?:req\.|user\.|input\.|param\.)(?!.*try\s*\{)/i, // JSON.parse without try-catch /__proto__\s*[:=]/i, // Direct __proto__ manipulation /constructor\s*\[\s*['"`]prototype['"`]\s*\]/i, // Constructor prototype access /Object\.setPrototypeOf\s*\([^)]*(?:req\.|user\.|input\.|param\.)/i, // setPrototypeOf with user input /eval\s*\(\s*['"`]\(\s*\{[^}]*\}\s*\)['"`]\s*\)/i // JSON-like eval patterns ], 'weak_crypto': [ // All MD5/SHA1 usage - will be reclassified by context analysis /crypto\.createHash\s*\(\s*['"`](?:md5|sha1)['"`]/gi, // MD5/SHA1 usage (global flag to catch all instances) /\.createHash\s*\(\s*['"`](?:md5|sha1)['"`]/gi, // Alternative MD5/SHA1 pattern /new\s+MD5\s*\(/i, // MD5 constructor usage /hash\s*:\s*['"`](?:md5|sha1)['"`]/i, // Hash configuration with weak algorithms ], 'weak_crypto_other': [ /bcrypt\.hash\s*\([^,]*,\s*[1-5]\s*\)/i, // Weak bcrypt rounds (less than 6) /pbkdf2\s*\([^,]*,\s*[^,]*,\s*(?:[1-9]\d{0,2}|[1-9]\d{3})\s*,/i, // PBKDF2 with < 10000 iterations /scrypt\s*\([^,]*,\s*[^,]*,\s*\d+\s*,\s*\{[^}]*N\s*:\s*(?:[1-9]\d{0,2}|[1-9]\d{3})\s*\}/i // Scrypt with low N parameter ], 'weak_encoding': [ /btoa\s*\([^)]*(?:password|secret|key)/i, // Base64 encoding of secrets (not encryption) /Buffer\.from\s*\([^)]*(?:password|secret|key)[^)]*\)\.toString\s*\(\s*['"`]base64['"`]/i, // Buffer to base64 for secrets /Math\.random\s*\(\s*\)\s*\*\s*\d+/i // Math.random for any security-related purpose ], 'timing_attacks': [ /===.*password/i, // Direct password comparison /==.*password/i, // Loose password comparison /password\s*===\s*/i, // Password comparison pattern /if\s*\([^)]*password[^)]*===\s*[^)]*\)/i, // If statement with password comparison /String\.prototype\.localeCompare(?!.*constant)/i // localeCompare without constant time mention ] }; /** * Match vulnerability patterns against source code content * @param {string} content - Source code content to analyze for vulnerabilities * @param {Object|string} patternOrType - Either a pattern object or a vulnerability type string * @returns {Array<Object>} Array of vulnerability matches with location and confidence data * * Rationale: Performs comprehensive pattern matching across all known vulnerability types * to identify potential security issues. Returns structured match data that includes * confidence scoring to help analysts prioritize investigation efforts on high-confidence * matches while not missing lower-confidence potential issues. */ function matchPatterns(content, patternOrType) { const matches = []; // If called with a pattern object (legacy compatibility) if (typeof patternOrType === 'object' && patternOrType.regex) { const found = content.match(patternOrType.regex); if (found) { matches.push({ type: patternOrType.type || 'unknown', pattern: patternOrType.regex.toString(), match: found[0], line: getLineNumber(content, found.index || 0), confidence: calculateConfidence(patternOrType.regex, found[0]), severity: patternOrType.severity || 'MEDIUM', message: patternOrType.message || `${patternOrType.type} vulnerability detected` }); } return matches; } // Check all vulnerability patterns against the content Object.entries(VULNERABILITY_PATTERNS).forEach(([type, patterns]) => { patterns.forEach(pattern => { let match; const regex = new RegExp(pattern.source, pattern.flags); if (pattern.global) { // Handle global patterns that can have multiple matches let execResult; while ((execResult = regex.exec(content)) !== null) { // Skip matches that are in comments or documentation if (!isInCommentOrDocumentation(content, execResult.index)) { // Apply context-aware filtering for crypto patterns let shouldInclude = true; let adjustedType = type; let adjustedSeverity = getSeverityForType(type); if (type === 'weak_crypto') { const cryptoContext = analyzeCryptoContext(content, execResult.index); if (cryptoContext.isPerformance && !cryptoContext.isSecurityCritical) { // Performance context - flag as code smell with low severity adjustedType = 'weak_crypto_performance'; adjustedSeverity = 'LOW'; } else if (cryptoContext.isSecurityCritical) { // Clear security context - flag as high severity adjustedType = 'weak_crypto_security'; adjustedSeverity = 'HIGH'; } else { // Ambiguous context - flag as medium severity with original type adjustedSeverity = 'MEDIUM'; } } if (shouldInclude) { matches.push({ type: adjustedType, pattern: pattern.toString(), match: execResult[0], line: getLineNumber(content, execResult.index), confidence: calculateConfidence(pattern, execResult[0]), severity: adjustedSeverity, message: `${adjustedType.replace(/_/g, ' ')} vulnerability detected` }); } } } } else { // Handle non-global patterns match = content.match(pattern); if (match && !isInCommentOrDocumentation(content, match.index || 0)) { // Apply context-aware filtering for crypto patterns let shouldInclude = true; let adjustedType = type; let adjustedSeverity = getSeverityForType(type); if (type === 'weak_crypto') { const cryptoContext = analyzeCryptoContext(content, match.index || 0); if (cryptoContext.isPerformance && !cryptoContext.isSecurityCritical) { // Performance context - flag as code smell with low severity adjustedType = 'weak_crypto_performance'; adjustedSeverity = 'LOW'; } else if (cryptoContext.isSecurityCritical) { // Clear security context - flag as high severity adjustedType = 'weak_crypto_security'; adjustedSeverity = 'HIGH'; } else { // Ambiguous context - flag as medium severity with original type adjustedSeverity = 'MEDIUM'; } } if (shouldInclude) { matches.push({ type: adjustedType, pattern: pattern.toString(), match: match[0], line: getLineNumber(content, match.index || 0), confidence: calculateConfidence(pattern, match[0]), severity: adjustedSeverity, message: `${adjustedType.replace(/_/g, ' ')} vulnerability detected` }); } } } }); }); return matches; } /** * Get severity level for vulnerability type * @param {string} type - Vulnerability type * @returns {string} Severity level */ function getSeverityForType(type) { const severityMap = { 'sql_injection': 'HIGH', 'xss': 'HIGH', 'code_injection': 'HIGH', 'eval_usage': 'MEDIUM', 'path_traversal': 'HIGH', 'insecure_random': 'MEDIUM', 'hardcoded_secret': 'HIGH', 'unsafe_parsing': 'MEDIUM', 'unsafe_file_operations': 'HIGH', 'ssrf': 'HIGH', 'nosql_injection': 'HIGH', 'json_vulnerabilities': 'MEDIUM', 'weak_crypto': 'MEDIUM', // Default for crypto issues - will be adjusted by context 'weak_crypto_security': 'HIGH', // Security-critical weak crypto is high severity 'weak_crypto_performance': 'LOW', // Performance-related crypto is low severity 'weak_crypto_other': 'HIGH', // Other weak crypto (bcrypt, pbkdf2, scrypt) 'weak_encoding': 'MEDIUM', 'timing_attacks': 'MEDIUM' }; return severityMap[type] || 'MEDIUM'; } /** * Check if crypto usage is in a performance/utility context vs security context * @param {string} content - Full source code content * @param {number} index - Character index of the match * @returns {Object} Context analysis with isPerformance and isSecurityCritical flags */ function analyzeCryptoContext(content, index) { const contextRadius = 300; // Characters to examine around the match const contextStart = Math.max(0, index - contextRadius); const contextEnd = Math.min(content.length, index + contextRadius); const surroundingContext = content.substring(contextStart, contextEnd).toLowerCase(); // Performance/utility indicators - enhanced detection const performanceIndicators = [ /(?:content|block|duplicate|cache|checksum|etag|fingerprint|identifier)/, /(?:hash|comparison|group|optimize|performance)/, /(?:key|lookup|index|sort|search)/, /(?:fast|quick|efficient).*(?:comparison|hash|lookup)/, /(?:comparison|hash|lookup).*(?:fast|quick|efficient)/, /function.*(?:generate|create).*hash/, /generatehash.*(?:content|block|comparison)/, /\/\*.*(?:fast|performance|optimize|duplicate|content|comparison).*\*\//, /\/\/.*(?:fast|performance|optimize|duplicate|content|comparison)/, /for.*(?:comparison|duplicate|group)/, /single responsibility.*hash.*(?:comparison|content)/, /description.*(?:fast|performance).*(?:comparison|hash)/, // Enhanced patterns for non-security contexts /(?:wet.*code|ast.*block|deduplication|file.*comparison)/, /(?:content.*matching|syntax.*tree|code.*analysis)/, /(?:diff|patch|merge|version.*control)/ ]; // Security-critical indicators const securityIndicators = [ /(?:password|secret|auth|token|sign|verify|salt|nonce|session)/, /(?:login|credential|key.*derivation|hmac|signature)/, /(?:encrypt|decrypt|secure|protect|validate)/, /function.*(?:hash|secure|auth|verify)/, /\/\*.*(?:security|crypto|auth|password).*\*\//, /\/\/.*(?:security|crypto|auth|password)/ ]; const isPerformance = performanceIndicators.some(pattern => pattern.test(surroundingContext)); const isSecurityCritical = securityIndicators.some(pattern => pattern.test(surroundingContext)); // Enhanced logic: If clearly performance-oriented and no security indicators, it's not security-critical const finalIsSecurityCritical = isSecurityCritical && !isPerformance; return { isPerformance, isSecurityCritical: finalIsSecurityCritical }; } /** * Check if a match is in a comment or documentation context * @param {string} content - Full source code content * @param {number} index - Character index of the match * @returns {boolean} True if match is in comment/documentation context */ function isInCommentOrDocumentation(content, index) { const lines = content.split('\n'); const lineNumber = getLineNumber(content, index); const currentLine = lines[lineNumber - 1] || ''; // Check if match is in a single-line comment const beforeMatch = content.substring(0, index); const lineStart = beforeMatch.lastIndexOf('\n') + 1; const lineBeforeMatch = content.substring(lineStart, index); // Check for various comment patterns and display contexts const commentPatterns = [ /^\s*\/\//, // Single-line comment /^\s*\*/, // Multi-line comment line /\/\*.*?\*\//, // Inline multi-line comment /description:\s*['"]/, // Property description /flag:\s*['"]/, // CLI flag help /['"]\w+['"]\s*:\s*['"].*\b(eval|innerHTML|document\.write|SELECT|INSERT|UPDATE|DELETE)\b.*['"]/, // Remediation advice strings /remediation.*:\s*['"]/, // Remediation property strings /mitigation.*:\s*['"]/, // Mitigation advice strings /['"].*Avoid\s+eval\(\).*['"]/, // Remediation text about eval /['"].*Instead\s+of:.*eval.*['"]/, // Example code in remediation /['"].*Use:.*['"].*eval.*['"]/, // Alternative code examples /EVAL_USAGE['"]:\s*['"]/, // Pattern for remediation suggestions object /suggestions\[.*\]\s*\|\|/, // Default suggestion pattern ]; // Check for template literal display contexts (not actual code execution) const displayContextPatterns = [ /\$\{[^}]*\|\|[^}]*['"][^'"]*['"][^}]*\}/, // Template literal with fallback text like ${x || 'default'} /\$\{[^}]*\?\s*[^:]*:\s*['"][^'"]*['"][^}]*\}/, // Ternary in template literal /rec\.\w+\s*\|\|/, // Object property with fallback /issue\.\w+\s*\|\|/, // Issue property with fallback /return\s*`[^`]*\$\{[^}]*\}[^`]*`/, // Template literal in return statement (likely for display) /\$\{i\s*\+\s*\d+\}/, // Template literal with index counter (display formatting) /\$\{\w+Icon\}/, // Template literal with icon variables (display formatting) /\$\{\w+\.(type|summary|recommendation|effort|impact)\}/, // Template literal with object property access (display) /console\.(log|info|warn|error)\s*\([^)]*`[^`]*\$\{[^}]*\}[^`]*`/, // Console logging with template literals /`\s*\$\{i\s*\+\s*\d+\}\.[^`]*\$\{[^}]*\}[^`]*`/, // Template literals with numbered formatting /require\s*\(\s*['"][^'"]*\.\.[^'"]*['"][^)]*\)/, // Static require with relative paths /const\s+.*=\s+require\s*\(\s*['"][^'"]*\.\.[^'"]*['"][^)]*\)/, // Const assignment with static require /from\s+['"][^'"]*\.\.[^'"]*['"]/, // ES6 import from relative paths /\.query\s*\(\s*['"][^'"]*\?\s*[^'"]*['"]\s*,\s*\[/, // Parameterized query with ? placeholder /WHERE\s+[^=]*=\s*\?\s*['"]/, // WHERE clause with ? parameter placeholder /path\.(basename|resolve|normalize|relative|extname)\s*\([^)]*\)/, // Safe path operations /\+\s*path\.(basename|resolve|normalize|relative|extname)/, // String concatenation with safe path methods /this\.startTime\s*=\s*Date\.now/, // Performance timing /startTime\s*=\s*Date\.now/, // Timing variables /Date\.now\(\)\s*-\s*\w+/, // Time calculations /\w+\s*-\s*Date\.now\(\)/, // Time differences /default:\s*Date\.now/, // Mongoose/Schema defaults /`[^`]*\/\/\s*In\s+\$\{[^}]*\}[^`]*:/, // Code generation comments like "// In ${block.file}:" /`[^`]*\/\/\s*In\s+\$\{[^}]*\.file\}[^`]*:/, // Specific pattern for generateUsageExamples /require\s*\(\s*['\"]\$\{[^}]*\}['\"]\s*\)/, // Template literal in generated require statements /path\.relative\([^)]*\)/, // Safe path.relative operations /relativePath\.startsWith/ // Safe path manipulation checks ]; const isDisplayContext = displayContextPatterns.some(pattern => { const lineStart = content.lastIndexOf('\n', index - 1) + 1; const lineEnd = content.indexOf('\n', index); const fullLine = content.substring(lineStart, lineEnd === -1 ? content.length : lineEnd); return pattern.test(fullLine); }); // Check for remediation/suggestions context - look for broader context around the match const isInRemediationContext = (() => { const contextRadius = 200; // Characters to look before and after const contextStart = Math.max(0, index - contextRadius); const contextEnd = Math.min(content.length, index + contextRadius); const surroundingContext = content.substring(contextStart, contextEnd); // Patterns that indicate we're in remediation/suggestion content const remediationContextPatterns = [ /const\s+suggestions\s*=\s*\{/, // suggestions object definition /function\s+getRemediationSuggestion/, // remediation function /suggestions\[['"]\w+['"]\]/, // accessing suggestions object /remediation.*:\s*['"]/, // remediation property /EVAL_USAGE['"]:\s*['"]/, // specific pattern name /Instead\s+of:.*eval/, // example in remediation text /Avoid\s+eval\(\)/, // remediation advice text /suggestions\[\w+\.type\]/, // accessing suggestion by type ]; return remediationContextPatterns.some(pattern => pattern.test(surroundingContext)); })(); // Check if the line starts with a comment (not just contains a comment after code) const lineStartsWithComment = /^\s*\/\//.test(currentLine); const isInMultilineComment = beforeMatch.includes('/*') && !beforeMatch.includes('*/'); return commentPatterns.some(pattern => pattern.test(currentLine)) || lineStartsWithComment || isInMultilineComment || isDisplayContext || isInRemediationContext; } /** * Calculate line number from character index in source content * @param {string} content - Full source code content * @param {number} index - Character index of the match within the content * @returns {number} Line number (1-based) where the match occurs * * Rationale: Provides precise location information for vulnerability matches, enabling * developers to quickly locate and fix identified issues. Line numbers are essential * for actionable security reports and integration with development tools. */ function getLineNumber(content, index) { return content.substring(0, index).split('\n').length; // Count newlines before match position } /** * Calculate confidence score for a pattern match based on specificity and context * @param {RegExp} pattern - The regular expression pattern that matched * @param {string} match - The actual text that was matched * @returns {number} Confidence score between 0.0 and 1.0 * * Rationale: Confidence scoring helps analysts prioritize investigation of matches. * Longer matches and more specific patterns typically indicate higher likelihood of * actual vulnerabilities rather than false positives. This reduces noise in security * reports and focuses attention on the most likely real issues. */ function calculateConfidence(pattern, match) { const baseConfidence = 0.5; // Starting confidence for any pattern match const lengthBonus = Math.min(match.length / 50, 0.3); // Longer matches are more specific const specificityBonus = pattern.source.includes('\\b') ? 0.2 : 0; // Word boundaries indicate more precise patterns // Cap confidence at 1.0 and combine all factors return Math.min(baseConfidence + lengthBonus + specificityBonus, 1.0); } /** * Add custom vulnerability detection pattern for project-specific security rules * @param {string} type - Vulnerability type category for the new pattern * @param {RegExp} pattern - Regular expression pattern to detect this vulnerability * * Rationale: Enables extensibility for organization-specific security requirements * or emerging vulnerability patterns not covered by the default rule set. This * allows the security scanner to evolve with changing threat landscapes and * custom security policies without requiring core system modifications. */ function addCustomPattern(type, pattern) { if (!VULNERABILITY_PATTERNS[type]) { VULNERABILITY_PATTERNS[type] = []; // Initialize new vulnerability type if needed } VULNERABILITY_PATTERNS[type].push(pattern); // Add pattern to existing type collection } /** * Generate contextual vulnerability messages based on type and context * @param {string} type - Vulnerability type * @param {string} match - The matched code * @param {Object} context - Context analysis results * @returns {string} Contextual vulnerability message */ function generateContextualMessage(type, match, context = {}) { const messages = { 'weak_crypto_security': 'Weak cryptographic hash (MD5/SHA1) detected in security-critical context. Use SHA-256, SHA-3, or bcrypt for secure operations.', 'weak_crypto': context?.isPerformance ? 'MD5/SHA1 hash detected in performance context. Consider documenting security implications.' : 'Weak cryptographic hash (MD5/SHA1) detected. Evaluate security requirements and consider stronger alternatives.', 'sql_injection': 'SQL injection vulnerability detected. Use parameterized queries or prepared statements.', 'xss': 'Cross-site scripting (XSS) vulnerability detected. Sanitize user input and use CSP.', 'hardcoded_secret': 'Hard-coded secret detected. Move to environment variables or secure vault.', 'timing_attacks': 'Timing attack vulnerability detected. Use constant-time comparison functions.', 'path_traversal': 'Path traversal vulnerability detected. Validate and sanitize file paths.', 'ssrf': 'Server-side request forgery (SSRF) vulnerability detected. Validate URLs and use allowlists.' }; return messages[type] || `${type.replace(/_/g, ' ')} vulnerability detected`; } module.exports = { matchPatterns, addCustomPattern, calculateConfidence, generateContextualMessage };