Custom Report Templates
Design custom report layouts with your organization's branding, compliance sections, and data presentation. PhantomYerra's reporting engine uses Jinja2 templates with full access to scan data, findings, evidence, and project metadata.
How the Report Engine Works
When a user generates a report, PhantomYerra:
- Collects all scan data: findings, evidence, metadata, compliance mappings, attack graph
- Passes the data into a Jinja2 HTML template
- Renders the template to HTML with all dynamic content filled in
- Converts the HTML to PDF using a server-side rendering engine
- 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
| Variable | Type | Description |
|---|---|---|
project.name | str | Project / engagement name |
project.client | str | Client organization name |
project.target | str | Primary target URL or IP range |
project.scope | list[str] | In-scope targets and exclusions |
project.start_date | datetime | Engagement start date |
project.end_date | datetime | Engagement end date |
project.testers | list[str] | Names of assigned penetration testers |
scan.id | str | Unique scan identifier |
scan.duration | timedelta | Total scan duration |
scan.tools_used | list[str] | Display names of tools used (branded names only) |
report.generated_at | datetime | Report generation timestamp |
report.version | str | PhantomYerra version that generated the report |
Findings Data
| Variable | Type | Description |
|---|---|---|
findings | list[Finding] | All findings, sorted by severity (critical first) |
findings_by_severity | dict | Findings grouped by severity level: {"critical": [...], "high": [...], ...} |
findings_by_category | dict | Findings grouped by vulnerability category (e.g., injection, auth, config) |
severity_counts | dict | Count per severity: {"critical": 2, "high": 5, "medium": 8, ...} |
total_findings | int | Total number of findings |
risk_score | float | Overall risk score (0-100) computed from all findings |
Per-Finding Fields
| Field | Description |
|---|---|
finding.title | Vulnerability title |
finding.severity | critical / high / medium / low / info |
finding.description | Full vulnerability description |
finding.evidence.request | Raw HTTP request that triggered the finding |
finding.evidence.response_body | Relevant response content |
finding.evidence.screenshot | Base64-encoded screenshot (if captured) |
finding.remediation | Specific remediation guidance |
finding.poc_steps | Numbered reproduction steps |
finding.cvss_vector | CVSS v3.1 vector string |
finding.cvss_score | Computed CVSS numeric score |
finding.cwe_id | CWE identifier |
finding.affected_url | Specific affected URL |
finding.compliance_map | Compliance framework mappings |
finding.references | External reference links |
Compliance & Statistics
| Variable | Description |
|---|---|
compliance.owasp_top10 | OWASP Top 10 mapping with finding counts per category |
compliance.pci_dss | PCI DSS requirement mapping |
compliance.nist | NIST 800-53 control mapping |
compliance.iso27001 | ISO 27001 control mapping |
compliance.hipaa | HIPAA safeguard mapping |
compliance.soc2 | SOC 2 trust service criteria mapping |
attack_graph | Attack path graph data (nodes and edges) |
statistics.requests_sent | Total HTTP requests sent during scan |
statistics.endpoints_tested | Number of unique endpoints tested |
statistics.technologies_detected | Detected 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 }} —
{{ 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
- Inline CSS — All styles should be inline or in a
<style>block within the template. External CSS files are automatically inlined during rendering. - Custom logo — Place your logo image in the
assets/directory. Reference it with{{ asset_url('logo.png') }}in the template. - Page breaks — Use
style="page-break-before: always;"to force page breaks in PDF output. - Headers/footers — PDF headers and footers can be configured separately. Use
@pageCSS rules for running headers. - Charts — Use inline SVG for charts. The
severity_chart_svghelper generates a severity distribution pie chart automatically. - Color palette — Use the standard severity colors for consistency: Critical (#dc2626), High (#ea580c), Medium (#d97706), Low (#2563eb), Info (#6b7280).
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
| Filter | Usage | Description |
|---|---|---|
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.