reptr
Write your pentest findings as Markdown. Run
reptr build. Get a polished HTML, JSON, DOCX, and PDF report.
No Docker. No database. No SaaS.
reptr is a single binary that turns a folder of Markdown files into a professional pentest report. Every finding is plain text you can commit to git, review in a pull request, and diff between assessments.
Why reptr?
Most pentest report tools are web applications — Docker, Postgres, Nginx, cloud sync. reptr is different:
| Feature | reptr | SysReptor | Ghostwriter | PwnDoc-ng | AttackForge |
|---|---|---|---|---|---|
| Install | cargo install | Docker compose | Docker + Postgres | Docker + Node + Mongo | SaaS |
| Storage | Files (git) | PostgreSQL | PostgreSQL | MongoDB | Cloud DB |
| Editor | Yours | Web UI | Web UI | Web UI | Web UI |
| Single binary | ✓ | ✗ | ✗ | ✗ | ✗ |
| Works offline | ✓ | ✓ | ✓ | ✓ | ✗ |
| Cost | Free | Free (Pro paid) | Free | Free | Paid |
reptr trades team collaboration features for git-native, single-binary simplicity. If you need real-time multi-user editing, use one of the others.
What you get
- HTML — self-contained report, default template embedded in the binary
- JSON — machine-readable snapshot for automation and diffing
- DOCX — LibreOffice-compatible Word document with embedded images
- PDF — via the
typstCLI (optional)
How it works
findings/
├── 001-sql-injection.md ← YAML front matter + Markdown body
├── 002-missing-headers.md
└── 003-idor.md
reptr build
# ✓ Parsed 3 findings
# ✓ Rendered HTML → output/acme-webapp-2026.html
# ✓ Rendered JSON → output/acme-webapp-2026.json
# Done in 8ms.
That’s it. No server to start, no database to migrate, no config files beyond reptr.toml.
Installation
Prebuilt binary (recommended)
If you have cargo-binstall installed, this grabs the prebuilt binary directly from GitHub Releases — no compilation needed:
cargo binstall reptr
From crates.io
Compiles from source using your local Rust toolchain:
cargo install reptr
From source
git clone https://github.com/vral-parmar/reptr
cd reptr
cargo install --path .
Platform support
reptr is tested on:
- macOS — Intel and Apple Silicon
- Linux — glibc (x86-64, aarch64) and musl (x86-64, aarch64)
- Windows — x86-64 MSVC
PDF output (optional)
PDF generation requires the typst CLI on your $PATH. Everything else (HTML, JSON, DOCX) works without it.
# macOS
brew install typst
# Any platform with cargo
cargo install --locked typst-cli
# Verify
typst --version
If typst is not found and you request PDF output, reptr build will error with a clear message rather than silently producing nothing.
Verify the install
reptr --version
# reptr 0.8.0
Quick start
This walks you from zero to a rendered report in under five minutes.
1. Scaffold a new engagement
reptr new acme-webapp-2026
cd acme-webapp-2026
This creates:
acme-webapp-2026/
├── reptr.toml # engagement metadata and output config
├── client.toml # client name and contacts
├── findings/
│ └── 001-example-finding.md
├── templates/ # drop custom HTML templates here
├── assets/screenshots/ # reference images from finding bodies
└── output/ # written by reptr build — do not commit
2. Add findings
reptr add finding "SQL Injection in Login Form" --severity critical
reptr add finding "Missing Security Headers" --severity low
Each command creates a numbered Markdown stub:
findings/
├── 001-example-finding.md
├── 002-sql-injection-in-login-form.md
└── 003-missing-security-headers.md
3. Edit a finding
Open any finding in your editor:
$EDITOR findings/002-sql-injection-in-login-form.md
Fill in the front matter and body:
---
id: F-002
title: SQL Injection in Login Form
severity: critical
cvss: "9.8"
cvss_vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
cwe: "CWE-89"
owasp: "A03:2021"
status: open
affected_assets:
- https://app.example.com/login
tags: [injection, p1]
---
## Description
The `/login` endpoint is vulnerable to SQL injection via the `username` parameter.
## Proof of Concept
...
## Impact / Remediation / References
4. Build the report
reptr build
# ✓ Parsed 3 findings
# ✓ Rendered HTML → output/acme-webapp-2026.html
# ✓ Rendered JSON → output/acme-webapp-2026.json
# Done in 8ms.
Open the report:
open output/acme-webapp-2026.html # macOS
xdg-open output/acme-webapp-2026.html # Linux
5. Live reload while writing
reptr watch
# Watching /path/to/acme-webapp-2026
# ... edit a finding file ...
# ✓ Rebuilt in 12ms (triggered by findings/002-sql-injection-in-login-form.md)
reptr watch rebuilds automatically whenever you save a finding, template, or config file. Keep the HTML open in a browser — just refresh after each save.
What’s next?
- Add more output formats: see Configuration
- Import findings from your library: see reptr library
- Track remediation between assessments: see reptr retest
- Enforce finding limits in CI: see Severity thresholds
reptr new
Scaffold a new engagement directory with all required files.
Usage
reptr new <NAME>
NAME becomes both the directory name and the engagement slug in reptr.toml.
Example
reptr new acme-webapp-2026
Output:
Created acme-webapp-2026/
├── reptr.toml
├── client.toml
├── findings/
│ └── 001-example-finding.md
├── templates/
├── assets/screenshots/
└── output/
Next: cd acme-webapp-2026 && reptr build
Generated files
reptr.toml
[engagement]
name = "Acme Webapp 2026"
slug = "acme-webapp-2026"
type = "External Web Application Penetration Test"
start_date = ""
end_date = ""
report_version = "1.0"
[client]
file = "client.toml"
[output]
formats = ["html", "json"]
directory = "output"
# Uncomment to enforce open-finding limits during build (useful in CI):
# [severity_thresholds]
# critical = 0 # fail if any critical finding is open
# high = 5
client.toml
name = "Acme Corp"
contact = "Jane Doe"
email = "security@acme.example"
findings/001-example-finding.md
A pre-filled stub showing every supported front matter field, with optional fields commented out so the file is valid on first build.
reptr add
Add a new finding to an engagement — either a blank stub or one imported from your finding library.
Usage
reptr add finding [OPTIONS] [TITLE]
| Option | Default | Description |
|---|---|---|
--severity | medium | One of critical, high, medium, low, info |
--from | — | Library template name (e.g. web/xss-stored) |
--path | . | Engagement root directory |
Create a blank stub
reptr add finding "SQL Injection in Login Form" --severity critical
# Created findings/002-sql-injection-in-login-form.md
The filename is auto-derived from the title (lower-cased, spaces → hyphens), prefixed with the next available sequence number. The finding ID (F-NNN) matches the sequence number.
Import from a library
reptr add finding "Stored XSS in Comments" --from web/xss-stored
# Created findings/003-stored-xss-in-comments.md
The imported file keeps the template’s severity, CVSS, CWE, body markdown, and all other fields. Only the id is freshly assigned and the title is overridden if you passed one.
If you omit TITLE, the template’s own title is used:
reptr add finding --from web/sql-injection
# Created findings/004-sql-injection.md (title from template)
See reptr library for how to manage templates.
Stub format
---
id: F-002
title: SQL Injection in Login Form
severity: critical
status: open
affected_assets: []
tags: []
# Optional — uncomment and fill in as needed:
# cvss: "0.0"
# cvss_vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N"
# cwe: "CWE-000"
# owasp: "A00:2021"
---
## Description
## Proof of Concept
## Impact
## Remediation
## References
reptr build
Parse all findings, validate them, and render every output format configured in reptr.toml.
Usage
reptr build [PATH]
PATH defaults to the current directory.
What it does
- Parse — reads
reptr.toml,client.toml, and every*.mdfile underfindings/ - Validate — runs all validation rules (see below); fails fast if any error is found
- Render — writes one file per format to
output/<slug>.<ext>
Example output
✓ Parsed 3 findings
✓ Rendered HTML → output/acme-webapp-2026.html
✓ Rendered JSON → output/acme-webapp-2026.json
Done in 8ms.
Validation rules
| Rule | Error |
|---|---|
| Unique finding IDs | Duplicate id across two files |
| Non-empty title | title field is blank or missing |
| Valid CVSS score | cvss (if present) must parse as a number between 0.0 and 10.0 |
| Valid CVSS vector | cvss_vector (if present) must be a well-formed CVSS 3.x string |
| Score/vector agreement | If both are present, the stated score must match the vector’s computed value (±0.05) |
| Non-empty slug | slug in reptr.toml must not be blank |
| Severity thresholds | Open finding counts must not exceed configured limits |
If validation fails, reptr build prints each error and exits non-zero:
✗ Validation failed:
• finding `findings/002-sqli.md` CVSS score `2.0` does not match 9.8
computed from vector `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`
— update the score or correct the vector
error: 1 validation error(s)
Output formats
Configure which formats to render in reptr.toml:
[output]
formats = ["html", "json", "docx", "pdf"]
directory = "output"
| Format | Notes |
|---|---|
html | Self-contained HTML. Uses the embedded default template unless you override it. |
json | Machine-readable engagement snapshot. Schema matches the Engagement struct. |
docx | LibreOffice-compatible Word document. Referenced images are embedded. |
pdf | Requires typst CLI on $PATH. |
CVSS auto-derivation
If a finding has cvss_vector but no cvss score, reptr build derives the score automatically:
# In the finding front matter:
cvss_vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
# cvss is derived as "7.5" — no need to compute it yourself
The derived score is written into all output formats (JSON, HTML, DOCX, PDF).
reptr watch
Build once, then auto-rebuild whenever findings, templates, or config files change.
Usage
reptr watch [PATH]
PATH defaults to the current directory.
How it works
On startup, reptr watch runs a full build — the same as reptr build. If the build fails the error is printed but watching continues.
It then watches these paths for changes:
| Path | What triggers a rebuild |
|---|---|
findings/ | Any finding file created, modified, or deleted |
templates/ | Custom template files modified |
reptr.toml | Engagement config changed |
client.toml | Client info changed |
Changes are debounced for 250 ms. If you save rapidly, reptr watch waits until saves settle before rebuilding — so you won’t get a partial-file build mid-type.
Example session
$ reptr watch
✓ Parsed 2 findings
✓ Rendered HTML → output/acme-webapp-2026.html
✓ Rendered JSON → output/acme-webapp-2026.json
Done in 8ms.
Watching /path/to/acme-webapp-2026
# ... you save a finding file ...
✓ Rebuilt in 11ms (triggered by findings/002-sqli.md)
# ... you save a template ...
✓ Rebuilt in 9ms (triggered by templates/report.html)
# ... a build fails (e.g. bad CVSS vector) ...
✗ Build failed: 1 validation error(s)
Workflow tip
Keep reptr watch running in a terminal while you write findings. Open the HTML output in a browser and refresh after each save. No server needed — the output file is written directly to disk.
# Terminal 1
reptr watch
# Terminal 2 (or your editor's integrated terminal)
$EDITOR findings/003-idor.md
reptr retest
Build the engagement and diff findings against the previous build to track remediation progress.
Usage
reptr retest [PATH]
PATH defaults to the current directory.
How it works
reptr retest reads the previous build’s JSON snapshot from output/<slug>.json, runs a fresh build, then compares the two sets of findings by ID.
First run: if no previous JSON exists, reptr retest behaves identically to reptr build — it establishes a baseline. No delta files are written.
Subsequent runs: computes the diff and writes:
output/<slug>-retest.json— machine-readable deltaoutput/<slug>-retest.html— human-readable HTML report
Change types
| Type | Meaning |
|---|---|
new | Finding appears for the first time (not in previous build) |
removed | Finding was in previous build but is gone now |
resolved | Status changed from open, accepted, or false_positive → resolved |
regressed | Status changed from resolved → open |
changed | Any other status or severity shift |
unchanged | No change detected |
Example
# Establish a baseline
reptr retest
# After the client remediates findings, update the finding files and run again
reptr retest
# ── Retest Delta ─────────────────────────────────────────
# 2 new · 3 resolved · 1 regressed · 0 changed · 0 removed · 4 unchanged
#
# [C] F-001 SQL Injection in Login Form ✓ open → resolved
# [H] F-003 Stored XSS in Comments ✓ open → resolved
# [L] F-005 Missing HSTS Header ✓ open → resolved
# [C] F-007 SSRF in File Upload ↩ resolved → open
# [H] F-008 Broken Object-Level Auth + New
# [M] F-009 Verbose Error Messages + New
Delta JSON schema
{
"engagement_name": "Acme Web Application Assessment",
"generated_at": "2026-05-23T10:00:00Z",
"new_count": 2,
"resolved_count": 3,
"regressed_count": 1,
"changed_count": 0,
"removed_count": 0,
"unchanged_count": 4,
"deltas": [
{
"id": "F-001",
"title": "SQL Injection in Login Form",
"severity": "critical",
"change_type": "resolved",
"label": "open → resolved",
"before_status": "open",
"after_status": "resolved"
}
]
}
Typical remediation workflow
Initial assessment
└── reptr retest ← establishes baseline
Client remediates findings
└── Update status: resolved in each fixed finding
└── reptr retest ← shows resolved/regressed/new
Verification assessment
└── Update findings again
└── reptr retest ← final delta for the report
reptr stats
Summarise findings across all engagements under a directory.
Usage
reptr stats [PATH] [--format text|json]
PATH defaults to the current directory.
How it works
reptr stats walks the immediate subdirectories of PATH. Any directory containing a reptr.toml is treated as an engagement and its findings are parsed and counted.
It also works when called from inside a single engagement directory — it produces a one-row table for that engagement.
Example: text output (default)
reptr stats ~/engagements
Engagements under /Users/you/engagements (3 engagements)
engagement crit high med low info total open
acme-webapp-2026 2 4 3 1 0 10 8
contoso-mobile-2026 1 2 1 0 0 4 4
globex-api-2026 0 3 5 2 0 10 6
───────────────────────────────────────────────────────────────────────
TOTAL 3 9 9 3 0 24 18
Example: JSON output
reptr stats ~/engagements --format json
{
"engagements": [
{
"slug": "acme-webapp-2026",
"name": "Acme Web Application Assessment",
"counts": {
"critical": 2,
"high": 4,
"medium": 3,
"low": 1,
"info": 0
},
"total": 10,
"open": 8,
"resolved": 2
}
],
"totals": {
"engagements": 3,
"total": 24,
"open": 18,
"resolved": 6,
"counts": {
"critical": 3,
"high": 9,
"medium": 9,
"low": 3,
"info": 0
}
}
}
The JSON schema is stable and suitable for piping into other tools, dashboards, or scripts.
reptr library
Manage your personal finding library — a collection of reusable finding templates.
Usage
reptr library list [--format text|json]
reptr library add <PATH>
reptr library remove <NAME>
reptr library show <NAME>
What is the library?
The library is a directory of Markdown finding files stored at ~/.config/reptr/library/. Each file is a fully-formed finding template you can import into any engagement with reptr add finding --from <name>.
Listing templates
reptr library list
Library templates (5)
web/sql-injection SQL Injection critical
web/xss-stored Stored Cross-Site Scripting high
web/xss-reflected Reflected Cross-Site Scripting high
web/idor Broken Object-Level Auth high
infra/default-credentials Default Credentials medium
JSON output:
reptr library list --format json
[
{
"name": "web/sql-injection",
"title": "SQL Injection",
"severity": "critical",
"path": "~/.config/reptr/library/web/sql-injection.md"
}
]
Adding a template
reptr library add findings/001-sql-injection.md --name web/sql-injection
The file is copied into the library under the given name. Subdirectory separators (the / in web/sql-injection) create folders in the library — useful for organising templates by category.
If --name is omitted the template is stored using the source file’s stem.
Removing a template
reptr library remove web/sql-injection
# Removed web/sql-injection
Showing a template
reptr library show web/sql-injection
Prints the raw Markdown source of the template, including front matter.
Using a template in an engagement
reptr add finding --from web/sql-injection
# Created findings/002-sql-injection.md
See reptr add for full options.
Sharing libraries
The library directory is plain Markdown files under ~/.config/reptr/library/. You can version-control it in a separate git repository and symlink or copy it across machines:
# Clone your team's shared library
git clone https://github.com/your-org/reptr-library ~/.config/reptr/library
# Pull updates before a new engagement
cd ~/.config/reptr/library && git pull
Writing findings
Each finding is a Markdown file inside the findings/ directory of your engagement. The file has a YAML front matter block followed by free-form Markdown.
File naming
Files are named with a numeric prefix followed by a slug:
findings/001-sql-injection.md
findings/002-stored-xss.md
findings/003-idor.md
The prefix determines display order. reptr add finding assigns the next available number automatically.
Front matter reference
---
id: F-001 # Required. Unique identifier within the engagement.
title: SQL Injection in Login Form # Required. Human-readable title.
severity: critical # Required. One of: critical high medium low info
status: open # Required. One of: open resolved accepted false_positive
# --- Optional fields ---
affected_assets: # List of affected systems or URLs.
- https://example.com/login
- https://example.com/api/auth
tags: # Free-form tags for filtering.
- injection
- authentication
cvss: "9.8" # CVSS 3.x score as a string (0.0–10.0).
cvss_vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" # CVSS 3.x vector.
cwe: "CWE-89" # CWE identifier.
owasp: "A03:2021" # OWASP Top 10 category.
---
Severity values
| Value | When to use |
|---|---|
critical | Direct, high-impact exploitation with no user interaction (RCE, SQLi with data exfiltration) |
high | Significant impact but requires some conditions (auth bypass, stored XSS) |
medium | Limited impact or requires specific conditions (CSRF, open redirect) |
low | Minimal direct impact (verbose errors, weak headers) |
info | Observations and hardening recommendations |
Status values
| Value | Meaning |
|---|---|
open | Finding is confirmed and unresolved |
resolved | Client has fixed the issue; verified by tester |
accepted | Client acknowledges and accepts the risk |
false_positive | Initially flagged but confirmed not exploitable |
reptr retest tracks transitions between these states.
CVSS auto-derivation
If you supply cvss_vector but omit cvss, reptr computes the score automatically:
cvss_vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
# cvss is derived as "7.5" — no manual calculation needed
If you supply both, reptr validates they agree (within ±0.05). Mismatches are a build error.
Body sections
The Markdown body is rendered directly into the report. Use any standard Markdown. Common sections:
## Description
Explain what the vulnerability is, where it was found, and why it matters.
## Proof of Concept
Step-by-step reproduction. Include request/response snippets, screenshots, or payload examples.
## Impact
Business impact if exploited — what data, systems, or users are at risk.
## Remediation
Specific, actionable guidance for the developer fixing this issue.
## References
- [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection)
- [CWE-89](https://cwe.mitre.org/data/definitions/89.html)
These sections are conventional — you can add, remove, or rename them freely. Custom templates can render whatever fields they need.
Images
Embed images with standard Markdown:

Paths are relative to the finding file. Images are embedded in DOCX output.
Example finding
---
id: F-001
title: SQL Injection in Login Form
severity: critical
status: open
affected_assets:
- https://example.com/login
tags:
- injection
cvss_vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
cwe: "CWE-89"
owasp: "A03:2021"
---
## Description
The login form at `/login` is vulnerable to SQL injection via the `username` parameter.
Input is concatenated directly into a SQL query without sanitisation or parameterisation.
## Proof of Concept
1. Navigate to `https://example.com/login`
2. Enter `' OR 1=1--` as the username and any value as the password
3. Observe successful authentication without valid credentials
```http
POST /login HTTP/1.1
Host: example.com
username=%27+OR+1%3D1--&password=anything
Impact
An attacker can bypass authentication, extract the full user database, and potentially
achieve remote code execution via xp_cmdshell (if running MSSQL).
Remediation
Use parameterised queries or a prepared statement library. Never concatenate user input into SQL strings.
References
Configuration
reptr uses two TOML files in the engagement root.
reptr.toml
The engagement configuration file. Created by reptr new.
[engagement]
slug = "acme-webapp-2026"
name = "Acme Web Application Assessment"
date = "2026-05-01"
report_date = "2026-05-23"
version = "1.0"
client = "acme" # matches the [client] section in client.toml
[output]
formats = ["html", "json"] # html | json | docx | pdf
directory = "output"
# Uncomment to enforce open-finding limits during build (useful in CI):
# [severity_thresholds]
# critical = 0 # fail if any critical finding is open
# high = 5 # fail if more than 5 high findings are open
# medium = 10
# low = 20
[engagement] fields
| Field | Type | Description |
|---|---|---|
slug | string | URL/filename-safe identifier. Must be non-empty. |
name | string | Full engagement name, appears in report headers. |
date | string | Engagement start date (ISO 8601, e.g. 2026-05-01). |
report_date | string | Report issue date. |
version | string | Report version string (e.g. 1.0, 1.1-draft). |
client | string | Must match the client slug in client.toml. |
[output] fields
| Field | Type | Description |
|---|---|---|
formats | array | Output formats to generate. See reptr build. |
directory | string | Directory to write output files. Default: output. |
[severity_thresholds] fields
All fields are optional. When set, the value is a count limit: build fails if the number of open findings of that severity exceeds the limit. 0 means fail if any open finding of that severity exists.
| Field | Type | Description |
|---|---|---|
critical | integer | Max allowed open critical findings. |
high | integer | Max allowed open high findings. |
medium | integer | Max allowed open medium findings. |
low | integer | Max allowed open low findings. |
See Severity thresholds for full details and CI usage.
client.toml
Client contact and branding information. Created by reptr new.
[client]
slug = "acme"
name = "Acme Corporation"
contact_name = "Jane Smith"
contact_email = "jsmith@acme.example"
logo = "assets/acme-logo.png" # optional, relative to engagement root
[client] fields
| Field | Type | Description |
|---|---|---|
slug | string | Short identifier. Must match the client field in reptr.toml. |
name | string | Full legal name of the client organisation. |
contact_name | string | Primary contact name, used in report cover pages. |
contact_email | string | Primary contact email. |
logo | string | Optional path to a logo image (PNG/SVG). Embedded in HTML and DOCX output. |
Environment variables
| Variable | Description |
|---|---|
REPTR_LIBRARY | Override the library directory (default: ~/.config/reptr/library). |
REPTR_TEMPLATE_DIR | Override the directory searched for custom templates. |
Custom templates
Place custom templates in the templates/ subdirectory of your engagement. See Custom templates.
Custom templates
reptr ships with a built-in HTML template. You can override it — or add new formats — by placing template files in the templates/ directory of your engagement.
How template resolution works
When rendering output, reptr looks for a template in this order:
templates/<format>.html(or.jinja) in the engagement root- The embedded default template
If a custom template is found it is used in place of the default. reptr watch automatically rebuilds when files in templates/ change.
Template engine
Templates use MiniJinja — a Rust port of Jinja2. Most Jinja2 syntax works:
{{ variable }}— output a value{% for ... %}/{% endfor %}— loops{% if ... %}/{% endif %}— conditionals{{ value | filter }}— filters
Context variables
The following variables are available in every template.
engagement
The top-level engagement object.
| Variable | Type | Description |
|---|---|---|
engagement.slug | string | Engagement slug from reptr.toml |
engagement.name | string | Full engagement name |
engagement.date | string | Engagement start date |
engagement.report_date | string | Report issue date |
engagement.version | string | Report version |
client
| Variable | Type | Description |
|---|---|---|
client.name | string | Client organisation name |
client.contact_name | string | Primary contact name |
client.contact_email | string | Primary contact email |
client.logo | string or null | Path to logo image (base64-encoded in HTML output) |
findings
A list of finding objects, sorted by severity then ID.
| Variable | Type | Description |
|---|---|---|
finding.id | string | Finding ID (e.g. F-001) |
finding.title | string | Finding title |
finding.severity | string | critical, high, medium, low, or info |
finding.status | string | open, resolved, accepted, or false_positive |
finding.affected_assets | list of strings | Affected hosts or URLs |
finding.tags | list of strings | Finding tags |
finding.cvss | string or null | CVSS score (e.g. "9.8") |
finding.cvss_vector | string or null | CVSS 3.x vector string |
finding.cwe | string or null | CWE identifier |
finding.owasp | string or null | OWASP Top 10 category |
finding.body_html | string | Rendered HTML from the Markdown body |
summary
Pre-computed counts for convenience.
| Variable | Type | Description |
|---|---|---|
summary.total | integer | Total number of findings |
summary.open | integer | Open findings |
summary.resolved | integer | Resolved findings |
summary.critical | integer | Critical findings (all statuses) |
summary.high | integer | High findings |
summary.medium | integer | Medium findings |
summary.low | integer | Low findings |
summary.info | integer | Info findings |
Example: minimal HTML template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ engagement.name }} — Security Assessment</title>
</head>
<body>
<h1>{{ engagement.name }}</h1>
<p>Client: {{ client.name }} · Date: {{ engagement.report_date }}</p>
<p>{{ summary.total }} findings ({{ summary.open }} open)</p>
{% for finding in findings %}
<section id="{{ finding.id }}">
<h2>[{{ finding.severity | upper }}] {{ finding.id }}: {{ finding.title }}</h2>
<p>Status: {{ finding.status }}</p>
{{ finding.body_html }}
</section>
{% endfor %}
</body>
</html>
Save this as templates/report.html in your engagement root, then run reptr build.
Example: severity badge filter
MiniJinja lets you define macros for repeated patterns:
{% macro severity_badge(s) %}
<span class="badge badge-{{ s }}">{{ s | upper }}</span>
{% endmacro %}
{% for finding in findings %}
{{ severity_badge(finding.severity) }} {{ finding.title }}
{% endfor %}
Tips
- Access the built-in template source by running
reptr build --dump-template(outputs the embedded HTML to stdout — useful as a starting point). - Keep images in
assets/and reference them with relative paths. reptr inlines them as base64 in HTML output so the file is self-contained. - The
body_htmlfield already contains rendered, sanitised HTML. Do not double-escape it — use{{ finding.body_html | safe }}if your Jinja environment escapes by default.
Severity thresholds
Severity thresholds let you enforce finding limits as part of reptr build. When a threshold is exceeded the build exits non-zero — making it suitable as a CI gate.
Configuration
Add a [severity_thresholds] section to reptr.toml:
[severity_thresholds]
critical = 0 # fail if any open critical finding exists
high = 5 # fail if more than 5 open high findings exist
medium = 10
low = 20
Any combination of fields is valid. Omitting a field means no limit is enforced for that severity.
Semantics
The threshold value is a maximum allowed count of open findings at that severity.
| Value | Meaning |
|---|---|
0 | Fail if even one open finding of this severity exists |
N | Fail if more than N open findings of this severity exist |
| (absent) | No limit — any count is allowed |
Only findings with status: open count against thresholds. resolved, accepted, and false_positive findings are ignored.
Build output when thresholds are exceeded
✗ Validation failed:
• 2 open critical finding(s) exceed the allowed limit of 0
— resolve them or raise [severity_thresholds].critical in reptr.toml
• 7 open high finding(s) exceed the allowed limit of 5
— resolve them or raise [severity_thresholds].high in reptr.toml
error: 2 validation error(s)
Common patterns
Zero-tolerance for critical findings
Fail the build if any critical finding remains open:
[severity_thresholds]
critical = 0
Graduated enforcement
Gradually tighten thresholds as an engagement progresses:
# Initial assessment — no limits yet
# [severity_thresholds]
# Mid-remediation — critical must be resolved
# [severity_thresholds]
# critical = 0
# Pre-delivery — critical and high must be resolved
[severity_thresholds]
critical = 0
high = 0
Informational gate only
Use thresholds in CI to block merging until findings are resolved, without blocking the report build during the engagement itself:
# reptr.toml (engagement config — no thresholds during engagement)
[output]
formats = ["html", "json"]
# .github/workflows/ci.yml (CI applies thresholds via a separate command)
- run: |
# Patch thresholds just for the CI check
cat >> reptr.toml <<'EOF'
[severity_thresholds]
critical = 0
EOF
reptr build
Using thresholds in CI
See CI integration for complete GitHub Actions examples.
CI integration
reptr works well as a CI step. The build exits non-zero on any validation error, making it easy to enforce quality gates in GitHub Actions or any other CI system.
Basic setup
Install reptr in CI
- name: Install reptr
run: cargo install reptr --locked
For faster installs, use cargo-binstall to download a pre-built binary:
- name: Install cargo-binstall
uses: cargo-bins/cargo-binstall@main
- name: Install reptr
run: cargo binstall reptr --no-confirm
Run a build
- name: Build report
run: reptr build path/to/engagement
If the build passes, all outputs are written to output/. If it fails (validation errors, threshold violations), the step exits non-zero and the job fails.
Full GitHub Actions workflow
# .github/workflows/reptr.yml
name: reptr build
on:
push:
paths:
- 'engagements/**'
pull_request:
paths:
- 'engagements/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ runner.os }}-cargo-reptr
- name: Install reptr
run: cargo install reptr --locked
- name: Build all engagements
run: |
for dir in engagements/*/; do
if [ -f "$dir/reptr.toml" ]; then
echo "Building $dir"
reptr build "$dir"
fi
done
- name: Upload reports
uses: actions/upload-artifact@v4
with:
name: reports
path: engagements/*/output/
Threshold enforcement in CI
Add [severity_thresholds] to reptr.toml to fail the build when too many findings remain open:
[severity_thresholds]
critical = 0 # block merge if any critical finding is unresolved
high = 0
The CI output will clearly state which thresholds were exceeded:
✗ Validation failed:
• 1 open critical finding(s) exceed the allowed limit of 0
— resolve them or raise [severity_thresholds].critical in reptr.toml
error: 1 validation error(s)
Storing reports as release assets
Tag-based releases can attach HTML/JSON reports:
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... install reptr ...
- run: reptr build
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: output/*.html
Retest in CI
Run reptr retest instead of reptr build to automatically generate a delta report comparing the current findings against the previous build:
- name: Retest
run: reptr retest
- name: Upload delta report
uses: actions/upload-artifact@v4
with:
name: retest-delta
path: output/*-retest.*
The delta JSON and HTML files show which findings were resolved, regressed, or added since the last run. See reptr retest for full details.
Typst / PDF output
PDF generation requires typst on $PATH. Install it in CI with:
- name: Install typst
uses: typst-community/setup-typst@v4
Then add pdf to your formats list in reptr.toml:
[output]
formats = ["html", "json", "pdf"]