import os from io import BytesIO from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak from app.models.schemas import ScanResult, Vulnerability from datetime import datetime class ReportGenerator: """ Service for generating PDF reports based on a professional Cyber Security Incident template. """ def __init__(self): self.styles = getSampleStyleSheet() self._setup_custom_styles() def _setup_custom_styles(self): self.styles.add(ParagraphStyle( name='TemplateTitle', fontSize=22, leading=26, textColor=colors.black, alignment=0, # Left fontWeight='BOLD', spaceAfter=10 )) self.styles.add(ParagraphStyle( name='PurpleHeader', fontSize=10, leading=12, textColor=colors.white, alignment=1, # Center fontWeight='BOLD' )) self.styles.add(ParagraphStyle( name='TableLabel', fontSize=9, leading=11, textColor=colors.black, fontWeight='BOLD' )) self.styles.add(ParagraphStyle( name='TableValue', fontSize=9, leading=11, textColor=colors.HexColor("#444444") )) def generate_scan_pdf(self, scan: ScanResult) -> BytesIO: buffer = BytesIO() doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=30) elements = [] # 1. Main Title elements.append(Paragraph("Cyber security incident report to enhance efficiency", self.styles['TemplateTitle'])) elements.append(Paragraph("This report covers the automated security assessment performed by VulneraAI agentic platform.", self.styles['Normal'])) elements.append(Spacer(1, 20)) # 2. Top Info Table info_data = [ [Paragraph("Reported by: VulneraAI Agent", self.styles['TableLabel']), Paragraph("Phone No: N/A (Automated)", self.styles['TableLabel'])], [Paragraph(f"Email: system@vulnera.ai", self.styles['TableLabel']), Paragraph(f"Date Reported: {scan.created_at.strftime('%d-%m-%Y')}", self.styles['TableLabel'])], [Paragraph(f"Agency: VulneraAI Security", self.styles['TableLabel']), Paragraph(f"Device Type: Cloud Scanner", self.styles['TableLabel'])], [Paragraph(f"Target URL: {scan.target}", self.styles['TableLabel']), Paragraph(f"Location: Global Node", self.styles['TableLabel'])], [Paragraph(f"Scan ID: {scan.scan_id[:8]}...", self.styles['TableLabel']), ""] ] info_table = Table(info_data, colWidths=[265, 265]) info_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#c0a9d1")), # Light purple ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('PADDING', (0, 0), (-1, -1), 6), ])) elements.append(info_table) elements.append(Spacer(1, 20)) # 3. Incident Type Bar type_header = Table([[Paragraph("Findings & Vulnerability Analysis", self.styles['PurpleHeader'])]], colWidths=[530]) type_header.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor("#5b4b8a")), # Dark purple ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('PADDING', (0, 0), (-1, -1), 8), ])) elements.append(type_header) # 4. Main Grid Logic (Mapping vulns to the sections) # We divide our vulns into categories for the report recon_vulns = [v for v in scan.vulnerabilities if "Recon" in v.type or "Information" in v.type] malicious_vulns = [v for v in scan.vulnerabilities if "Injection" in v.type or "XSS" in v.type] grid_data = [ [Paragraph("Intelligence Assets", self.styles['TableLabel']), Paragraph("Malicious Activity Detection", self.styles['TableLabel'])], [ Paragraph("
".join([f"• {v.type}" for v in recon_vulns[:5]]) or "No exposure detected.", self.styles['TableValue']), Paragraph("
".join([f"• {v.type}" for v in malicious_vulns[:5]]) or "No active payloads detected.", self.styles['TableValue']) ], [Paragraph("Investigated by:", self.styles['TableLabel']), Paragraph("Evidence Collected:", self.styles['TableLabel'])], [ Paragraph("VulneraAI Autonomous Engine", self.styles['TableValue']), Paragraph("Yes (Captured in technical logs)", self.styles['TableValue']) ], [Paragraph("Incident Source:", self.styles['TableLabel']), Paragraph("Analysis of Findings:", self.styles['TableLabel'])], [ Paragraph(scan.target, self.styles['TableValue']), Paragraph(f"Found {len(scan.vulnerabilities)} vulnerabilities during deep scan.", self.styles['TableValue']) ] ] grid_table = Table(grid_data, colWidths=[265, 265]) grid_table.setStyle(TableStyle([ ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('VALIGN', (0, 0), (-1, -1), 'TOP'), ('PADDING', (0, 0), (-1, -1), 10), ('BACKGROUND', (0, 0), (1, 0), colors.HexColor("#f3f4f6")), ('BACKGROUND', (0, 2), (1, 2), colors.HexColor("#f3f4f6")), ('BACKGROUND', (0, 4), (1, 4), colors.HexColor("#f3f4f6")), ])) elements.append(grid_table) elements.append(Spacer(1, 20)) # 5. Recommendations section (Mimicking the template footer) elements.append(Paragraph("Recommended Action:", self.styles['TableLabel'])) rec_text = "Implement immediate security patches for detected vulnerabilities. Review access logs and enforce multi-factor authentication across all exposed endpoints." elements.append(Paragraph(rec_text, self.styles['Normal'])) elements.append(Spacer(1, 10)) elements.append(Paragraph("Additional Comments:", self.styles['TableLabel'])) elements.append(Paragraph("Automated report generated by VulneraAI. Manual verification of suspected risks is advised.", self.styles['Normal'])) # Detailed Findings on a new page (Optional but good for technical depth) if scan.vulnerabilities: elements.append(PageBreak()) elements.append(Paragraph("Technical Appendix: Detailed Finding Breakdown", self.styles['TemplateTitle'])) elements.append(Spacer(1, 15)) for vuln in scan.vulnerabilities: vuln_table_data = [ [Paragraph(f"{vuln.type}", self.styles['Normal']), Paragraph(vuln.severity.upper(), self.styles['Normal'])], [Paragraph(f"Description: {vuln.description}", self.styles['TableValue']), ""], [Paragraph(f"Remediation: {vuln.remediation}", self.styles['TableValue']), ""] ] vt = Table(vuln_table_data, colWidths=[430, 100]) vt.setStyle(TableStyle([ ('SPAN', (0, 1), (1, 1)), ('SPAN', (0, 2), (1, 2)), ('LINEBELOW', (0, 2), (-1, -2), 0.5, colors.black), ('BACKGROUND', (1, 0), (1, 0), colors.red if vuln.severity in ['high', 'critical'] else colors.orange), ('TEXTCOLOR', (1, 0), (1, 0), colors.white), ])) elements.append(vt) elements.append(Spacer(1, 15)) doc.build(elements) buffer.seek(0) return buffer report_generator = ReportGenerator()