smart-ast-analyzer
Version:
Advanced AST-based project analysis tool with deep complexity analysis, security scanning, and optional AI enhancement
888 lines (759 loc) • 27.3 kB
JavaScript
const fs = require('fs').promises;
const path = require('path');
class SecurityReportGenerator {
constructor(options = {}) {
this.outputDir = options.output || './security-reports';
this.format = options.format || 'html';
}
async generateSecurityReport(securityResults, projectInfo) {
const report = {
id: `security-${Date.now()}`,
timestamp: new Date().toISOString(),
projectInfo,
securityResults,
summary: this.generateExecutiveSummary(securityResults),
riskMatrix: this.generateRiskMatrix(securityResults),
complianceStatus: this.generateComplianceStatus(securityResults),
actionPlan: this.generateActionPlan(securityResults)
};
await fs.mkdir(this.outputDir, { recursive: true });
const reportFiles = [];
if (this.format === 'html' || this.format === 'all') {
const htmlFile = await this.generateHTMLReport(report);
reportFiles.push(htmlFile);
}
if (this.format === 'json' || this.format === 'all') {
const jsonFile = await this.generateJSONReport(report);
reportFiles.push(jsonFile);
}
if (this.format === 'pdf' || this.format === 'all') {
const pdfFile = await this.generatePDFReport(report);
reportFiles.push(pdfFile);
}
return {
reportId: report.id,
files: reportFiles,
summary: report.summary
};
}
generateExecutiveSummary(results) {
const summary = {
overallRisk: this.calculateOverallRisk(results),
totalVulnerabilities: 0,
criticalIssues: 0,
securityScore: 0,
complianceScore: 0,
keyFindings: [],
immediateActions: []
};
if (results.vulnerabilities) {
summary.totalVulnerabilities = results.vulnerabilities.length;
summary.criticalIssues = results.vulnerabilities.filter(v => v.severity === 'critical').length;
}
if (results.securityScore) {
summary.securityScore = results.securityScore;
}
if (results.complianceReport?.overallCompliance) {
summary.complianceScore = results.complianceReport.overallCompliance;
}
// Generate key findings
summary.keyFindings = this.extractKeyFindings(results);
summary.immediateActions = this.extractImmediateActions(results);
return summary;
}
calculateOverallRisk(results) {
const scores = [];
if (results.securityScore) scores.push(results.securityScore);
if (results.dependencyAnalysis?.securityScore) scores.push(results.dependencyAnalysis.securityScore);
if (results.staticAnalysis?.riskScore) scores.push(results.staticAnalysis.riskScore);
if (scores.length === 0) return 'unknown';
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
if (avgScore >= 80) return 'low';
if (avgScore >= 60) return 'medium';
if (avgScore >= 40) return 'high';
return 'critical';
}
extractKeyFindings(results) {
const findings = [];
// Authentication findings
if (results.authentication?.methods?.length === 0) {
findings.push('No authentication mechanisms detected');
} else if (results.authentication?.methods && !results.authentication.methods.includes('mfa')) {
findings.push('Multi-factor authentication not implemented');
}
// Authorization findings
if (!results.authorization?.type || results.authorization.type === 'unknown') {
findings.push('Authorization mechanism unclear or missing');
}
// Vulnerability findings
const criticalVulns = results.vulnerabilities?.filter(v => v.severity === 'critical') || [];
if (criticalVulns.length > 0) {
findings.push(`${criticalVulns.length} critical vulnerabilities identified`);
}
// Dependency findings
if (results.dependencyAnalysis?.vulnerabilities?.length > 0) {
findings.push(`${results.dependencyAnalysis.vulnerabilities.length} vulnerable dependencies found`);
}
// Static analysis findings
if (results.staticAnalysis?.hardcodedSecrets?.length > 0) {
findings.push(`${results.staticAnalysis.hardcodedSecrets.length} hardcoded secrets detected`);
}
// OWASP compliance findings
if (results.owaspCompliance?.violations?.length > 0) {
findings.push(`OWASP Top 10 violations detected: ${results.owaspCompliance.violations.join(', ')}`);
}
return findings.slice(0, 10); // Top 10 findings
}
extractImmediateActions(results) {
const actions = [];
// Critical vulnerabilities
const criticalVulns = results.vulnerabilities?.filter(v => v.severity === 'critical') || [];
criticalVulns.forEach(vuln => {
actions.push(`Fix ${vuln.type} vulnerability in ${vuln.location?.file || 'unknown location'}`);
});
// Hardcoded secrets
if (results.staticAnalysis?.hardcodedSecrets?.length > 0) {
actions.push('Remove all hardcoded secrets and use environment variables');
}
// Critical dependency vulnerabilities
const criticalDepVulns = results.dependencyAnalysis?.vulnerabilities?.filter(v => v.severity === 'critical') || [];
criticalDepVulns.forEach(vuln => {
actions.push(`Update ${vuln.package} to fix critical vulnerability`);
});
return actions.slice(0, 5); // Top 5 immediate actions
}
generateRiskMatrix(results) {
const matrix = {
critical: { high: [], medium: [], low: [] },
high: { high: [], medium: [], low: [] },
medium: { high: [], medium: [], low: [] },
low: { high: [], medium: [], low: [] }
};
if (results.vulnerabilities) {
results.vulnerabilities.forEach(vuln => {
const impact = this.assessImpact(vuln);
const likelihood = vuln.likelihood || this.assessLikelihood(vuln);
matrix[vuln.severity]?.[likelihood]?.push({
type: vuln.type,
description: vuln.description,
location: vuln.location
});
});
}
return matrix;
}
assessImpact(vulnerability) {
// Simple impact assessment based on vulnerability type
const highImpactTypes = ['sql injection', 'code injection', 'authentication bypass'];
const mediumImpactTypes = ['xss', 'csrf', 'information disclosure'];
const vulnType = vulnerability.type?.toLowerCase() || '';
if (highImpactTypes.some(type => vulnType.includes(type))) {
return 'high';
} else if (mediumImpactTypes.some(type => vulnType.includes(type))) {
return 'medium';
} else {
return 'low';
}
}
assessLikelihood(vulnerability) {
// Simple likelihood assessment
if (vulnerability.type?.toLowerCase().includes('injection')) return 'high';
if (vulnerability.location?.file?.includes('public') ||
vulnerability.location?.file?.includes('api')) return 'medium';
return 'low';
}
generateComplianceStatus(results) {
const status = {
overall: 'unknown',
standards: {},
gaps: [],
recommendations: []
};
if (results.owaspCompliance) {
status.standards.owasp = {
score: results.owaspCompliance.score,
status: results.owaspCompliance.score >= 70 ? 'compliant' : 'non-compliant',
violations: results.owaspCompliance.violations
};
}
if (results.complianceReport) {
Object.entries(results.complianceReport.standards).forEach(([standard, data]) => {
status.standards[standard] = {
score: data.score,
status: data.score >= 70 ? 'compliant' : 'non-compliant'
};
});
}
// Calculate overall compliance
const scores = Object.values(status.standards).map(s => s.score);
if (scores.length > 0) {
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
status.overall = avgScore >= 70 ? 'compliant' : 'non-compliant';
}
return status;
}
generateActionPlan(results) {
const plan = {
immediate: [], // 0-1 week
shortTerm: [], // 1-4 weeks
mediumTerm: [], // 1-3 months
longTerm: [] // 3+ months
};
// Categorize recommendations by timeline
if (results.recommendations) {
results.recommendations.forEach(rec => {
const timeline = rec.implementation?.timeline || this.inferTimeline(rec);
switch (timeline) {
case 'immediate':
plan.immediate.push(rec);
break;
case 'short_term':
plan.shortTerm.push(rec);
break;
case 'medium_term':
plan.mediumTerm.push(rec);
break;
case 'long_term':
plan.longTerm.push(rec);
break;
}
});
}
// Add critical vulnerability fixes to immediate
const criticalVulns = results.vulnerabilities?.filter(v => v.severity === 'critical') || [];
criticalVulns.forEach(vuln => {
plan.immediate.push({
priority: 'critical',
title: `Fix ${vuln.type}`,
description: vuln.mitigation || `Address critical ${vuln.type} vulnerability`,
effort: 'high',
impact: 'high'
});
});
return plan;
}
inferTimeline(recommendation) {
if (recommendation.priority === 'critical') return 'immediate';
if (recommendation.priority === 'high') return 'short_term';
if (recommendation.priority === 'medium') return 'medium_term';
return 'long_term';
}
async generateHTMLReport(report) {
const filename = `${report.id}.html`;
const filepath = path.join(this.outputDir, filename);
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Security Analysis Report - ${report.projectInfo.framework}</title>
<style>
${this.getSecurityReportCSS()}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
</head>
<body>
<div class="container">
${this.generateHTMLHeader(report)}
${this.generateHTMLExecutiveSummary(report)}
${this.generateHTMLVulnerabilitySection(report)}
${this.generateHTMLComplianceSection(report)}
${this.generateHTMLRiskMatrix(report)}
${this.generateHTMLActionPlan(report)}
${this.generateHTMLAppendix(report)}
</div>
<script>
${this.generateSecurityChartJS(report)}
</script>
</body>
</html>
`;
await fs.writeFile(filepath, html.trim());
return filepath;
}
generateHTMLHeader(report) {
return `
<header class="security-header">
<div class="header-content">
<h1>🔒 Security Analysis Report</h1>
<div class="header-meta">
<div class="meta-item">
<strong>Project:</strong> ${report.projectInfo.framework}
</div>
<div class="meta-item">
<strong>Generated:</strong> ${new Date(report.timestamp).toLocaleString()}
</div>
<div class="meta-item risk-badge ${report.summary.overallRisk}">
<strong>Risk Level:</strong> ${report.summary.overallRisk.toUpperCase()}
</div>
</div>
</div>
</header>
`;
}
generateHTMLExecutiveSummary(report) {
return `
<section class="executive-summary">
<h2>📊 Executive Summary</h2>
<div class="summary-metrics">
<div class="metric-card critical">
<h3>Critical Issues</h3>
<div class="metric-value">${report.summary.criticalIssues}</div>
</div>
<div class="metric-card vulnerabilities">
<h3>Total Vulnerabilities</h3>
<div class="metric-value">${report.summary.totalVulnerabilities}</div>
</div>
<div class="metric-card security-score">
<h3>Security Score</h3>
<div class="metric-value">${report.summary.securityScore}/100</div>
</div>
<div class="metric-card compliance">
<h3>Compliance Score</h3>
<div class="metric-value">${report.summary.complianceScore}/100</div>
</div>
</div>
<div class="key-findings">
<h3>🔍 Key Findings</h3>
<ul>
${report.summary.keyFindings.map(finding => `<li>${finding}</li>`).join('')}
</ul>
</div>
<div class="immediate-actions">
<h3>⚡ Immediate Actions Required</h3>
<ol>
${report.summary.immediateActions.map(action => `<li>${action}</li>`).join('')}
</ol>
</div>
</section>
`;
}
generateHTMLVulnerabilitySection(report) {
if (!report.securityResults.vulnerabilities?.length) {
return '<section><h2>🔒 Vulnerabilities</h2><p>No vulnerabilities detected.</p></section>';
}
const vulns = report.securityResults.vulnerabilities;
const severityGroups = this.groupBySeverity(vulns);
return `
<section class="vulnerabilities-section">
<h2>🔒 Security Vulnerabilities</h2>
<div class="vulnerability-summary">
<canvas id="vulnerabilitySeverityChart" width="400" height="200"></canvas>
</div>
${Object.entries(severityGroups).map(([severity, vulnList]) => `
<div class="severity-group ${severity}">
<h3>${severity.toUpperCase()} Severity (${vulnList.length})</h3>
<div class="vulnerabilities-list">
${vulnList.map(vuln => this.generateVulnerabilityCard(vuln)).join('')}
</div>
</div>
`).join('')}
</section>
`;
}
generateVulnerabilityCard(vuln) {
return `
<div class="vulnerability-card ${vuln.severity}">
<div class="vuln-header">
<h4>${vuln.type || 'Unknown Vulnerability'}</h4>
<span class="severity-badge ${vuln.severity}">${vuln.severity}</span>
</div>
<div class="vuln-content">
<p><strong>Description:</strong> ${vuln.description || 'No description available'}</p>
${vuln.location ? `<p><strong>Location:</strong> ${vuln.location.file}:${vuln.location.line}</p>` : ''}
${vuln.owasp_category ? `<p><strong>OWASP Category:</strong> ${vuln.owasp_category}</p>` : ''}
${vuln.cvss_score ? `<p><strong>CVSS Score:</strong> ${vuln.cvss_score}</p>` : ''}
</div>
<div class="vuln-mitigation">
<p><strong>Mitigation:</strong> ${vuln.mitigation || 'No mitigation provided'}</p>
</div>
</div>
`;
}
generateHTMLComplianceSection(report) {
const compliance = report.complianceStatus || { overall: 'unknown', standards: {} };
const overall = compliance.overall || 'unknown';
return `
<section class="compliance-section">
<h2>📋 Compliance Status</h2>
<div class="compliance-overview">
<div class="compliance-score ${overall}">
<h3>Overall Compliance: ${overall.toUpperCase()}</h3>
</div>
</div>
<div class="standards-grid">
${Object.entries(compliance.standards || {}).map(([standard, data]) => `
<div class="standard-card ${data.status || 'unknown'}">
<h4>${standard.toUpperCase()}</h4>
<div class="standard-score">${data.score || 0}/100</div>
<div class="standard-status">${data.status || 'unknown'}</div>
</div>
`).join('')}
</div>
</section>
`;
}
generateHTMLRiskMatrix(report) {
return `
<section class="risk-matrix-section">
<h2>📊 Risk Matrix</h2>
<div class="risk-matrix">
${this.generateRiskMatrixHTML(report.riskMatrix)}
</div>
</section>
`;
}
generateRiskMatrixHTML(matrix) {
return `
<table class="risk-matrix-table">
<thead>
<tr>
<th>Severity / Likelihood</th>
<th>High</th>
<th>Medium</th>
<th>Low</th>
</tr>
</thead>
<tbody>
${Object.entries(matrix).map(([severity, likelihoods]) => `
<tr>
<td class="severity-label ${severity}">${severity.toUpperCase()}</td>
${Object.entries(likelihoods).map(([likelihood, risks]) => `
<td class="risk-cell ${severity}-${likelihood}">
${risks.length > 0 ? `<span class="risk-count">${risks.length}</span>` : ''}
</td>
`).join('')}
</tr>
`).join('')}
</tbody>
</table>
`;
}
generateHTMLActionPlan(report) {
return `
<section class="action-plan-section">
<h2>📅 Action Plan</h2>
<div class="timeline-container">
${Object.entries(report.actionPlan).map(([timeline, actions]) => `
<div class="timeline-group">
<h3>${this.formatTimelineName(timeline)} (${actions.length} actions)</h3>
<div class="actions-list">
${actions.map(action => `
<div class="action-item ${action.priority}">
<h4>${action.title}</h4>
<p>${action.description}</p>
${action.effort ? `<span class="effort-badge">${action.effort} effort</span>` : ''}
${action.impact ? `<span class="impact-badge">${action.impact} impact</span>` : ''}
</div>
`).join('')}
</div>
</div>
`).join('')}
</div>
</section>
`;
}
formatTimelineName(timeline) {
const names = {
immediate: '🚨 Immediate (0-1 week)',
shortTerm: '⏰ Short Term (1-4 weeks)',
mediumTerm: '📅 Medium Term (1-3 months)',
longTerm: '🎯 Long Term (3+ months)'
};
return names[timeline] || timeline;
}
generateHTMLAppendix(report) {
return `
<section class="appendix-section">
<h2>📚 Appendix</h2>
<div class="methodology">
<h3>Analysis Methodology</h3>
<p>This security analysis was performed using automated static analysis, dependency scanning, and AI-powered code review following OWASP guidelines.</p>
</div>
<div class="references">
<h3>References</h3>
<ul>
<li><a href="https://owasp.org/Top10/">OWASP Top 10 2021</a></li>
<li><a href="https://cwe.mitre.org/">Common Weakness Enumeration</a></li>
<li><a href="https://nvd.nist.gov/">National Vulnerability Database</a></li>
<li><a href="https://github.com/advisories">GitHub Security Advisories</a></li>
</ul>
</div>
<div class="disclaimer">
<h3>Disclaimer</h3>
<p>This automated analysis provides security insights but should be supplemented with manual security testing and professional security auditing for comprehensive coverage.</p>
</div>
</section>
`;
}
groupBySeverity(vulnerabilities) {
const groups = {
critical: [],
high: [],
medium: [],
low: []
};
vulnerabilities.forEach(vuln => {
const severity = vuln.severity || 'low';
if (groups[severity]) {
groups[severity].push(vuln);
}
});
return groups;
}
async generateJSONReport(report) {
const filename = `${report.id}.json`;
const filepath = path.join(this.outputDir, filename);
await fs.writeFile(filepath, JSON.stringify(report));
return filepath;
}
async generatePDFReport(report) {
// PDF generation would require a library like Puppeteer or PDFKit
// For now, return a placeholder
const filename = `${report.id}.pdf`;
const filepath = path.join(this.outputDir, filename);
// Placeholder - in a real implementation, convert HTML to PDF
await fs.writeFile(filepath, 'PDF report generation not implemented yet');
return filepath;
}
getSecurityReportCSS() {
return `
/* Security Report Styles */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
background: #f8fafc;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
min-height: 100vh;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}
.security-header {
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
color: white;
padding: 2rem;
text-align: center;
}
.header-meta {
display: flex;
justify-content: center;
gap: 2rem;
margin-top: 1rem;
flex-wrap: wrap;
}
.meta-item {
background: rgba(255,255,255,0.1);
padding: 0.5rem 1rem;
border-radius: 20px;
}
.risk-badge {
font-weight: bold;
}
.risk-badge.critical { background: #dc2626; }
.risk-badge.high { background: #ea580c; }
.risk-badge.medium { background: #ca8a04; }
.risk-badge.low { background: #16a34a; }
.executive-summary {
padding: 2rem;
border-bottom: 1px solid #e5e7eb;
}
.summary-metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.metric-card {
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
color: white;
padding: 1.5rem;
border-radius: 12px;
text-align: center;
}
.metric-card.critical {
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
}
.metric-value {
font-size: 2.5rem;
font-weight: bold;
margin-top: 0.5rem;
}
.vulnerabilities-section {
padding: 2rem;
}
.severity-group {
margin-bottom: 2rem;
}
.vulnerability-card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
border-left: 4px solid #6b7280;
}
.vulnerability-card.critical { border-left-color: #dc2626; }
.vulnerability-card.high { border-left-color: #ea580c; }
.vulnerability-card.medium { border-left-color: #ca8a04; }
.vulnerability-card.low { border-left-color: #16a34a; }
.vuln-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.severity-badge {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: bold;
color: white;
}
.severity-badge.critical { background: #dc2626; }
.severity-badge.high { background: #ea580c; }
.severity-badge.medium { background: #ca8a04; }
.severity-badge.low { background: #16a34a; }
.compliance-section {
padding: 2rem;
background: #f9fafb;
}
.standards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.standard-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
text-align: center;
border: 2px solid #e5e7eb;
}
.standard-card.compliant { border-color: #16a34a; }
.standard-card.non-compliant { border-color: #dc2626; }
.risk-matrix-section {
padding: 2rem;
}
.risk-matrix-table {
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
}
.risk-matrix-table th,
.risk-matrix-table td {
border: 1px solid #e5e7eb;
padding: 1rem;
text-align: center;
}
.risk-matrix-table th {
background: #f3f4f6;
font-weight: bold;
}
.severity-label {
font-weight: bold;
text-transform: uppercase;
}
.risk-cell {
position: relative;
min-height: 60px;
}
.risk-count {
display: inline-block;
background: #dc2626;
color: white;
border-radius: 50%;
width: 30px;
height: 30px;
line-height: 30px;
font-weight: bold;
}
.action-plan-section {
padding: 2rem;
background: #f9fafb;
}
.timeline-group {
margin-bottom: 2rem;
}
.action-item {
background: white;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
border-left: 4px solid #6b7280;
}
.action-item.critical { border-left-color: #dc2626; }
.action-item.high { border-left-color: #ea580c; }
.action-item.medium { border-left-color: #ca8a04; }
.action-item.low { border-left-color: #16a34a; }
.effort-badge,
.impact-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
margin-right: 0.5rem;
}
.effort-badge { background: #e0e7ff; color: #3730a3; }
.impact-badge { background: #fef3c7; color: #92400e; }
.appendix-section {
padding: 2rem;
border-top: 1px solid #e5e7eb;
}
(max-width: 768px) {
.header-meta { flex-direction: column; }
.summary-metrics { grid-template-columns: 1fr; }
.standards-grid { grid-template-columns: 1fr; }
}
`;
}
generateSecurityChartJS(report) {
return `
// Vulnerability Severity Chart
const ctx = document.getElementById('vulnerabilitySeverityChart');
if (ctx && ${JSON.stringify(report.securityResults.vulnerabilities || [])}.length > 0) {
const vulnerabilities = ${JSON.stringify(report.securityResults.vulnerabilities || [])};
const severityCounts = vulnerabilities.reduce((counts, vuln) => {
counts[vuln.severity] = (counts[vuln.severity] || 0) + 1;
return counts;
}, {});
new Chart(ctx, {
type: 'doughnut',
data: {
labels: Object.keys(severityCounts),
datasets: [{
data: Object.values(severityCounts),
backgroundColor: [
'#dc2626', // critical
'#ea580c', // high
'#ca8a04', // medium
'#16a34a' // low
]
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
},
title: {
display: true,
text: 'Vulnerabilities by Severity'
}
}
}
});
}
console.log('Security report loaded successfully!');
`;
}
}
module.exports = SecurityReportGenerator;