| import os |
| from fpdf import FPDF |
| from datetime import datetime |
|
|
| class ReportGenerator: |
| def __init__(self, vulns): |
| self.vulns = vulns |
| self.filename = "Chimera_Scan_Report.pdf" |
|
|
| def get_severity_color(self, severity): |
| """Returns RGB color codes based on threat severity.""" |
| sev = severity.upper() |
| if "CRITICAL" in sev: return (220, 38, 38) |
| if "HIGH" in sev: return (234, 88, 12) |
| if "MEDIUM" in sev: return (202, 138, 4) |
| return (37, 99, 235) |
|
|
| def sanitize_text(self, text): |
| """ |
| Sanitizes text to be compatible with FPDF's latin-1 encoding. |
| Replaces common problematic characters and strips unencodable ones. |
| """ |
| if not text: |
| return "N/A" |
| |
| |
| replacements = { |
| '\u2013': '-', |
| '\u2014': '-', |
| '\u2018': "'", |
| '\u2019': "'", |
| '\u201c': '"', |
| '\u201d': '"', |
| '\u2022': '-', |
| '\u2026': '...', |
| } |
| |
| text = str(text) |
| for char, replacement in replacements.items(): |
| text = text.replace(char, replacement) |
| |
| |
| return text.encode('latin-1', 'replace').decode('latin-1') |
|
|
| def generate(self): |
| pdf = FPDF() |
| pdf.set_auto_page_break(auto=True, margin=15) |
| pdf.add_page() |
| |
| |
| |
| |
| pdf.set_fill_color(10, 15, 10) |
| pdf.rect(0, 0, 210, 40, 'F') |
| |
| pdf.set_y(15) |
| pdf.set_font("Courier", 'B', 24) |
| pdf.set_text_color(0, 255, 65) |
| pdf.cell(0, 10, txt=">> CHIMERA: FORENSIC AUDIT", ln=True, align='C') |
| |
| pdf.set_font("Courier", '', 10) |
| pdf.set_text_color(150, 150, 150) |
| pdf.cell(0, 10, txt=f"REPORT GENERATED: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC", ln=True, align='C') |
| |
| pdf.set_y(50) |
| |
| |
| |
| |
| pdf.set_font("Helvetica", 'B', 16) |
| pdf.set_text_color(0, 0, 0) |
| pdf.cell(0, 10, txt="1.0 EXECUTIVE SUMMARY", ln=True) |
| |
| |
| pdf.set_line_width(0.5) |
| pdf.set_draw_color(0, 255, 65) |
| pdf.line(10, pdf.get_y(), 200, pdf.get_y()) |
| pdf.ln(5) |
| |
| pdf.set_font("Helvetica", '', 11) |
| pdf.set_text_color(50, 50, 50) |
| |
| crit_count = sum(1 for v in self.vulns if 'CRITICAL' in v.get('severity', '').upper()) |
| high_count = sum(1 for v in self.vulns if 'HIGH' in v.get('severity', '').upper()) |
| med_count = sum(1 for v in self.vulns if 'MEDIUM' in v.get('severity', '').upper()) |
| |
| summary_text = ( |
| f"The Chimera Engine has completed a deep forensic security audit of the target application. " |
| f"During the automated scan, a total of {len(self.vulns)} security threats were identified.\n\n" |
| f"THREAT BREAKDOWN:\n" |
| f"- CRITICAL RISKS: {crit_count}\n" |
| f"- HIGH RISKS: {high_count}\n" |
| f"- MEDIUM/LOW RISKS: {med_count}\n\n" |
| f"Immediate remediation is strictly advised for all Critical and High severity findings to prevent " |
| f"unauthorized system compromise, data exfiltration, or denial of service." |
| ) |
| |
| pdf.multi_cell(0, 6, txt=self.sanitize_text(summary_text)) |
| pdf.ln(10) |
| |
| |
| |
| |
| pdf.set_font("Helvetica", 'B', 16) |
| pdf.set_text_color(0, 0, 0) |
| pdf.cell(0, 10, txt="2.0 DETAILED THREAT ANALYSIS", ln=True) |
| pdf.line(10, pdf.get_y(), 200, pdf.get_y()) |
| pdf.ln(8) |
| |
| for i, vuln in enumerate(self.vulns): |
| |
| v_type = self.sanitize_text(vuln.get('type', 'Unknown')) |
| severity = self.sanitize_text(vuln.get('severity', 'HIGH')).upper() |
| url = self.sanitize_text(vuln.get('url', 'N/A')) |
| impact = self.sanitize_text(vuln.get('impact', 'Potential compromise.')) |
| remediation = self.sanitize_text(vuln.get('remediation', 'Manual audit required.')) |
| evidence = self.sanitize_text(vuln.get('payload', 'N/A')) |
| risk_score = vuln.get('risk_score', 'N/A') |
|
|
| |
| r, g, b = self.get_severity_color(severity) |
| pdf.set_fill_color(r, g, b) |
| pdf.set_text_color(255, 255, 255) |
| pdf.set_font("Helvetica", 'B', 12) |
| pdf.cell(0, 10, txt=f" FINDING #{i+1}: {v_type} [{severity}]", ln=True, fill=True) |
| |
| |
| pdf.ln(2) |
| pdf.set_text_color(0, 0, 0) |
| pdf.set_font("Helvetica", 'B', 10) |
| pdf.cell(30, 6, txt="TARGET:") |
| pdf.set_font("Courier", '', 10) |
| pdf.set_text_color(37, 99, 235) |
| pdf.multi_cell(0, 6, txt=url) |
| |
| pdf.set_text_color(0, 0, 0) |
| pdf.set_font("Helvetica", 'B', 10) |
| pdf.cell(30, 6, txt="RISK SCORE:") |
| pdf.set_font("Helvetica", '', 10) |
| pdf.cell(0, 6, txt=f"{risk_score} / 10", ln=True) |
| |
| |
| pdf.ln(2) |
| pdf.set_font("Helvetica", 'B', 10) |
| pdf.cell(0, 6, txt="EVIDENCE / VULNERABLE CODE:", ln=True) |
| pdf.set_fill_color(245, 245, 245) |
| pdf.set_font("Courier", '', 9) |
| pdf.multi_cell(0, 5, txt=f"{evidence}", fill=True) |
| |
| |
| pdf.ln(4) |
| pdf.set_font("Helvetica", 'B', 10) |
| pdf.set_text_color(220, 38, 38) |
| pdf.cell(0, 6, txt=">> AI IMPACT ANALYSIS:", ln=True) |
| pdf.set_text_color(40, 40, 40) |
| pdf.set_font("Helvetica", '', 10) |
| pdf.multi_cell(0, 5, txt=impact) |
| |
| |
| pdf.ln(2) |
| pdf.set_font("Helvetica", 'B', 10) |
| pdf.set_text_color(22, 163, 74) |
| pdf.cell(0, 6, txt=">> AI SYSTEM REMEDIATION:", ln=True) |
| pdf.set_text_color(40, 40, 40) |
| pdf.set_font("Helvetica", '', 10) |
| pdf.multi_cell(0, 5, txt=remediation) |
| |
| |
| |
| |
| screenshot_path = vuln.get('screenshot') |
| if screenshot_path and os.path.exists(screenshot_path): |
| pdf.ln(4) |
| pdf.set_font("Helvetica", 'B', 10) |
| pdf.set_text_color(0, 0, 0) |
| pdf.cell(0, 6, txt=">> FORENSIC EVIDENCE (SCREENSHOT):", ln=True) |
| try: |
| |
| pdf.image(screenshot_path, x=20, w=170) |
| pdf.ln(5) |
| except Exception: |
| pdf.set_text_color(220, 38, 38) |
| pdf.cell(0, 6, txt="[Error rendering screenshot]", ln=True) |
| |
| pdf.ln(8) |
| |
| pdf.output(self.filename) |