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