Report Template

Pentest Report Template Guide

Guide for creating Word document (.docx) templates for penetration test reports using python-docx-template (docxtpl) with Jinja2 syntax.


Overview

The report generator fetches data and renders it into Word templates using Jinja2 templating. Templates are stored in S3 and downloaded at generation time.


Template Syntax

python-docx-template uses Jinja2 syntax with special delimiters for Word documents:

  • Variables: {{ variable_name }}
  • Loops: {%tr for item in items %}...{%tr endfor %} (table rows)
  • Conditionals: {% if condition %}...{% endif %}
  • Paragraph loops: {%p for item in items %}...{%p endfor %}

Important Notes

  • Use {%tr ... %} for table row loops (the tr indicates table row)
  • Use {%p ... %} for paragraph loops
  • Use {% ... %} for inline control structures
  • Variables with {{ }} can be placed anywhere in text

Available Template Variables

Report Metadata

  • {{ organization_name }} - Organization name
  • {{ report_date }} - Report date (Month Year format)
  • {{ generated_at }} - Full timestamp
  • {{ report_type }} - Type of report

Attack Surface Totals

Zone-Based Assets:

  • {{ total_hosts }} - Total number of hosts discovered
  • {{ total_services }} - Total number of services discovered
  • {{ total_web_apps }} - Total number of web applications
  • {{ total_infrastructure }} - Total infrastructure devices

Cloud Assets:

  • {{ total_s3_buckets }} - AWS S3 buckets
  • {{ total_elbs }} - AWS Elastic Load Balancers
  • {{ total_cloudfront }} - AWS CloudFront distributions
  • {{ total_app_services }} - Azure App Services
  • {{ total_storage_accounts }} - Azure Storage Accounts
  • {{ total_github_repos }} - GitHub repositories
  • {{ total_azure_devops_repos }} - Azure DevOps repositories
  • {{ total_agents }} - Endpoint agents

Findings Summary

  • {{ critical_count }} - Number of critical findings
  • {{ high_count }} - Number of high findings
  • {{ medium_count }} - Number of medium findings
  • {{ low_count }} - Number of low findings
  • {{ info_count }} - Number of informational findings
  • {{ total_findings }} - Total findings count
  • {{ manual_findings_count }} - Number of manual findings

List/Table Variables

Attack Surface by Zone

{%tr for zone in attack_surface %}
{{ zone.zone_name }}
{{ zone.infrastructure_count }}
{{ zone.hosts_count }}
{{ zone.services_count }}
{{ zone.web_apps_count }}
{%tr endfor %}

Fields available:

  • zone.zone_name - Zone name
  • zone.infrastructure_count - Infrastructure device count
  • zone.hosts_count - Host count
  • zone.services_count - Service count
  • zone.web_apps_count - Web application count

Findings by Zone

This table includes both zone-based findings and cloud asset findings. Cloud assets (S3, CloudFront, ELBs, Azure App Services, Storage Accounts, GitHub repos, Azure DevOps repos, Agents) appear as additional rows with their asset type as the zone_name.

{%tr for zone in findings_by_zone %}
{{ zone.zone_name }}
{{ zone.critical_count }}
{{ zone.high_count }}
{{ zone.medium_count }}
{{ zone.low_count }}
{%tr endfor %}

Fields available:

  • zone.zone_name - Zone name or cloud asset type (e.g., "AWS S3 Buckets", "GitHub Repositories")
  • zone.critical_count - Critical findings count
  • zone.high_count - High findings count
  • zone.medium_count - Medium findings count
  • zone.low_count - Low findings count

Manual Findings by Zone

Same structure as Total Findings - includes both zone-based and cloud asset findings.

{%tr for zone in manual_findings_by_zone %}
{{ zone.zone_name }}
{{ zone.critical_count }}
{{ zone.high_count }}
{{ zone.medium_count }}
{{ zone.low_count }}
{%tr endfor %}

Recommendations Summary

{%tr for rec in recommendations %}
{{ rec.recommendation }}
{{ rec.risk }}
{{ rec.percentage }}%
{%tr endfor %}

