UNPKG

agentsqripts

Version:

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

616 lines (536 loc) • 25.1 kB
#!/usr/bin/env node /** * @file Command-line interface for security vulnerability analysis and risk assessment * @description Single responsibility: Provide interactive CLI for identifying security vulnerabilities * * This CLI tool serves as the security analysis interface for the AgentSqripts platform, * enabling detection of security vulnerabilities, attack vectors, and potential risks * in codebases. It implements configurable severity filtering, confidence thresholds, * and multiple output formats to support security auditing workflows and CI/CD integration. * * Design rationale: * - Security-first CLI design enables integration with security scanning pipelines * - Severity and confidence filtering allows noise reduction in security reports * - Multiple output formats support different security workflow requirements * - Language detection automates analysis without manual configuration * - Comprehensive help documentation reduces security scanning adoption barriers */ const path = require('path'); const fs = require('fs'); const { promises: fsPromises } = require('fs'); const { analyzeSecurityVulnerabilities } = require('../lib/security-vulns/analyzeSecurityVulns'); const { analyzeProjectSecurity } = require('../lib/security-vulns/projectSecurityAnalyzer'); // Removed qerrors dependency for qtests compatibility // const qerrors = require('qerrors'); const { createHelpFunction } = require('./lib/helpFormatter'); const { parseArgs: sharedParseArgs } = require('./lib/argumentParserV2'); const { handleAnalysisError, exitOnSeverityThreshold } = require('./lib/errorHandler'); const { outputJson: sharedOutputJson } = require('./lib/jsonOutputFormatter'); const showUsage = createHelpFunction({ command: 'analyze-security.js', description: 'Analyzes code for security vulnerabilities and potential attack vectors.', options: [ { flag: '--mode <mode>', description: 'Analysis mode: file, project (default: auto-detect)' }, { flag: '--language <lang>', description: 'Programming language (auto-detect if not provided)' }, { flag: '--severity <level>', description: 'Minimum severity to report: low, medium, high, critical (default: medium)' }, { flag: '--output-format <fmt>', description: 'Output format: json, summary, detailed (default: summary)' }, { flag: '--confidence <level>', description: 'Minimum confidence level: low, medium, high (default: high)' }, { flag: '--dogfood', description: 'Enable dogfooding mode - analyze test, demo, and internal files' }, { flag: '--help', description: 'Show this help message' } ], examples: [ 'node analyze-security.js src/auth/login.js', 'node analyze-security.js --mode project --severity high .', 'node analyze-security.js --output-format json --language python src/', 'node analyze-security.js --dogfood --severity low --confidence low .' ], output: ' The tool outputs security analysis results in the specified format.\n - summary: Human-readable summary of vulnerabilities and risk level\n - detailed: Detailed analysis with specific remediation advice\n - json: Machine-readable JSON output for AI agent consumption' }); async function main() { const { getProcessArgs } = require('../lib/utils/processHelpers'); const { shouldShowHelp } = require('../lib/utils/cliHelpers'); const args = getProcessArgs(); if (shouldShowHelp(args)) { showUsage(); process.exit(0); } try { // Parse command line arguments using shared parser const { options: parsedOptions, targetPath: parsedPath, error } = sharedParseArgs(args, { defaults: { mode: 'auto', language: null, severity: 'medium', outputFormat: 'summary', confidence: 'medium', dogfood: false }, flags: { mode: { type: 'string' }, language: { type: 'string' }, severity: { type: 'string' }, outputFormat: { type: 'string' }, confidence: { type: 'string' }, dogfood: { type: 'boolean' } } }); if (error) { const { logAnalysisError } = require('../lib/utils/errorMessages'); logAnalysisError('security', error); process.exit(1); } const options = parsedOptions; let targetPath = parsedPath; // Resolve target path targetPath = path.resolve(targetPath); // Auto-detect mode if not specified if (options.mode === 'auto') { const stats = await fsPromises.stat(targetPath); options.mode = stats.isFile() ? 'file' : 'project'; } // Configure quiet mode for JSON output to avoid polluting stdout const isJsonOutput = options.outputFormat === 'json'; if (isJsonOutput) { process.env.ANALYSIS_OUTPUT = 'json'; process.env.QUIET_LOGS = '1'; } if (!isJsonOutput) { console.log(`šŸ”’ Security analysis for: ${targetPath}`); console.log(`šŸŽÆ Mode: ${options.mode}`); console.log(`āš ļø Minimum severity: ${options.severity}`); console.log(`šŸŽ° Minimum confidence: ${options.confidence}`); if (options.dogfood) { console.log(`šŸ½ļø Dogfood mode: enabled (analyzing test/demo/internal files)`); } if (options.language) { console.log(`šŸ’» Language: ${options.language}`); } console.log(); } let results = {}; if (options.mode === 'file') { // Single file analysis results.fileAnalysis = await analyzeSecurityVulnerabilities(targetPath, options); } else { // Project analysis results.projectAnalysis = await analyzeProjectSecurity(targetPath, options); } // Filter results based on severity and confidence results = filterResults(results, options); // Output results based on format switch (options.outputFormat) { case 'json': outputJson(results, options); break; case 'detailed': outputDetailed(results, options); break; case 'summary': default: outputSummary(results, options); break; } // Set exit code based on findings const hasHighRiskIssues = checkForHighRiskIssues(results, options); process.exit(hasHighRiskIssues ? 1 : 0); } catch (error) { console.error(`Fatal error:`, error); // Log error using console since qerrors is optional for standalone operation console.error(`Error details:`, error.message); process.exit(1); } } function filterResults(results, options) { const severityOrder = { 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 }; const confidenceOrder = { 'low': 1, 'medium': 2, 'high': 3 }; const minSeverity = severityOrder[options.severity] || 2; const minConfidence = confidenceOrder[options.confidence] || 2; if (results.fileAnalysis) { // Filter vulnerabilities with optimized approach const filteredVulns = []; for (let i = 0; i < results.fileAnalysis.vulnerabilities.length; i++) { const vuln = results.fileAnalysis.vulnerabilities[i]; const severityLevel = severityOrder[typeof vuln.severity === 'string' ? vuln.severity.toLowerCase() : 'low'] || 0; const confidenceLevel = getConfidenceLevel(vuln.confidence, confidenceOrder); if (severityLevel >= minSeverity && confidenceLevel >= minConfidence) { filteredVulns.push(vuln); } } results.fileAnalysis.vulnerabilities = filteredVulns; } if (results.projectAnalysis) { // Transform file analyses without nested loops using efficient approach const filteredAnalyses = []; for (let i = 0; i < results.projectAnalysis.fileAnalyses.length; i++) { const fileAnalysis = results.projectAnalysis.fileAnalyses[i]; const filteredVulns = []; // Filter vulnerabilities in single pass for (let j = 0; j < fileAnalysis.vulnerabilities.length; j++) { const vuln = fileAnalysis.vulnerabilities[j]; const severityLevel = severityOrder[typeof vuln.severity === 'string' ? vuln.severity.toLowerCase() : 'low'] || 0; const confidenceLevel = getConfidenceLevel(vuln.confidence, confidenceOrder); if (severityLevel >= minSeverity && confidenceLevel >= minConfidence) { filteredVulns.push(vuln); } } filteredAnalyses.push({ ...fileAnalysis, vulnerabilities: filteredVulns }); } results.projectAnalysis.fileAnalyses = filteredAnalyses; // Recalculate top vulnerabilities efficiently const maxTop = 10; // Collect all vulnerabilities with file info without nested iterations const allVulns = []; for (let i = 0; i < results.projectAnalysis.fileAnalyses.length; i++) { const file = results.projectAnalysis.fileAnalyses[i]; // Add vulnerabilities efficiently using index-based iteration const vulns = file.vulnerabilities; for (let k = 0; k < vulns.length; k++) { allVulns.push({ ...vulns[k], file: file.file }); } } // Sort by severity and take top 10 results.projectAnalysis.topVulnerabilities = allVulns .sort((a, b) => { const severityA = severityOrder[a.severity.toLowerCase()] || 0; const severityB = severityOrder[b.severity.toLowerCase()] || 0; return severityB - severityA; }) .slice(0, maxTop); // Recalculate vulnerability breakdown and summary to keep JSON consistent const newBreakdown = { critical: 0, high: 0, medium: 0, low: 0 }; for (let i = 0; i < filteredAnalyses.length; i++) { const vulns = filteredAnalyses[i].vulnerabilities || []; for (let j = 0; j < vulns.length; j++) { const sev = (vulns[j].severity || 'LOW').toUpperCase(); if (sev === 'CRITICAL') newBreakdown.critical++; else if (sev === 'HIGH') newBreakdown.high++; else if (sev === 'MEDIUM') newBreakdown.medium++; else newBreakdown.low++; } } const totalFiltered = newBreakdown.critical + newBreakdown.high + newBreakdown.medium + newBreakdown.low; // Recompute project security score based on filtered results const filesCount = filteredAnalyses.length || 0; const vulnerabilityDensity = filesCount > 0 ? totalFiltered / filesCount : 0; let projectScore = 100; projectScore -= newBreakdown.critical * 15; projectScore -= newBreakdown.high * 8; projectScore -= Math.min(vulnerabilityDensity * 3, 20); const newScore = Math.max(0, Math.min(100, Math.round(projectScore))); if (results.projectAnalysis.vulnerabilityBreakdown) { results.projectAnalysis.vulnerabilityBreakdown = newBreakdown; } if (results.projectAnalysis.summary) { results.projectAnalysis.summary.totalVulnerabilities = totalFiltered; results.projectAnalysis.summary.criticalVulnerabilities = newBreakdown.critical; results.projectAnalysis.summary.highVulnerabilities = newBreakdown.high; // Re-derive overall risk from filtered results if possible const risk = newBreakdown.critical > 0 ? 'CRITICAL' : newBreakdown.high > 0 ? 'HIGH' : totalFiltered > 0 ? 'MEDIUM' : 'LOW'; results.projectAnalysis.summary.overallRisk = risk; results.projectAnalysis.overallRiskLevel = risk; results.projectAnalysis.summary.averageSecurityScore = newScore; } results.projectAnalysis.overallSecurityScore = newScore; } return results; } function checkForHighRiskIssues(results, options) { if (results.fileAnalysis) { const highRiskVulns = results.fileAnalysis.vulnerabilities.filter(vuln => ['HIGH', 'CRITICAL'].includes(vuln.severity.toUpperCase()) ); return highRiskVulns.length > 0 || results.fileAnalysis.riskLevel === 'CRITICAL'; } else if (results.projectAnalysis) { return results.projectAnalysis.overallRiskLevel === 'CRITICAL' || results.projectAnalysis.overallRiskLevel === 'HIGH' || results.projectAnalysis.vulnerabilityBreakdown.critical > 0; } return false; } /** * Convert confidence value to numeric level for filtering * @param {string|number} confidence - Confidence value * @param {Object} confidenceOrder - Confidence level mapping * @returns {number} Confidence level */ function getConfidenceLevel(confidence, confidenceOrder) { if (typeof confidence === 'string') { return confidenceOrder[confidence.toLowerCase()] || 0; } else if (typeof confidence === 'number') { // Convert numeric confidence (0.0-1.0) to confidence level if (confidence >= 0.8) return 3; // high if (confidence >= 0.6) return 2; // medium return 1; // low } return 0; } function outputJson(results, options) { sharedOutputJson(results, options, 'security'); } function outputDetailed(results, options) { if (results.fileAnalysis) { outputDetailedFile(results.fileAnalysis); } if (results.projectAnalysis) { outputDetailedProject(results.projectAnalysis); } } function outputDetailedFile(analysis) { console.log('šŸ”’ FILE SECURITY ANALYSIS\n'); console.log('=' .repeat(50)); console.log(`šŸ“„ File: ${analysis.file}`); console.log(`šŸ’» Language: ${analysis.language}`); console.log(`🚨 Risk Level: ${analysis.riskLevel}`); console.log(`šŸ›”ļø Security Score: ${analysis.securityScore}/100\n`); if (analysis.vulnerabilities.length > 0) { console.log(`āš ļø Vulnerabilities Found: ${analysis.vulnerabilities.length}\n`); // Group by category efficiently using index-based iteration const categories = {}; const vulnerabilities = analysis.vulnerabilities; for (let i = 0; i < vulnerabilities.length; i++) { const vuln = vulnerabilities[i]; const category = vuln.category || 'Other'; if (!categories[category]) { categories[category] = []; } categories[category].push(vuln); } // Process each category efficiently with index-based iteration for (const [category, vulnList] of Object.entries(categories)) { const output = [`šŸ“‚ ${category.toUpperCase()} (${vulnList.length} issues):`]; for (let i = 0; i < vulnList.length; i++) { const vuln = vulnList[i]; const name = vuln.name || vuln.type || 'Security Issue'; let description = vuln.description || vuln.message || `${vuln.type} vulnerability detected`; // Add context information for better analysis if (vuln.severityNote) { description += ` ${vuln.severityNote}`; } output.push(` ${i + 1}. [${vuln.severity}] ${name} (Line ${vuln.line})`); output.push(` Description: ${description}`); output.push(` Code: ${vuln.code}`); output.push(` Confidence: ${vuln.confidence}`); // Show context tags to help distinguish false positives from code smells if (vuln.contextTags && vuln.contextTags.length > 0) { const contextInfo = vuln.contextTags.map(tag => { switch(tag) { case 'intentional_test_code': return 'FALSE POSITIVE - Test Code'; case 'test_or_demo_file': return 'Test/Demo File'; case 'code_deduplication_usage': return 'CODE SMELL - Non-cryptographic usage'; default: return tag.replace(/_/g, ' '); } }).join(', '); output.push(` Context: ${contextInfo}`); } output.push(` Remediation: ${vuln.remediation}`); output.push(''); } console.log(output.join('\n')); } } else { console.log('āœ… No security vulnerabilities found with current filters'); } if (analysis.recommendations && analysis.recommendations.length > 0) { console.log('šŸ’” Security Recommendations:'); for (let i = 0; i < analysis.recommendations.length; i++) { const rec = analysis.recommendations[i]; console.log(` ${i + 1}. [${rec.priority}] ${rec.message}`); if (rec.remediation) { console.log(` Fix: ${rec.remediation}`); } if (rec.cweReference) { console.log(` Reference: ${rec.cweReference}`); } console.log(''); } } } function outputDetailedProject(analysis) { console.log('šŸ”’ PROJECT SECURITY ANALYSIS\n'); console.log('=' .repeat(50)); console.log(`🚨 Overall Risk Level: ${analysis.overallRiskLevel}`); console.log(`šŸ›”ļø Overall Security Score: ${analysis.overallSecurityScore}/100`); console.log(`šŸ“„ Files Analyzed: ${analysis.analyzedFiles}/${analysis.totalFiles}\n`); console.log('šŸ“Š Vulnerability Breakdown:'); console.log(` Critical: ${analysis.vulnerabilityBreakdown.critical}`); console.log(` High: ${analysis.vulnerabilityBreakdown.high}`); console.log(` Medium: ${analysis.vulnerabilityBreakdown.medium}`); console.log(` Low: ${analysis.vulnerabilityBreakdown.low}\n`); console.log('šŸ“‚ Category Breakdown:'); const categoryOutput = []; for (const category in analysis.categoryBreakdown) { const count = analysis.categoryBreakdown[category]; if (count > 0) { categoryOutput.push(` ${category.charAt(0).toUpperCase() + category.slice(1)}: ${count}`); } } console.log(categoryOutput.join('\n')); console.log(); if (analysis.topVulnerabilities.length > 0) { console.log('šŸ” Top Security Issues:'); const topFive = analysis.topVulnerabilities.slice(0, 5); for (let index = 0; index < topFive.length; index++) { const vuln = topFive[index]; console.log(` ${index + 1}. [${vuln.severity}] ${vuln.name}`); console.log(` File: ${path.basename(vuln.file)}:${vuln.line}`); console.log(` Description: ${vuln.description}`); console.log(` Remediation: ${vuln.remediation}\n`); } } if (analysis.recommendations && analysis.recommendations.length > 0) { console.log('šŸ’” Project Security Recommendations:'); for (let index = 0; index < analysis.recommendations.length; index++) { const rec = analysis.recommendations[index]; console.log(` ${index + 1}. [${rec.priority}] ${rec.title}`); console.log(` ${rec.description}`); console.log(` Action: ${rec.action}\n`); } } // Most vulnerable files const vulnerableFiles = analysis.fileAnalyses .filter(file => file.vulnerabilities.length > 0) .sort((a, b) => b.vulnerabilities.length - a.vulnerabilities.length) .slice(0, 5); if (vulnerableFiles.length > 0) { console.log('šŸ“„ Most Vulnerable Files:'); for (let index = 0; index < vulnerableFiles.length; index++) { const file = vulnerableFiles[index]; console.log(` ${index + 1}. ${path.basename(file.file)} (${file.vulnerabilities.length} issues, Risk: ${file.riskLevel})`); } console.log(); } } function outputSummary(results, options) { console.log('šŸ”’ SECURITY ANALYSIS SUMMARY\n'); if (results.fileAnalysis) { const analysis = results.fileAnalysis; console.log(`šŸ“„ File: ${path.basename(analysis.file)}`); console.log(`🚨 Risk Level: ${analysis.riskLevel}`); console.log(`šŸ›”ļø Security Score: ${analysis.securityScore}/100`); console.log(`āš ļø Vulnerabilities: ${analysis.vulnerabilities.length}`); if (analysis.vulnerabilities.length > 0) { // Count severities using reduce const severityCount = analysis.vulnerabilities.reduce((counts, vuln) => { counts[vuln.severity] = (counts[vuln.severity] || 0) + 1; return counts; }, {}); console.log('\nšŸ“Š Severity Breakdown:'); const severityOrder = { 'CRITICAL': 4, 'HIGH': 3, 'MEDIUM': 2, 'LOW': 1 }; const sortedSeverities = Object.entries(severityCount) .sort(([a], [b]) => (severityOrder[b] || 0) - (severityOrder[a] || 0)); for (let i = 0; i < sortedSeverities.length; i++) { const [severity, count] = sortedSeverities[i]; console.log(` ${severity}: ${count}`); } // Display critical/high vulnerabilities with optimized approach const criticalVulns = []; const highVulns = []; // Separate collection passes efficiently for (let i = 0; i < analysis.vulnerabilities.length; i++) { const vuln = analysis.vulnerabilities[i]; if (vuln.severity === 'CRITICAL' && criticalVulns.length < 3) { criticalVulns.push(vuln); } else if (vuln.severity === 'HIGH' && highVulns.length < 5) { highVulns.push(vuln); } } if (criticalVulns.length === 0) { analysis.vulnerabilities.forEach(vuln => { if (vuln.severity === 'HIGH' && highVulns.length < 3) { highVulns.push(vuln); } }); } if (criticalVulns.length > 0) { console.log('\n🚨 Critical Issues:'); criticalVulns.forEach(vuln => { console.log(` - ${vuln.name} (Line ${vuln.line})`); }); } else if (highVulns.length > 0) { console.log('\nāš ļø High Priority Issues:'); highVulns.forEach(vuln => { console.log(` - ${vuln.name} (Line ${vuln.line})`); }); } } } if (results.projectAnalysis) { const analysis = results.projectAnalysis; console.log(`🚨 Overall Risk: ${analysis.overallRiskLevel}`); console.log(`šŸ›”ļø Security Score: ${analysis.overallSecurityScore}/100`); console.log(`šŸ“„ Files Analyzed: ${analysis.analyzedFiles}`); const totalVulns = analysis.vulnerabilityBreakdown.critical + analysis.vulnerabilityBreakdown.high + analysis.vulnerabilityBreakdown.medium + analysis.vulnerabilityBreakdown.low; console.log(`āš ļø Total Vulnerabilities: ${totalVulns}`); if (analysis.vulnerabilityBreakdown.critical > 0) { console.log(`🚨 Critical Issues: ${analysis.vulnerabilityBreakdown.critical}`); } if (analysis.vulnerabilityBreakdown.high > 0) { console.log(`āš ļø High Issues: ${analysis.vulnerabilityBreakdown.high}`); } // Top categories const topCategories = Object.entries(analysis.categoryBreakdown) .filter(([_, count]) => count > 0) .sort(([,a], [,b]) => b - a) .slice(0, 3); if (topCategories.length > 0) { console.log('\nšŸ“‚ Top Issue Categories:'); topCategories.forEach(([category, count]) => { console.log(` ${category.charAt(0).toUpperCase() + category.slice(1)}: ${count}`); }); } } console.log('\nšŸ¤– AI Agent Guidance:'); if (results.fileAnalysis) { const analysis = results.fileAnalysis; const riskLevel = analysis.riskLevel; console.log(` - SCOPE: Single file security analysis (${riskLevel} risk)`); if (riskLevel === 'CRITICAL' || riskLevel === 'HIGH') { console.log(` - PRIORITY: Immediate security fixes required`); console.log(` - FOCUS: Address ${analysis.vulnerabilities.filter(v => ['CRITICAL', 'HIGH'].includes(v.severity)).length} high-priority vulnerabilities`); } else if (analysis.vulnerabilities.length > 0) { console.log(` - PRIORITY: Medium - schedule security improvements`); console.log(` - FOCUS: General security hardening`); } else { console.log(` - STATUS: No security issues detected with current filters`); } } if (results.projectAnalysis) { const analysis = results.projectAnalysis; const riskLevel = analysis.overallRiskLevel; console.log(` - SCOPE: Project-wide security analysis (${riskLevel} risk)`); const criticalCount = analysis.vulnerabilityBreakdown.critical; const highCount = analysis.vulnerabilityBreakdown.high; if (riskLevel === 'CRITICAL') { console.log(` - PRIORITY: Stop development - fix security issues immediately`); console.log(` - CRITICAL_FILES: ${analysis.fileAnalyses.filter(f => f.riskLevel === 'CRITICAL').length} files need immediate attention`); } else if (riskLevel === 'HIGH') { console.log(` - PRIORITY: High - address before deployment`); console.log(` - HIGH_RISK_FILES: ${analysis.fileAnalyses.filter(f => ['CRITICAL', 'HIGH'].includes(f.riskLevel)).length} files need review`); } else if (analysis.overallSecurityScore < 70) { console.log(` - PRIORITY: Medium - improve security posture`); console.log(` - FOCUS: General security improvements`); } else { console.log(` - STATUS: Security posture acceptable`); } // Top remediation categories const topCategories = Object.entries(analysis.categoryBreakdown) .filter(([_, count]) => count > 0) .sort(([,a], [,b]) => b - a) .slice(0, 2); if (topCategories.length > 0) { console.log(` - TOP_CATEGORIES: ${topCategories.map(([cat]) => cat).join(', ')}`); } } console.log(`\nā±ļø Analysis completed at: ${new Date().toLocaleString()}`); } // Run the CLI if (require.main === module) { main().catch(error => { console.error("Fatal error:", error); process.exit(1); }); } module.exports = { main };