#!/usr/bin/env python3 """ Professional Pentest Report Generator Generates detailed reports with PoCs, CVSS scores, requests/responses """ import base64 import json import os from datetime import datetime from pathlib import Path from typing import Dict, List, Any import html import logging logger = logging.getLogger(__name__) class ReportGenerator: """Generates professional penetration testing reports""" def __init__(self, scan_results: Dict, llm_analysis: str = ""): self.scan_results = scan_results self.llm_analysis = llm_analysis self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") def _get_severity_color(self, severity: str) -> str: """Get color for severity level""" colors = { "critical": "#dc3545", "high": "#fd7e14", "medium": "#ffc107", "low": "#17a2b8", "info": "#6c757d" } return colors.get(severity.lower(), "#6c757d") def _get_severity_badge(self, severity: str) -> str: """Get HTML badge for severity""" color = self._get_severity_color(severity) return f'{severity.upper()}' def _escape_html(self, text: str) -> str: """Escape HTML characters""" if not text: return "" return html.escape(str(text)) def _format_code_block(self, code: str, language: str = "") -> str: """Format code block with syntax highlighting""" escaped = self._escape_html(code) return f'
{escaped}'
def embed_screenshot(self, filepath: str) -> str:
"""Convert a screenshot file to a base64 data URI for HTML embedding."""
path = Path(filepath)
if not path.exists():
return ""
try:
with open(path, 'rb') as f:
data = base64.b64encode(f.read()).decode('ascii')
return f"data:image/png;base64,{data}"
except Exception:
return ""
def build_screenshots_html(self, finding_id: str, screenshots_dir: str = "reports/screenshots") -> str:
"""Build screenshot grid HTML for a finding, embedding images as base64."""
finding_dir = Path(screenshots_dir) / finding_id
if not finding_dir.exists():
return ""
screenshots = sorted(finding_dir.glob("*.png"))[:3]
if not screenshots:
return ""
cards = ""
for ss in screenshots:
data_uri = self.embed_screenshot(str(ss))
if data_uri:
caption = ss.stem.replace('_', ' ').title()
cards += f"""
| Target: | {self._escape_html(self.scan_results.get('target', 'N/A'))} |
| Scan Started: | {self.scan_results.get('scan_started', 'N/A')} |
| Scan Completed: | {self.scan_results.get('scan_completed', 'N/A')} |
| Overall Risk Level: | {self._get_severity_badge(risk_level)} |
Total Vulnerabilities: {total}
Open Ports Found: {summary.get('open_ports', 0)}
Tools Executed: {summary.get('tools_executed', 0)}
{self._escape_html(vuln.get('description', 'No description available'))}
{self._escape_html(vuln.get('affected_endpoint', 'N/A'))}
{self._escape_html(vuln.get('impact', 'Impact not assessed'))}
{self._escape_html(vuln.get('cvss_vector', 'N/A'))}
{self._escape_html(vuln.get('remediation', 'Consult vendor documentation for patches'))}
| Port | Protocol | Service | Version |
|---|
{self._escape_html(tool.get('command', 'N/A')[:100])}| Tool | Command | Status | Timestamp |
|---|
Penetration Test Report
Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
No vulnerabilities found during the assessment.
'}