Fields available:

  • rec.recommendation - Recommendation category
  • rec.risk - Risk level in Title Case (Critical, High, Medium, Low)
  • rec.percentage - Percentage of findings this addresses

Finding Details

Findings are deduplicated by vulnerability - multiple instances of the same vulnerability across different assets are grouped together with all affected assets listed.

{%p for finding in critical_findings %}

{{ finding.vulnerability_name }}

Severity: Critical
CVSS Score: {{ finding.base_score }}
Exploitable: {% if finding.vulnerability_exploitable %}Yes{% else %}No{% endif %}
Compromised: {{ finding.compromised }}

Description
{{ finding.vulnerability_summary }}

Remediation
{%p for rem in finding.remediations %}
{{ rem.description }}
{%p endfor %}

Vulnerable Assets
{%p for asset in finding.affected_assets %}
{{ asset }}
{%p endfor %}

{%p endfor %}

Available Finding Fields

  • finding.vulnerability_name - Name of the vulnerability
  • finding.vulnerability_id - Unique vulnerability ID
  • finding.vulnerability_summary - Description of the vulnerability
  • finding.severity - Numeric severity (0-4)
  • finding.severity_name - Severity name (Critical, High, etc.)
  • finding.base_score - CVSS base score
  • finding.overall_score - Overall CVSS score
  • finding.vulnerability_exploitable - Boolean - is it exploitable
  • finding.compromised - Compromise level achieved
  • finding.affected_assets - List of affected asset strings
  • finding.affected_asset_count - Count of affected assets
  • finding.remediations - List of remediation objects
  • finding.references - List of reference objects

Remediation Fields

  • rem.name - Remediation name
  • rem.description - Full remediation description (HTML converted to formatted text)
  • rem.category_display - Category display name
  • rem.solution_type_display - Solution type

Note on HTML Content: Remediation descriptions may contain HTML formatting including tables, lists, and code blocks. The generator automatically converts this to formatted text:

  • Bold/italic text is preserved
  • Lists are converted to bullet points
  • Code blocks use monospace font
  • Tables are rendered as aligned text with pipe separators and header separators

Reference Fields

  • ref.reference_type - Type (URL, CVE, etc.)
  • ref.reference_url - URL of the reference

Complete Template Structure Example

Below is the recommended structure for a pentest report template.

Cover Page

Initial Penetration Test Report (External)
{{ report_date }}

Prepared for: {{ organization_name }}
Prepared by: {{ company_name }}

Executive Summary

Background
[Standard background text about penetration testing...]

Objective
[Standard objective text...]

Methodology
[Standard methodology text...]

Results
{{ company_name }} tested from the following perspectives:
- Internet facing

After initial analysis of all external systems and services, it was determined
{{ total_web_apps }} web applications are exposed on the Internet along with
{{ total_hosts }} hosts and {{ total_services }} services.

Numbers Summary

| Exceptions | % w/o Critical or High | Critical | High | Medium |
|------------|------------------------|----------|------|--------|
| 0          | XX%                    | {{ critical_count }} | {{ high_count }} | {{ medium_count }} |

Recommendations

{%tr for rec in recommendations %}
| {{ rec.recommendation }} | {{ rec.risk }} | {{ rec.percentage }}% |
{%tr endfor %}

Technical Volume - Attack Surface

Attack Surface

| Location | Infrastructure | Hosts | Services | Web Apps |
|----------|---------------|-------|----------|----------|
{%tr for zone in attack_surface %}
| {{ zone.zone_name }} | {{ zone.infrastructure_count }} | {{ zone.hosts_count }} | {{ zone.services_count }} | {{ zone.web_apps_count }} |
{%tr endfor %}
| **Total** | {{ total_infrastructure }} | {{ total_hosts }} | {{ total_services }} | {{ total_web_apps }} |

Technical Volume - Findings Summary

Both tables include zone-based findings and cloud asset findings (S3, CloudFront, ELBs, Azure services, repositories, agents) in the same table.

Total Findings

| Location | Critical | High | Medium | Low |
|----------|----------|------|--------|-----|
{%tr for zone in findings_by_zone %}
| {{ zone.zone_name }} | {{ zone.critical_count }} | {{ zone.high_count }} | {{ zone.medium_count }} | {{ zone.low_count }} |
{%tr endfor %}

