How the Report Engine Works

When a user generates a report, PhantomYerra:

  1. Collects all scan data: findings, evidence, metadata, compliance mappings, attack graph
  2. Passes the data into a Jinja2 HTML template
  3. Renders the template to HTML with all dynamic content filled in
  4. Converts the HTML to PDF using a server-side rendering engine
  5. Optionally also exports to Word (DOCX), JSON, or CSV formats

Custom templates follow the same flow. You write an HTML template with Jinja2 placeholders, place it in the templates directory, and it becomes available as a report format option in the UI.

Template Location & Structure

extensions/ my-report-template/ __init__.py # Registration templates/ executive_report.html # Main template file partials/ header.html # Reusable header partial footer.html # Reusable footer partial finding_card.html # Per-finding rendering assets/ logo.png # Custom logo style.css # Additional CSS (inlined at render time)

Registration

"""__init__.py — Register the custom report template.""" from phantomyerra.sdk.registry import report_registry def register(): report_registry.register_template( name="executive_report", display_name="Executive Summary Report", description="High-level report for C-suite and board presentations", template_path="templates/executive_report.html", format="pdf", # "pdf", "html", "docx" category="executive", # "executive", "technical", "compliance", "custom" )

Template Variables

Your Jinja2 template receives these variables, all populated automatically from scan data:

Project & Scan Metadata

VariableTypeDescription
project.namestrProject / engagement name
project.clientstrClient organization name
project.targetstrPrimary target URL or IP range
project.scopelist[str]In-scope targets and exclusions
project.start_datedatetimeEngagement start date
project.end_datedatetimeEngagement end date
project.testerslist[str]Names of assigned penetration testers
scan.idstrUnique scan identifier
scan.durationtimedeltaTotal scan duration
scan.tools_usedlist[str]Display names of tools used (branded names only)
report.generated_atdatetimeReport generation timestamp
report.versionstrPhantomYerra version that generated the report

Findings Data

VariableTypeDescription
findingslist[Finding]All findings, sorted by severity (critical first)
findings_by_severitydictFindings grouped by severity level: {"critical": [...], "high": [...], ...}
findings_by_categorydictFindings grouped by vulnerability category (e.g., injection, auth, config)
severity_countsdictCount per severity: {"critical": 2, "high": 5, "medium": 8, ...}
total_findingsintTotal number of findings
risk_scorefloatOverall risk score (0-100) computed from all findings

Per-Finding Fields

FieldDescription
finding.titleVulnerability title
finding.severitycritical / high / medium / low / info
finding.descriptionFull vulnerability description
finding.evidence.requestRaw HTTP request that triggered the finding
finding.evidence.response_bodyRelevant response content
finding.evidence.screenshotBase64-encoded screenshot (if captured)
finding.remediationSpecific remediation guidance
finding.poc_stepsNumbered reproduction steps
finding.cvss_vectorCVSS v3.1 vector string
finding.cvss_scoreComputed CVSS numeric score
finding.cwe_idCWE identifier
finding.affected_urlSpecific affected URL
finding.compliance_mapCompliance framework mappings
finding.referencesExternal reference links

Compliance & Statistics

VariableDescription
compliance.owasp_top10OWASP Top 10 mapping with finding counts per category
compliance.pci_dssPCI DSS requirement mapping
compliance.nistNIST 800-53 control mapping
compliance.iso27001ISO 27001 control mapping
compliance.hipaaHIPAA safeguard mapping
compliance.soc2SOC 2 trust service criteria mapping
attack_graphAttack path graph data (nodes and edges)
statistics.requests_sentTotal HTTP requests sent during scan
statistics.endpoints_testedNumber of unique endpoints tested
statistics.technologies_detectedDetected technology stack

Template Example

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{{ project.name }} — Security Assessment Report</title> <style> body { font-family: 'Segoe UI', sans-serif; color: #333; margin: 40px; } h1 { color: #1a1a2e; border-bottom: 3px solid #e94560; padding-bottom: 10px; } .severity-badge { padding: 4px 12px; border-radius: 4px; color: #fff; font-weight: bold; } .severity-critical { background: #dc2626; } .severity-high { background: #ea580c; } .severity-medium { background: #d97706; } .severity-low { background: #2563eb; } .severity-info { background: #6b7280; } table { width: 100%; border-collapse: collapse; margin: 20px 0; } th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #e5e7eb; } th { background: #f3f4f6; font-weight: 600; } .evidence-block { background: #f8f9fa; border: 1px solid #e5e7eb; padding: 12px; font-family: monospace; font-size: 0.85rem; white-space: pre-wrap; border-radius: 4px; margin: 10px 0; } </style> </head> <body> <h1>{{ project.name }}</h1> <p><strong>Client:</strong> {{ project.client }}</p> <p><strong>Target:</strong> {{ project.target }}</p> <p><strong>Date:</strong> {{ project.start_date|dateformat }} &mdash; {{ project.end_date|dateformat }}</p> <h2>Executive Summary</h2> <p>{{ total_findings }} vulnerabilities identified. Risk score: <strong>{{ risk_score }}/100</strong>.</p> <table> <tr><th>Severity</th><th>Count</th></tr> {% for sev, count in severity_counts.items() %} <tr><td><span class="severity-badge severity-{{ sev }}"> {{ sev|upper }}</span></td><td>{{ count }}</td></tr> {% endfor %} </table> <h2>Findings</h2> {% for finding in findings %} <div class="finding"> <h3><span class="severity-badge severity-{{ finding.severity }}"> {{ finding.severity|upper }}</span> {{ finding.title }}</h3> <p>{{ finding.description }}</p> <h4>Evidence</h4> <div class="evidence-block">{{ finding.evidence.request }}</div> <h4>Reproduction Steps</h4> <ol>{% for step in finding.poc_steps %}<li>{{ step }}</li>{% endfor %}</ol> <h4>Remediation</h4> <p>{{ finding.remediation }}</p> </div> <hr> {% endfor %} <footer><p>Generated by PhantomYerra on {{ report.generated_at|dateformat }}</p></footer> </body> </html>

Styling & Branding

Adding Compliance Sections

Use the compliance variable to add framework-specific compliance sections to your report:

<h2>OWASP Top 10 Compliance</h2> <table> <tr><th>Category</th><th>Findings</th><th>Status</th></tr> {% for category in compliance.owasp_top10 %} <tr> <td>{{ category.id }}: {{ category.name }}</td> <td>{{ category.finding_count }}</td> <td>{% if category.finding_count == 0 %}PASS{% else %}FAIL{% endif %}</td> </tr> {% endfor %} </table> <h2>PCI DSS Mapping</h2> <table> <tr><th>Requirement</th><th>Description</th><th>Findings</th></tr> {% for req in compliance.pci_dss %} <tr> <td>{{ req.requirement_id }}</td> <td>{{ req.description }}</td> <td>{{ req.findings|join(', ', attribute='title') }}</td> </tr> {% endfor %} </table>

Available Jinja2 Filters

FilterUsageDescription
dateformat{{ date|dateformat }}Formats a datetime as "YYYY-MM-DD"
datetimeformat{{ date|datetimeformat }}Formats as "YYYY-MM-DD HH:MM:SS UTC"
severity_color{{ finding.severity|severity_color }}Returns the hex color for a severity level
cvss_badge{{ finding.cvss_score|cvss_badge }}Renders a colored CVSS score badge
markdown{{ text|markdown }}Renders Markdown text as HTML
truncate{{ text|truncate(200) }}Truncates text to N characters with ellipsis
highlight_code{{ code|highlight_code('python') }}Syntax-highlights code blocks
asset_url{{ asset_url('logo.png') }}Returns the data URI for an asset file
Testing templates: Use Reports → Preview Template in the PhantomYerra UI to preview your template with sample data before running an actual scan. The preview uses realistic mock data so you can verify layout, styling, and data binding.