How to Customize VulnClaw's Report Generation Template: 4 Proven Methods

VulnClaw uses Jinja2 templates defined as string literals in vulnclaw/report/generator.py to render penetration-test reports, allowing customization by editing the source template, loading external files, or extending the rendering context.

VulnClaw generates security assessment reports using embedded Jinja2 templates that transform scan data into formatted Markdown or HTML output. If you need to modify report branding, add custom fields, or completely restructure the output layout, you must interact with the template system located in the core generator module. This guide shows you how to customize VulnClaw report generation templates using four distinct approaches based on the actual source code implementation.

Where Report Templates Live in VulnClaw

VulnClaw maintains two primary report templates as Python string constants inside vulnclaw/report/generator.py. The REPORT_TEMPLATE variable (starting at line 16) handles standard per-target reports, while CYCLE_REPORT_TEMPLATE (starting at line 374) generates persistent weekly cycle reports.

When the CLI or Web API triggers report generation, the generate_report() or generate_persistent_cycle_report() function loads the appropriate template, injects a context dictionary containing variables like target, verified_count, and recommendations, and calls jinja2.Template.render(). The resulting string is written as Markdown (*.md) or wrapped in a minimal HTML wrapper (lines 410-416) when report_format="html".

How to Customize VulnClaw Report Templates

Method 1: Edit the Inline Template String

The fastest way to customize VulnClaw report generation templates is modifying the string literal directly in vulnclaw/report/generator.py. For example, to add a report author field after the project overview:


# In vulnclaw/report/generator.py

REPORT_TEMPLATE = """\

# 渗透测试报告

## 1. 项目概述

| 项目 | 详情 |
|------|------|
| **测试目标** | {{ target }} |
| **报告作者** | {{ author }} |
| **测试时间** | {{ started_at }} |
...
"""

You must then extend the context dictionary in generate_report() (around line 68) to supply the new variable:

context = {
    "target": session.target or "unknown",
    "author": "Security Team <security@example.com>",
    "started_at": session.started_at,
    # ... other variables

}

Method 2: Load an External Template File

For maintainability, load a custom template file at runtime without modifying the source constants. Create a file named custom_report_template.md and patch generate_report() (around line 300) to check for an environment variable:

import os
from jinja2 import Template

template_path = os.getenv("VULNCLAW_REPORT_TEMPLATE")
if template_path and os.path.isfile(template_path):
    with open(template_path, "r", encoding="utf-8") as f:
        template_src = f.read()
else:
    template_src = REPORT_TEMPLATE

template = Template(template_src)
report_content = template.render(**context)

Run VulnClaw with your custom template:

export VULNCLAW_REPORT_TEMPLATE=/path/to/custom_report_template.md
vulnclaw report generate --target 10.0.0.1

Method 3: Extend the Rendering Context

To expose new data fields from the API to the template, extend the ReportGenerateRequest schema in vulnclaw/web/schemas.py (line 168):

class ReportGenerateRequest(BaseModel):
    target: str
    format: Literal["markdown", "html"] = "markdown"
    custom_note: Optional[str] = None  # New field

Pass this value through the web handler in vulnclaw/web/app.py (line 215):

async def report_target(request: ReportGenerateRequest):
    report_path = generate_report(
        session, 
        report_format=request.format,
        custom_note=request.custom_note
    )
    # ...

Update generate_report() to accept and inject the parameter:

def generate_report(
    session: SessionState,
    output_path: Optional[str] = None,
    llm_attack_summary: str = "",
    report_format: str = "markdown",
    target_state_context: Optional[dict[str, Any]] = None,
    custom_note: Optional[str] = None,  # New parameter

) -> Path:
    context = {
        # ... existing variables

        "custom_note": custom_note or "",
    }
    # ...

Your template can now access {{ custom_note }} to display user-defined content.

Method 4: Change the Output Format

VulnClaw supports both Markdown and HTML output without template modifications. The report_format parameter controls whether the generator produces raw Markdown or wraps content in HTML. Set the format via CLI:

vulnclaw report generate --target 10.0.0.1 --format html

Or specify "html" in the ReportGenerateRequest payload when using the Web API.

Verifying Your Template Changes

Test your modifications using the VulnClaw CLI:

vulnclaw report generate --target 10.0.0.1 --format markdown

For Web UI verification, navigate to the Risk Results page and click the "Generate Report" button (handled in frontend/src/pages/RiskResultsPage.tsx at line 179). The frontend calls generateTargetReport(target, reportFormat), and your customized template will render immediately after a page refresh.

Summary

  • Template Location: VulnClaw stores Jinja2 templates in vulnclaw/report/generator.py as REPORT_TEMPLATE and CYCLE_REPORT_TEMPLATE.
  • Inline Editing: Modify string literals directly and update the context dictionary in generate_report() to inject new variables.
  • External Files: Use the VULNCLAW_REPORT_TEMPLATE environment variable to load template files without touching source code.
  • Context Extension: Add fields to ReportGenerateRequest in vulnclaw/web/schemas.py and thread them through vulnclaw/web/app.py to expose API data to templates.
  • Format Options: Set report_format to "markdown" or "html" to control output wrapping behavior.

Frequently Asked Questions

Where are VulnClaw report templates stored?

VulnClaw report templates are stored as string literals inside vulnclaw/report/generator.py. The standard template is defined in the REPORT_TEMPLATE variable starting at line 16, and the persistent cycle report template is CYCLE_REPORT_TEMPLATE starting at line 374.

Can I use an external file instead of editing the source code?

Yes. Create a custom template file and set the VULNCLAW_REPORT_TEMPLATE environment variable to its absolute path. Modify generate_report() in vulnclaw/report/generator.py (around line 300) to check for this variable and load the file content using open() with UTF-8 encoding before falling back to the default template.

How do I add custom variables to my VulnClaw report template?

First, add the field to ReportGenerateRequest in vulnclaw/web/schemas.py (line 168). Then pass the value from the request handler in vulnclaw/web/app.py (line 215) into the generate_report() function. Finally, update the generate_report() signature to accept the parameter and include it in the context dictionary passed to template.render().

Does VulnClaw support HTML report generation?

Yes. VulnClaw supports both Markdown and HTML output formats. Set report_format="html" in your API request or use the --format html CLI flag. When HTML is selected, the generator wraps the rendered Markdown content in a minimal HTML wrapper (implemented between lines 410-416 in vulnclaw/report/generator.py).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →