/** * HTML Report Generator * Generates professional security audit reports * Supports Spanish (default) and English */ import * as fs from 'fs'; import * as path from 'path'; import Handlebars from 'handlebars'; import { ScanResult, Finding, Severity, FindingCategory, ReportGenerator } from '../types'; import { escapeHtml, formatDuration, getSeverityColor, getSeverityBadge } from '../utils'; import { logger } from '../utils/logger'; import { Language, getTranslations, Translations, defaultLanguage } from '../i18n'; import { getAllAnalyzers } from '../analyzers'; /** * HTML Report Generator Class */ export class HtmlReportGenerator implements ReportGenerator { name = 'HTML Report Generator'; format: 'html' = 'html'; private language: Language; private t: Translations; constructor(language: Language = defaultLanguage) { this.language = language; this.t = getTranslations(language); } /** * Set report language */ setLanguage(language: Language): void { this.language = language; this.t = getTranslations(language); } /** * Generate HTML report */ async generate(result: ScanResult): Promise { logger.info('📄 Generando reporte HTML...'); const template = this.getTemplate(); const compiledTemplate = Handlebars.compile(template); // Register helpers this.registerHelpers(); // Prepare data const data = this.prepareData(result); // Generate HTML const html = compiledTemplate(data); return html; } /** * Save report to file */ async saveReport(result: ScanResult, outputPath: string): Promise { const html = await this.generate(result); fs.writeFileSync(outputPath, html, 'utf-8'); logger.info(`📁 Reporte guardado en: ${outputPath}`); } /** * Register Handlebars helpers */ private registerHelpers(): void { Handlebars.registerHelper('severityColor', (severity: Severity) => getSeverityColor(severity)); Handlebars.registerHelper('severityBadge', (severity: Severity) => getSeverityBadge(severity)); Handlebars.registerHelper('escapeHtml', (text: string) => escapeHtml(text)); Handlebars.registerHelper('formatDate', (date: Date) => new Date(date).toLocaleString()); Handlebars.registerHelper('uppercase', (text: string) => text.toUpperCase()); Handlebars.registerHelper('json', (obj: any) => JSON.stringify(obj, null, 2)); Handlebars.registerHelper('eq', (a: any, b: any) => a === b); Handlebars.registerHelper('gt', (a: number, b: number) => a > b); } /** * Prepare data for template */ private prepareData(result: ScanResult): any { const criticalCount = result.findings.filter(f => f.severity === Severity.CRITICAL).length; const highCount = result.findings.filter(f => f.severity === Severity.HIGH).length; const mediumCount = result.findings.filter(f => f.severity === Severity.MEDIUM).length; const lowCount = result.findings.filter(f => f.severity === Severity.LOW).length; const infoCount = result.findings.filter(f => f.severity === Severity.INFO).length; const malwareCount = result.findings.filter(f => f.category === FindingCategory.MALWARE).length; const vulnCount = result.findings.filter(f => f.category === FindingCategory.VULNERABILITY).length; // Get unique files with malware const malwareFindings = result.findings.filter(f => f.category === FindingCategory.MALWARE); const malwareFilesSet = new Set(malwareFindings.map(f => f.location.file)); const malwareFiles = Array.from(malwareFilesSet); // Get analyzer versions const analyzers = getAllAnalyzers().map(a => ({ name: a.name, version: a.version, languages: a.languages.join(', ') })); // Group findings by file const findingsByFile: Record = {}; for (const finding of result.findings) { const file = finding.location.file; if (!findingsByFile[file]) { findingsByFile[file] = []; } findingsByFile[file].push(finding); } // Sort findings by severity const sortedFindings = [...result.findings].sort((a, b) => { const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 }; return (order[a.severity] || 4) - (order[b.severity] || 4); }); return { projectName: result.projectName, projectPath: result.projectPath, scanId: result.scanId, scanDate: new Date().toISOString(), riskScore: result.riskScore, riskLevel: result.riskLevel, totalFindings: result.findings.length, totalFiles: result.stats.totalFiles, totalLines: result.stats.totalLines, duration: formatDuration(result.stats.duration), // Severity counts criticalCount, highCount, mediumCount, lowCount, infoCount, // Category counts malwareCount, vulnCount, malwareFiles, // Analyzers analyzers, // Findings findings: sortedFindings, findingsByFile, // Stats filesByLanguage: result.stats.filesByLanguage, // Risk assessment hasCritical: criticalCount > 0, hasHigh: highCount > 0, hasMalware: malwareCount > 0, // Translations t: this.t, lang: this.language, malwareDescriptionText: this.t.malwareDescription(malwareCount), criticalDescriptionText: this.t.criticalDescription(criticalCount) }; } /** * Get HTML template */ private getTemplate(): string { return ` {{t.reportTitle}} - {{projectName}} 🔐 Secure-Scan {{t.reportTitle}} {{t.project}}: {{projectName}} {{t.scanId}}: {{scanId}} {{t.date}}: {{scanDate}} {{#if hasMalware}} 🦠 {{t.malwareDetected}} {{malwareDescriptionText}} {{t.affectedFiles}}: {{#each malwareFiles}} 📄 {{this}} {{/each}} {{/if}} {{#if hasCritical}} ⚠️ {{t.criticalVulnerabilities}} {{criticalDescriptionText}} {{/if}} {{t.riskScore}} {{riskScore}}/100 {{t.totalFindings}} {{totalFindings}} {{#if criticalCount}}{{criticalCount}}{{/if}} {{#if highCount}}{{highCount}}{{/if}} {{#if mediumCount}}{{mediumCount}}{{/if}} {{#if lowCount}}{{lowCount}}{{/if}} {{#if infoCount}}{{infoCount}}{{/if}} {{t.filesScanned}} {{totalFiles}} {{t.linesOfCode}} {{totalLines}} {{t.scanDuration}} {{duration}} {{t.securityFindings}} {{totalFindings}} {{t.issues}} {{#each findings}} {{this.title}} 📄 {{this.location.file}}:{{this.location.startLine}} {{this.severity}} {{this.description}} {{#if this.snippet.contextBefore}}{{this.snippet.contextBefore}}{{/if}} {{this.snippet.code}} {{#if this.snippet.contextAfter}}{{this.snippet.contextAfter}}{{/if}} {{#each this.standards}} {{this.name}}: {{this.id}} {{/each}} {{../t.remediation}} {{this.remediation}} {{/each}} {{#unless findings.length}} {{t.noIssuesFound}} {{/unless}} {{t.scanStatistics}} {{criticalCount}} {{t.critical}} {{highCount}} {{t.high}} {{mediumCount}} {{t.medium}} {{lowCount}} {{t.low}} {{infoCount}} {{t.info}} 🦠 {{malwareCount}} {{t.malware}} {{#if analyzers.length}} 🔧 {{t.analyzersUsed}} {{#each analyzers}} {{name}} v{{version}} ({{languages}}) {{/each}} {{/if}} `; } } export default HtmlReportGenerator;
{{malwareDescriptionText}}
{{criticalDescriptionText}}
{{this.description}}
{{this.remediation}}
{{t.noIssuesFound}}