reptr 0.8.0

Local-first pentest report generator: Markdown findings -> HTML/JSON/DOCX/PDF.
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ engagement.meta.name }}</title>
  <meta name="generator" content="reptr">
  <meta name="generated" content="{{ generated_at }}">
  <style>
    :root {
      --fg: #1c1c1f;
      --bg: #ffffff;
      --muted: #6b6b73;
      --rule: #e4e4ea;
      --accent: #4a3ae0;
      --crit: #b00020;
      --high: #c2410c;
      --med:  #b45309;
      --low:  #2563eb;
      --info: #4b5563;
    }
    @media (prefers-color-scheme: dark) {
      :root {
        --fg: #ececf1;
        --bg: #16161a;
        --muted: #9b9ba3;
        --rule: #2a2a31;
        --accent: #a594ff;
      }
    }
    html { background: var(--bg); color: var(--fg); }
    body {
      font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
      max-width: 880px; margin: 3rem auto; padding: 0 1.25rem;
    }
    h1, h2, h3, h4 { line-height: 1.25; }
    h1 { font-size: 2rem; margin: 0 0 .25rem; }
    .subtitle { color: var(--muted); margin: 0 0 2.5rem; }
    .meta { display: grid; grid-template-columns: max-content 1fr; gap: .25rem 1rem; margin: 1.5rem 0; font-size: .92rem; }
    .meta dt { color: var(--muted); }
    table { border-collapse: collapse; width: 100%; font-size: .95rem; }
    th, td { text-align: left; padding: .5rem .65rem; border-bottom: 1px solid var(--rule); }
    th { font-weight: 600; color: var(--muted); }
    .sev { font-weight: 600; text-transform: uppercase; font-size: .78rem; letter-spacing: .04em; }
    .sev-critical { color: var(--crit); }
    .sev-high     { color: var(--high); }
    .sev-medium   { color: var(--med);  }
    .sev-low      { color: var(--low);  }
    .sev-info     { color: var(--info); }
    .finding { padding: 1.25rem 0; border-bottom: 1px solid var(--rule); }
    .finding:last-child { border-bottom: 0; }
    .finding h3 { margin: 0 0 .25rem; font-size: 1.25rem; }
    .finding .tagline { color: var(--muted); font-size: .9rem; margin-bottom: 1rem; }
    .tag { display: inline-block; padding: .05rem .45rem; border: 1px solid var(--rule); border-radius: 999px; font-size: .78rem; color: var(--muted); margin-right: .25rem; }
    code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
    pre { background: rgba(0,0,0,.05); padding: .85rem 1rem; border-radius: .35rem; overflow-x: auto; }
    @media (prefers-color-scheme: dark) { pre { background: rgba(255,255,255,.05); } }
    img { max-width: 100%; height: auto; }
    .footer { margin-top: 3rem; color: var(--muted); font-size: .82rem; text-align: center; }
    @media print {
      body { max-width: none; margin: 0; padding: 1cm; }
      .finding { page-break-inside: avoid; }
    }
  </style>
</head>
<body>

<header>
  <h1>{{ engagement.meta.name }}</h1>
  <p class="subtitle">{{ engagement.meta.kind }}</p>

  <dl class="meta">
    {% if engagement.client.name %}<dt>Client</dt><dd>{{ engagement.client.name }}</dd>{% endif %}
    {% if engagement.meta.start_date %}<dt>Start</dt><dd>{{ engagement.meta.start_date }}</dd>{% endif %}
    {% if engagement.meta.end_date %}<dt>End</dt><dd>{{ engagement.meta.end_date }}</dd>{% endif %}
    <dt>Version</dt><dd>{{ engagement.meta.report_version }}</dd>
  </dl>
</header>

<section>
  <h2>Executive Summary</h2>
  <table>
    <thead><tr><th>Severity</th><th>Count</th></tr></thead>
    <tbody>
    {% for row in severity_counts %}
      <tr>
        <td><span class="sev sev-{{ row.name }}">{{ row.name }}</span></td>
        <td>{{ row.count }}</td>
      </tr>
    {% endfor %}
    </tbody>
  </table>
</section>

<section>
  <h2>Findings Overview</h2>
  <table>
    <thead><tr><th>ID</th><th>Severity</th><th>Title</th><th>Status</th></tr></thead>
    <tbody>
    {% for f in engagement.findings %}
      <tr>
        <td><a href="#{{ f.id }}"><code>{{ f.id }}</code></a></td>
        <td><span class="sev sev-{{ f.severity }}">{{ f.severity }}</span></td>
        <td>{{ f.title }}</td>
        <td>{{ f.status }}</td>
      </tr>
    {% endfor %}
    </tbody>
  </table>
</section>

<section>
  <h2>Findings Detail</h2>
  {% for f in engagement.findings %}
  <article class="finding" id="{{ f.id }}">
    <h3><code>{{ f.id }}</code> — {{ f.title }}</h3>
    <div class="tagline">
      <span class="sev sev-{{ f.severity }}">{{ f.severity }}</span>
      {% if f.cvss %} · CVSS {{ f.cvss }}{% endif %}
      {% if f.cwe %} · {{ f.cwe }}{% endif %}
      {% if f.owasp %} · OWASP {{ f.owasp }}{% endif %}
      · status: {{ f.status }}
    </div>
    {% if f.tags %}<div>{% for tag in f.tags %}<span class="tag">{{ tag }}</span>{% endfor %}</div>{% endif %}
    {{ f.body_html|safe }}
  </article>
  {% endfor %}
</section>

<footer class="footer">
  Generated by reptr · {{ generated_at }}
</footer>

</body>
</html>