Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

FeaturereptrSysReptorGhostwriterPwnDoc-ngAttackForge
Installcargo installDocker composeDocker + PostgresDocker + Node + MongoSaaS
StorageFiles (git)PostgreSQLPostgreSQLMongoDBCloud DB
EditorYoursWeb UIWeb UIWeb UIWeb UI
Single binary
Works offline
CostFreeFree (Pro paid)FreeFreePaid

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 typst CLI (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

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?

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]
OptionDefaultDescription
--severitymediumOne of critical, high, medium, low, info
--fromLibrary 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

  1. Parse — reads reptr.toml, client.toml, and every *.md file under findings/
  2. Validate — runs all validation rules (see below); fails fast if any error is found
  3. 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

RuleError
Unique finding IDsDuplicate id across two files
Non-empty titletitle field is blank or missing
Valid CVSS scorecvss (if present) must parse as a number between 0.0 and 10.0
Valid CVSS vectorcvss_vector (if present) must be a well-formed CVSS 3.x string
Score/vector agreementIf both are present, the stated score must match the vector’s computed value (±0.05)
Non-empty slugslug in reptr.toml must not be blank
Severity thresholdsOpen 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"
FormatNotes
htmlSelf-contained HTML. Uses the embedded default template unless you override it.
jsonMachine-readable engagement snapshot. Schema matches the Engagement struct.
docxLibreOffice-compatible Word document. Referenced images are embedded.
pdfRequires 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:

PathWhat triggers a rebuild
findings/Any finding file created, modified, or deleted
templates/Custom template files modified
reptr.tomlEngagement config changed
client.tomlClient 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 delta
  • output/<slug>-retest.html — human-readable HTML report

Change types

TypeMeaning
newFinding appears for the first time (not in previous build)
removedFinding was in previous build but is gone now
resolvedStatus changed from open, accepted, or false_positiveresolved
regressedStatus changed from resolvedopen
changedAny other status or severity shift
unchangedNo 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

ValueWhen to use
criticalDirect, high-impact exploitation with no user interaction (RCE, SQLi with data exfiltration)
highSignificant impact but requires some conditions (auth bypass, stored XSS)
mediumLimited impact or requires specific conditions (CSRF, open redirect)
lowMinimal direct impact (verbose errors, weak headers)
infoObservations and hardening recommendations

Status values

ValueMeaning
openFinding is confirmed and unresolved
resolvedClient has fixed the issue; verified by tester
acceptedClient acknowledges and accepts the risk
false_positiveInitially 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:

![Request showing injection payload](../screenshots/sqli-request.png)

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

FieldTypeDescription
slugstringURL/filename-safe identifier. Must be non-empty.
namestringFull engagement name, appears in report headers.
datestringEngagement start date (ISO 8601, e.g. 2026-05-01).
report_datestringReport issue date.
versionstringReport version string (e.g. 1.0, 1.1-draft).
clientstringMust match the client slug in client.toml.

[output] fields

FieldTypeDescription
formatsarrayOutput formats to generate. See reptr build.
directorystringDirectory 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.

FieldTypeDescription
criticalintegerMax allowed open critical findings.
highintegerMax allowed open high findings.
mediumintegerMax allowed open medium findings.
lowintegerMax 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

FieldTypeDescription
slugstringShort identifier. Must match the client field in reptr.toml.
namestringFull legal name of the client organisation.
contact_namestringPrimary contact name, used in report cover pages.
contact_emailstringPrimary contact email.
logostringOptional path to a logo image (PNG/SVG). Embedded in HTML and DOCX output.

Environment variables

VariableDescription
REPTR_LIBRARYOverride the library directory (default: ~/.config/reptr/library).
REPTR_TEMPLATE_DIROverride 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:

  1. templates/<format>.html (or .jinja) in the engagement root
  2. 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.

VariableTypeDescription
engagement.slugstringEngagement slug from reptr.toml
engagement.namestringFull engagement name
engagement.datestringEngagement start date
engagement.report_datestringReport issue date
engagement.versionstringReport version

client

VariableTypeDescription
client.namestringClient organisation name
client.contact_namestringPrimary contact name
client.contact_emailstringPrimary contact email
client.logostring or nullPath to logo image (base64-encoded in HTML output)

findings

A list of finding objects, sorted by severity then ID.

VariableTypeDescription
finding.idstringFinding ID (e.g. F-001)
finding.titlestringFinding title
finding.severitystringcritical, high, medium, low, or info
finding.statusstringopen, resolved, accepted, or false_positive
finding.affected_assetslist of stringsAffected hosts or URLs
finding.tagslist of stringsFinding tags
finding.cvssstring or nullCVSS score (e.g. "9.8")
finding.cvss_vectorstring or nullCVSS 3.x vector string
finding.cwestring or nullCWE identifier
finding.owaspstring or nullOWASP Top 10 category
finding.body_htmlstringRendered HTML from the Markdown body

summary

Pre-computed counts for convenience.

VariableTypeDescription
summary.totalintegerTotal number of findings
summary.openintegerOpen findings
summary.resolvedintegerResolved findings
summary.criticalintegerCritical findings (all statuses)
summary.highintegerHigh findings
summary.mediumintegerMedium findings
summary.lowintegerLow findings
summary.infointegerInfo 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_html field 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.

ValueMeaning
0Fail if even one open finding of this severity exists
NFail 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"]