Manual Findings

| Location | Critical | High | Medium | Low |
|----------|----------|------|--------|-----|
{%tr for zone in manual_findings_by_zone %}
| {{ zone.zone_name }} | {{ zone.critical_count }} | {{ zone.high_count }} | {{ zone.medium_count }} | {{ zone.low_count }} |
{%tr endfor %}

Finding Details

CRITICAL RISK FINDINGS

{%p for finding in critical_findings %}
────────────────────────────────────────────────────────────
{{ finding.vulnerability_name }}

Severity: Critical
CVSS Score: {{ finding.base_score }}
Exploitable: {% if finding.vulnerability_exploitable %}Yes{% else %}No{% endif %}
Compromised: {{ finding.compromised }}

Description
{{ finding.vulnerability_summary }}

Remediation
{%p for rem in finding.remediations %}
{{ rem.description }}
{%p endfor %}

Vulnerable Assets
{%p for asset in finding.affected_assets %}
{{ asset }}
{%p endfor %}
{%p endfor %}

HIGH RISK FINDINGS

{%p for finding in high_findings %}
[Same structure as critical findings...]
{%p endfor %}

MEDIUM RISK FINDINGS

{%p for finding in medium_findings %}
[Same structure as critical findings...]
{%p endfor %}

Creating the Template in Word

  1. Create a new Word document or copy an existing report
  2. Replace static text with Jinja2 variables (e.g., replace "Examworks" with {{ organization_name }})
  3. For tables with dynamic rows:
    • Create a table with header row
    • Add one data row with variables
    • Wrap the data row with {%tr for ... %} and {%tr endfor %}
  4. For repeated sections:
    • Use {%p for ... %} at the start of a paragraph
    • Use {%p endfor %} at the end
  5. Save as .docx (not .doc)
  6. Upload to S3 template bucket

Tips

  • Keep formatting in the template - it will be preserved
  • Test with sample data first
  • Use conditional blocks to hide empty sections:
{% if critical_findings %}
CRITICAL RISK FINDINGS
{%p for finding in critical_findings %}...{%p endfor %}
{% endif %}

Environment Variables

The report generator uses these environment variables:

  • DB_HOST - PostgreSQL host(s), default: localhost
  • DB_NAME - Database name, default: cyberoptix
  • DB_USER - Database user, default: postgres
  • DB_PASSWD - Database password
  • BROKER_URL - Redis connection URL, default: redis://localhost:6379/0
  • AWS_ACCESS_KEY_ID - AWS access key
  • AWS_SECRET_ACCESS_KEY - AWS secret
  • AWS_REGION - AWS region, default: us-east-1
  • COMPANY_NAME - Your company name, default: CyberOptix
  • WORKER_THREADS - Concurrent workers, default: 4

Task Queue Message Format

The Go API sends tasks to the Redis queue with this structure:

{
  "id": "task-uuid",
  "organization_id": "org-uuid",
  "report_id": "report-uuid",
  "template_id": "template-uuid",
  "template_bucket": "cyberoptix-org-uuid-templates",
  "template_key": "pentest-report-template.docx",
  "output_bucket": "cyberoptix-org-uuid-reports",
  "name": "External Penetration Test Report",
  "generated_by": "user-uuid",
  "generated_by_name": "John Doe",
  "generation_params": "{\"filters\":{\"statuses\":[\"open\"]},\"report_type\":\"pentest\"}"
}

Generation Parameters

The generation_params JSON can include:

{
  "filters": {
    "statuses": ["open"],
    "zone_id": "optional-zone-uuid",
    "severity": [4, 3, 2],
    "manual_only": false
  },
  "report_type": "pentest",
  "custom_data": {
    "tester_name": "Avery Rozar",
    "engagement_dates": "June 17 - July 22, 2025"
  }
}

Custom data fields are merged into the template context and can be accessed directly (e.g., {{ tester_name }}).


Next Steps

  1. Create your Word template using the variables above
  2. Upload the template to your S3 templates bucket
  3. Configure template in CyberOptix platform
  4. Generate test reports to verify formatting
  5. Iterate on template design as needed

Additional Resources