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