UNPKG

agentsqripts

Version:

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

450 lines (398 loc) 18.1 kB
/** * @file Vulnerability scanner * @description Main scanner for detecting security vulnerabilities in content */ // Remove unused import since we're using patternMatcher directly const { analyzeWithAST } = require('./astAnalyzer'); const { detectModernVulnerabilities } = require('./modernPatternDetector'); const { detectFrameworkSecurity } = require('./frameworkDetector'); const { generateContextualMessage } = require('./patternMatcher'); /** * Check if a potential vulnerability is actually a test case, pattern definition, or documentation example * @param {string} line - The line of code containing the potential vulnerability * @param {string} content - Full file content for context * @param {string} filePath - File path for additional context * @returns {boolean} True if this appears to be a test case, pattern, or example - not real vulnerability */ function isPatternDefinition(line, content, filePath = '') { // Enhanced test, demo, and debug file detection const isTestFile = filePath.includes('.test.js') || filePath.includes('.spec.js') || filePath.includes('/test/') || filePath.includes('/tests/') || filePath.includes('temp-') || filePath.includes('test-') || filePath.includes('debug') || filePath.includes('demo') || filePath.includes('/demo/') || filePath.includes('sample') || filePath.includes('example') || content.includes('qtests') || content.includes('mocha') || content.includes('jest') || content.includes('@file Unit tests') || content.includes('createAssertions') || content.includes('debugging') || content.includes('demonstration'); // Test case indicators - stronger detection for test scenarios const testIndicators = [ // Common test patterns /\/\/.*test|\/\/.*example|\/\/.*vulnerability.*test/i, /\/\/.*Security.*test|\/\/.*XSS.*vulnerability|\/\/.*SQL.*injection.*vulnerability/i, /\/\/.*Another.*vulnerability|\/\/.*injection.*pattern/i, // Test content creation /testContent|testCode|xssCode|sqlCode|tempFile.*=|temp.*test/i, /writeFileSync.*temp|createTemporary|Security.*test.*content/i, // Test function contexts /function.*test|it\(|describe\(|test\(/, // Test assertions and descriptions /assert\.|expect\(|should.*detect|vulnerability.*detection/i, ]; // Common pattern definition indicators (existing) const patternIndicators = [ // Pattern object definitions /pattern:\s*\//, // pattern: /regex/ /regex:\s*\//, // regex: /pattern/ // Variable names that suggest this is a pattern definition /vulnerableRegexPatterns|securityPatterns|PATTERNS|pattern.*=.*\/|regex.*=.*\//, // Comments indicating this is a detection pattern /\/\/.*pattern|\/\/.*detection|\/\/.*regex.*vulnerable/i, // Array of patterns /\[\s*\/.*\/.*,/, ]; // Enhanced context indicators const contextIndicators = [ // File contains pattern definitions content.includes('pattern definitions') || content.includes('Pattern Constants'), content.includes('vulnerableRegexPatterns') || content.includes('PATTERNS ='), content.includes('detectReDoS') || content.includes('detectVulnerabilities'), // Line is in an array or object literal for patterns line.includes('pattern:') || line.includes('severity:'), line.trim().startsWith('//') && line.includes('pattern'), // Documentation examples line.includes('// Instead of:') || line.includes('// Use:'), // Test-specific context content.includes('Unit tests') || content.includes('Test suite'), content.includes('temporary test file') || content.includes('Create temporary'), ]; // Check surrounding lines for test context (look 2 lines above and below) const lines = content.split('\n'); const currentLineIndex = lines.findIndex(l => l.includes(line.trim())); if (currentLineIndex !== -1) { const surroundingLines = lines.slice(Math.max(0, currentLineIndex - 2), currentLineIndex + 3); const surroundingContext = surroundingLines.join('\n'); const surroundingTestPatterns = [ /\/\/.*test|\/\/.*example|\/\/.*vulnerability/i, /writeFileSync|temp.*File|test.*Content/i, /Security.*vulnerability|XSS.*vulnerability|SQL.*injection/i, ]; if (surroundingTestPatterns.some(pattern => pattern.test(surroundingContext))) { return true; } } // If it's a test file, be more lenient about flagging as test content // Check for test-specific context patterns if (isTestFile) { // Strong test indicators that suggest this is intentional test code const strongTestIndicators = [ /\/\/.*XSS.*vulnerability|\/\/.*Another.*XSS|\/\/.*SQL.*injection/i, /\/\/.*vulnerability.*-.*direct|\/\/.*dangerous|\/\/.*pattern/i, /tempFile|xssCode|sqlCode|testCode|renderUserContent/i, /writeFileSync.*temp|createTemp/i, ]; // Check surrounding context for test patterns const hasStrongTestContext = strongTestIndicators.some(indicator => indicator.test(line) || indicator.test(content) ); if (hasStrongTestContext) { return true; // This is definitely test content } // Fallback to existing logic for weaker indicators return (testIndicators.some(indicator => indicator.test(line)) || patternIndicators.some(indicator => indicator.test(line))) && (line.includes('test') || line.includes('spec') || line.includes('mock') || line.includes('expect') || line.includes('assert') || line.includes('describe')); } // For non-test files, use stricter criteria return patternIndicators.some(indicator => indicator.test(line)) || contextIndicators.some(indicator => indicator); } /** * Scans content for security vulnerabilities * @param {string} content - File content * @param {string} language - Programming language * @param {string} filePath - File path for enhanced output * @returns {Object} Object containing vulnerabilities and framework findings */ function scanForVulnerabilities(content, language, filePath = '') { const vulnerabilities = []; const { matchPatterns } = require('./patternMatcher'); // Traditional pattern-based scanning - call once to avoid duplication const patternMatches = matchPatterns(content); // Filter out pattern definitions, test cases, and documentation examples const lines = content.split('\n'); const filteredMatches = patternMatches.filter(m => { const lineNumber = m.line || 1; const line = lines[lineNumber - 1] || ''; return !isPatternDefinition(line, content, filePath); }); // Deduplicate matches from overlapping patterns const deduplicatedMatches = deduplicateVulnerabilities(filteredMatches); // Check if this is a test/demo/debug file for confidence adjustment const isTestOrDemoFile = filePath.includes('.test.js') || filePath.includes('.spec.js') || filePath.includes('/test/') || filePath.includes('/tests/') || filePath.includes('temp-') || filePath.includes('test-') || filePath.includes('debug') || filePath.includes('demo') || filePath.includes('/demo/') || filePath.includes('sample') || filePath.includes('example'); vulnerabilities.push(...deduplicatedMatches.map(m => { let adjustedConfidence = m.confidence || 'medium'; let contextTags = []; let severityAdjustment = ''; // Reduce confidence for vulnerabilities in test/demo files that slipped through if (isTestOrDemoFile && adjustedConfidence === 'high') { adjustedConfidence = 'medium'; contextTags.push('test_or_demo_file'); } else if (isTestOrDemoFile && adjustedConfidence === 'medium') { adjustedConfidence = 'low'; contextTags.push('test_or_demo_file'); } // Enhanced context detection for dogfood mode const lineNumber = m.line || 1; const line = lines[lineNumber - 1] || ''; // Check if this is intentional test code if (isTestOrDemoFile) { const testPatterns = [ /\/\/.*test|\/\/.*example|\/\/.*vulnerability/i, /writeFileSync|temp.*File|test.*Content|xssCode|sqlCode/i, /Security.*vulnerability|XSS.*vulnerability|SQL.*injection/i, /buggyCode.*=|testFile.*=/i ]; if (testPatterns.some(pattern => pattern.test(line)) || testPatterns.some(pattern => pattern.test(content.substring(Math.max(0, content.indexOf(line) - 200), content.indexOf(line) + 200)))) { contextTags.push('intentional_test_code'); adjustedConfidence = 'low'; severityAdjustment = '(likely false positive - test code)'; } } // Special handling for specific vulnerability types if (m.type === 'weak_crypto' || m.type === 'weak_crypto_performance' || m.message?.includes('MD5') || line.includes('md5')) { // MD5 for code deduplication is a code smell, not a security vulnerability const md5Context = content.substring(Math.max(0, content.indexOf(line) - 100), content.indexOf(line) + 100); if (md5Context.includes('duplicate') || md5Context.includes('hash') || md5Context.includes('similarity') || md5Context.includes('grouping') || md5Context.includes('deduplication')) { contextTags.push('code_deduplication_usage'); severityAdjustment = '(code smell - MD5 for non-cryptographic use)'; adjustedConfidence = 'low'; } } return { ...m, name: m.type || 'Unknown Vulnerability', confidence: adjustedConfidence, contextTags, severityNote: severityAdjustment, description: m.message || `${m.type.replace(/_/g, ' ')} vulnerability detected`, file: filePath, category: getCategoryForType(m.type), remediation: getRemediationForType(m.type), cweReference: getCWEReference(m.type), code: m.match }; })); // AST-based analysis for JavaScript/TypeScript if (language === 'javascript' || language === 'typescript') { try { const astVulns = analyzeWithAST(content); vulnerabilities.push(...astVulns.map(v => ({ name: v.type, type: v.type, severity: v.confidence === 'high' ? 'HIGH' : 'MEDIUM', category: getCategoryForType(v.type) || 'Code Analysis', description: v.message, remediation: getRemediationForType(v.type), cweReference: getCWEReference(v.type), code: v.code || 'AST-based detection', line: v.line, confidence: v.confidence, file: filePath }))); } catch (error) { const quiet = process.env.QUIET_LOGS === '1' || process.env.ANALYSIS_OUTPUT === 'json'; if (!quiet) { console.error('AST analysis error:', error.message); } } } // Modern vulnerability detection try { const modernVulns = detectModernVulnerabilities(content); // Filter out pattern definitions from modern vulnerability detection const filteredModernVulns = modernVulns.filter(v => { const lineNumber = v.line || 1; const line = lines[lineNumber - 1] || ''; return !isPatternDefinition(line, content); }); vulnerabilities.push(...filteredModernVulns.map(v => ({ ...v, name: v.name || v.type || 'Modern Vulnerability', description: v.description || v.message || `${(v.type || 'security').replace(/_/g, ' ')} vulnerability detected`, file: filePath, category: v.category || getCategoryForType(v.type), remediation: v.remediation || getRemediationForType(v.type), code: v.code || v.match }))); } catch (error) { const quiet = process.env.QUIET_LOGS === '1' || process.env.ANALYSIS_OUTPUT === 'json'; if (!quiet) { console.error('Modern pattern detection error:', error.message); } } // Framework security analysis let frameworkFindings = {}; try { frameworkFindings = detectFrameworkSecurity(content); } catch (error) { const quiet = process.env.QUIET_LOGS === '1' || process.env.ANALYSIS_OUTPUT === 'json'; if (!quiet) { console.error('Framework detection error:', error.message); } } // Deduplicate overlapping vulnerabilities const deduplicatedVulns = deduplicateVulnerabilities(vulnerabilities); return { vulnerabilities: deduplicatedVulns, frameworkFindings }; } /** * Get CWE (Common Weakness Enumeration) reference for vulnerability type * @param {string} type - Vulnerability type * @returns {string} CWE reference */ function getCWEReference(type) { const cweMap = { 'sql_injection': 'CWE-89', 'xss': 'CWE-79', 'code_injection': 'CWE-94', 'path_traversal': 'CWE-22', 'insecure_random': 'CWE-338', 'hardcoded_secret': 'CWE-798', 'ssrf': 'CWE-918', 'nosql_injection': 'CWE-943', 'json_vulnerabilities': 'CWE-502', 'weak_crypto': 'CWE-327', 'weak_crypto_security': 'CWE-327', 'timing_attacks': 'CWE-208', 'eval_usage': 'CWE-94', 'unsafe_parsing': 'CWE-20', 'unsafe_file_operations': 'CWE-22', 'weak_crypto_other': 'CWE-326', 'weak_encoding': 'CWE-327' }; return cweMap[type] || 'CWE-Other'; } /** * Get category for vulnerability type * @param {string} type - Vulnerability type * @returns {string} Category name */ function getCategoryForType(type) { const categoryMap = { 'sql_injection': 'Injection', 'xss': 'Cross-Site Scripting', 'code_injection': 'Code Injection', 'path_traversal': 'Path Traversal', 'insecure_random': 'Cryptography', 'hardcoded_secret': 'Secrets Management', 'ssrf': 'Server-Side Request Forgery', 'nosql_injection': 'NoSQL Injection', 'json_vulnerabilities': 'JSON Security', 'weak_crypto': 'Weak Cryptography', 'weak_crypto_security': 'Weak Cryptography', 'timing_attacks': 'Timing Attacks' }; return categoryMap[type] || 'Security'; } /** * Get remediation advice for vulnerability type * @param {string} type - Vulnerability type * @returns {string} Remediation advice */ function getRemediationForType(type) { const remediationMap = { 'sql_injection': 'Use parameterized queries or prepared statements instead of string concatenation', 'xss': 'Sanitize user input and use safe DOM manipulation methods', 'code_injection': 'Avoid eval() and dynamic code execution, validate all inputs', 'path_traversal': 'Validate file paths and use path.resolve() to prevent directory traversal', 'weak_crypto_security': 'Replace MD5/SHA1 with SHA-256, SHA-3, or bcrypt for security-critical operations', 'weak_crypto': 'Evaluate if cryptographic security is required; use stronger hash functions for security contexts', 'timing_attacks': 'Use crypto.timingSafeEqual() for password/token comparisons to prevent timing attacks', 'insecure_random': 'Use cryptographically secure random number generators like crypto.randomBytes()', 'hardcoded_secret': 'Move secrets to environment variables or secure secret management systems', 'ssrf': 'Validate and restrict URLs, use allow-lists for external requests', 'nosql_injection': 'Use parameterized queries and avoid string concatenation in NoSQL queries', 'json_vulnerabilities': 'Use safe JSON parsing with proper error handling and avoid __proto__ manipulation', 'weak_crypto': 'Use strong cryptographic algorithms like SHA-256 or better instead of MD5/SHA1', 'timing_attacks': 'Use constant-time comparison functions for password and secret validation' }; return remediationMap[type] || 'Review and fix the identified security issue'; } /** * Deduplicate overlapping vulnerabilities based on location and match * @param {Array} vulnerabilities - Array of vulnerability objects * @returns {Array} Deduplicated vulnerability array */ function deduplicateVulnerabilities(vulnerabilities) { const seen = new Map(); const deduplicated = []; for (const vuln of vulnerabilities) { // Enhanced deduplication key - consider line number and vulnerability type // For overlapping patterns (like crypto.createHash vs .createHash), group by line+type const key = `${vuln.line}-${vuln.type}`; const existing = seen.get(key); if (!existing) { // First time seeing this location/type combination seen.set(key, vuln); deduplicated.push(vuln); } else { // Merge with existing if this one has higher severity or longer match const newSeverity = getSeverityScore(vuln.severity || 'MEDIUM'); const existingSeverity = getSeverityScore(existing.severity || 'MEDIUM'); const newMatchLength = (vuln.match || '').length; const existingMatchLength = (existing.match || '').length; if (newSeverity > existingSeverity || (newSeverity === existingSeverity && newMatchLength > existingMatchLength)) { // Replace with higher severity or more complete finding const index = deduplicated.findIndex(v => v === existing); if (index >= 0) { deduplicated[index] = vuln; seen.set(key, vuln); } } } } return deduplicated; } /** * Convert severity to numeric score for comparison * @param {string} severity - Severity level * @returns {number} Numeric severity score */ function getSeverityScore(severity) { const scores = { 'LOW': 1, 'MEDIUM': 2, 'HIGH': 3, 'CRITICAL': 4 }; return scores[severity] || 0; } module.exports = { scanForVulnerabilities };