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
JavaScript
/**
* @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
};