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()