UNPKG

pury

Version:

đŸ›Ąī¸ AI-powered security scanner with advanced threat detection, dual reporting system (detailed & summary), and comprehensive code analysis

292 lines â€ĸ 12.4 kB
import chalk from 'chalk'; import { table } from 'table'; import { Severity } from '../types/index.js'; export class ConsoleReporter { async output(report) { this.printHeader(); this.printSummary(report); if (report.findings.length > 0) { this.printFindings(report.findings); } else { this.printNoFindings(); } this.printFooter(report); } async writeToFile(report, filePath) { // For console output, we'll just write a plain text version const fs = await import('fs/promises'); const output = this.generateTextReport(report); await fs.writeFile(filePath, output, 'utf8'); } printHeader() { console.log(); console.log(chalk.cyan.bold('╔══════════════════════════════════════════════════╗')); console.log(chalk.cyan.bold('║') + chalk.white.bold(' đŸ›Ąī¸ PuryAI Security Scanner ') + chalk.cyan.bold('║')); console.log(chalk.cyan.bold('║') + chalk.gray(' Advanced AI-Powered Code Analysis ') + chalk.cyan.bold('║')); console.log(chalk.cyan.bold('╚══════════════════════════════════════════════════╝')); } printSummary(report) { const { summary } = report; console.log(); console.log(chalk.yellow.bold('📊 Scan Summary')); console.log(chalk.yellow('━'.repeat(50))); const summaryData = [ ['Files Scanned', summary.filesScanned.toString()], [ 'Threats Found', this.colorBySeverity(summary.threatsFound.toString(), summary.threatsFound > 0 ? 'medium' : 'info') ], ['Scan Duration', `${(summary.scanDuration / 1000).toFixed(2)}s`], ['Timestamp', new Date(report.metadata.timestamp).toLocaleString()] ]; // Add severity breakdown if (summary.threatsFound > 0) { summaryData.push(['', '']); // Empty row summaryData.push([chalk.bold('Severity Breakdown'), '']); for (const [severity, count] of Object.entries(summary.severityCount)) { if (count > 0) { summaryData.push([ ` ${this.getSeverityIcon(severity)} ${severity.charAt(0).toUpperCase() + severity.slice(1)}`, this.colorBySeverity(count.toString(), severity) ]); } } } console.log(table(summaryData, { border: { topBody: '', topJoin: '', topLeft: '', topRight: '', bottomBody: '', bottomJoin: '', bottomLeft: '', bottomRight: '', bodyLeft: '', bodyRight: '', bodyJoin: '', joinBody: '', joinLeft: '', joinRight: '', joinJoin: '' }, columnDefault: { paddingLeft: 0, paddingRight: 2 } })); } printFindings(findings) { console.log(); console.log(chalk.red.bold('🔍 Security Findings')); console.log(chalk.red('━'.repeat(50))); // Group findings by file and severity for better organization const groupedByFile = this.groupFindingsByFileAndSeverity(findings); const basePath = process.cwd(); for (const [filePath, severityGroups] of groupedByFile.entries()) { const relativePath = this.getRelativePath(filePath, basePath); console.log(); console.log(chalk.cyan.bold(`📄 ${relativePath}`)); // Show findings by severity for this file for (const severity of ['critical', 'high', 'medium', 'low']) { const severityFindings = severityGroups[severity]; if (severityFindings && severityFindings.length > 0) { this.printFileSeverityFindings(severity, severityFindings); } } } } printFileSeverityFindings(severity, findings) { const icon = this.getSeverityIcon(severity); const severityColor = this.colorBySeverity(` ${icon} ${severity.toUpperCase()}`, severity); // Group by issue type to reduce repetition const groupedByType = this.groupFindingsByType(findings); for (const [issueType, typeFindings] of groupedByType.entries()) { if (typeFindings.length === 1) { const finding = typeFindings[0]; console.log(severityColor); console.log(` ├─ ${finding.title}${finding.line ? ` (Line ${finding.line})` : ''}`); if (finding.evidence && finding.evidence.length <= 50) { console.log(chalk.gray(` │ 💡 ${finding.evidence}`)); } } else { console.log(severityColor); console.log(` ├─ ${issueType} (${typeFindings.length} occurrences)`); typeFindings.forEach((finding, idx) => { const prefix = idx === typeFindings.length - 1 ? ' └─' : ' ├─'; console.log(`${prefix} Line ${finding.line || '?'}`); }); } } } printNoFindings() { console.log(); console.log(chalk.green.bold('╔═══════════════════════════════════════╗')); console.log(chalk.green.bold('║') + chalk.white.bold(' ✅ No security issues found! ') + chalk.green.bold('║')); console.log(chalk.green.bold('║') + chalk.gray(' Your code appears clean & secure ') + chalk.green.bold('║')); console.log(chalk.green.bold('╚═══════════════════════════════════════╝')); } printFooter(report) { console.log(); console.log(chalk.cyan('━'.repeat(50))); console.log(chalk.magenta(`🚀 Generated by PuryAI v${report.metadata.version}`)); if (report.metadata.aiProvider) { console.log(chalk.blue(`🤖 AI Analysis powered by ${report.metadata.aiProvider}`)); } // Show recommendations if (report.summary.threatsFound > 0) { console.log(); console.log(chalk.yellow.bold('💡 Recommendations:')); console.log(chalk.yellow(' â€ĸ Review and address all critical and high severity issues')); console.log(chalk.yellow(' â€ĸ Use --format json for detailed analysis')); console.log(chalk.yellow(' â€ĸ Consider running additional analyzers with --analyzers')); } else { console.log(chalk.green('🎉 Great job! Your code follows security best practices.')); } console.log(); } groupFindingsByFileAndSeverity(findings) { const grouped = new Map(); for (const finding of findings) { if (!grouped.has(finding.file)) { grouped.set(finding.file, { [Severity.CRITICAL]: [], [Severity.HIGH]: [], [Severity.MEDIUM]: [], [Severity.LOW]: [] }); } grouped.get(finding.file)[finding.severity].push(finding); } return grouped; } groupFindingsByType(findings) { const grouped = new Map(); for (const finding of findings) { const key = finding.title; if (!grouped.has(key)) { grouped.set(key, []); } grouped.get(key).push(finding); } return grouped; } getRelativePath(filePath, basePath) { if (filePath.startsWith(basePath)) { const relativePath = filePath.substring(basePath.length); return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath; } return filePath; } groupFindingsBySeverity(findings) { const grouped = { [Severity.CRITICAL]: [], [Severity.HIGH]: [], [Severity.MEDIUM]: [], [Severity.LOW]: [] }; for (const finding of findings) { grouped[finding.severity].push(finding); } return grouped; } getSeverityIcon(severity) { switch (severity) { case Severity.CRITICAL: return '🚨'; case Severity.HIGH: return 'âš ī¸'; case Severity.MEDIUM: return '📋'; case Severity.LOW: return 'â„šī¸'; default: return '📝'; } } colorBySeverity(text, severity) { switch (severity) { case Severity.CRITICAL: return chalk.red.bold(text); case Severity.HIGH: return chalk.red(text); case Severity.MEDIUM: return chalk.yellow(text); case Severity.LOW: return chalk.blue(text); case 'info': return chalk.cyan(text); default: return text; } } generateTextReport(report) { const lines = []; lines.push('PuryAI Security Scanner Report'); lines.push('='.repeat(40)); lines.push(''); // Summary lines.push('SCAN SUMMARY'); lines.push('-'.repeat(20)); lines.push(`Files Scanned: ${report.summary.filesScanned}`); lines.push(`Threats Found: ${report.summary.threatsFound}`); lines.push(`Scan Duration: ${(report.summary.scanDuration / 1000).toFixed(2)}s`); lines.push(`Timestamp: ${new Date(report.metadata.timestamp).toLocaleString()}`); lines.push(''); // Severity breakdown if (report.summary.threatsFound > 0) { lines.push('SEVERITY BREAKDOWN'); lines.push('-'.repeat(20)); for (const [severity, count] of Object.entries(report.summary.severityCount)) { if (count > 0) { lines.push(`${severity.charAt(0).toUpperCase() + severity.slice(1)}: ${count}`); } } lines.push(''); } // Findings if (report.findings.length > 0) { lines.push('SECURITY FINDINGS'); lines.push('-'.repeat(20)); const groupedFindings = this.groupFindingsBySeverity(report.findings); for (const severity of ['critical', 'high', 'medium', 'low']) { const severityFindings = groupedFindings[severity]; if (severityFindings && severityFindings.length > 0) { lines.push(`\\n${severity.toUpperCase()} SEVERITY (${severityFindings.length})`); lines.push('-'.repeat(30)); severityFindings.forEach((finding, index) => { const location = finding.line ? `${finding.file}:${finding.line}` : finding.file; lines.push(`\\n${index + 1}. ${finding.title}`); lines.push(` Location: ${location}`); lines.push(` Description: ${finding.description}`); if (finding.evidence) { lines.push(` Evidence: ${finding.evidence}`); } if (finding.suggestion) { lines.push(` Suggestion: ${finding.suggestion}`); } }); } } } else { lines.push('No security issues found!'); } lines.push(''); lines.push('-'.repeat(40)); lines.push(`Generated by PuryAI v${report.metadata.version}`); return lines.join('\\n'); } } //# sourceMappingURL=console.js.map