composing-vulnerability-report

v2026.09.24

Read findings JSONL files from cluster 1-4 skills, deduplicate by fingerprint, group by severity, and compose a deliverable- grade markdown vulnerability report with per-finding sections (title, severity, target, detail, remediation, evidence) and a top-level summary table. The canonical written artifact a customer receives at engagement close; precise, reproducible, machine- checkable against source findings. Use when: closing an engagement, generating an interim report, regenerating after CVE or OWASP enrichment, or producing the input for generating-executive-summary. Threshold: findings missing required fields are dropped. HIGH and CRITICAL findings highlighted in the summary section. Trigger with: "compose vuln report", "write pentest report", "generate vulnerability deliverable", "render findings to report".

GitHub
Install command
npx skhub add jeremylongshore/composing-vulnerability-report
Markdown
SKILL.md

Composing Vulnerability Report

Overview

After cluster 1-4 scan skills run, each one produces a Findings file. A typical engagement ends up with eight to twenty such files across the different skill categories. The customer wants ONE vulnerability report — comprehensive, deduplicated, organized by severity, with each finding cross-referenced to its source skill and target.

This skill consumes one or more findings files (JSONL preferred, JSON list also accepted), deduplicates entries by the canonical fingerprint defined in lib/finding.py, enriches each finding with a CVSS v3.1 vector when one isn't present (using a deterministic heuristic based on severity + category — explicitly noted as "derived, not assigned by NVD" in the output), and emits a single markdown report with per-finding sections plus a top-level summary table.

The report has a defined structure that downstream tools (next two skills in cluster 6) consume:

  1. Header — engagement ID, generation timestamp, source files
  2. Summary table — finding count by severity
  3. Per-severity sections — CRITICAL first, then HIGH, MEDIUM, LOW, INFO
  4. Per-finding subsections — title, severity, target, detail, remediation, evidence, references

When the skill produces findings

FindingSeverityThresholdAffected control
Source file unparseableHIGHJSON/JSONL parse fails(operational)
Finding missing required fieldHIGHA finding record is missing title, severity, target, detail, or remediation(operational)
Duplicate fingerprint across filesINFOSame finding appears in N>1 sources; reported as deduplication count(informational)
Source file has zero findingsINFOEmpty or all-info-only file; reported but not an error(informational)
Report generated cleanlyINFOPositive confirmation(informational)

Prerequisites

  • Python 3.9+
  • One or more findings files in JSON or JSONL format produced by any cluster 1-4 scan skill (which all share lib/finding.py schema)

Instructions

Step 1 — Gather findings sources

By default the skill reads every file matching engagement/findings/*.json and engagement/findings/*.jsonl. Override with --source FILE (repeatable).

Step 2 — Run the composer

python3 ./scripts/compose_report.py engagements/acme-2026-q2/

Options:

Usage: compose_report.py PATH [OPTIONS]

Options:
  --source FILE         Specific findings file (repeatable; overrides default glob)
  --report-output FILE  Write the composed report here (default:
                        PATH/reports/vulnerability-report.md)
  --engagement-id ID    Override the engagement ID (default: parse from PATH/roe.yaml)
  --output FILE         Operational findings output (this skill's own findings)
  --format FMT          json | jsonl | markdown (default: markdown)
  --min-severity SEV    Filter report to findings at or above this severity
  --include-info        Include INFO-severity findings in the report (default: omit)

Step 3 — Review the report

The output report has a predictable structure. The header identifies the engagement, the source files, and the generation timestamp. The summary table shows finding counts by severity. Per-severity sections follow.

Each finding subsection includes a stable anchor (the fingerprint) so cross-references from later artifacts (executive summary, OWASP mapping) resolve into the report cleanly.

Step 4 — Verify against sources

python3 ./scripts/compose_report.py engagements/acme-2026-q2/ --format json --output /tmp/compose-findings.json
jq '.[] | select(.severity == "high")' /tmp/compose-findings.json

If the report references a finding the operator didn't expect, trace back via the finding's skill_id + target to the source file.

Examples

Example 1 — End-of-engagement report

python3 ./scripts/compose_report.py engagements/acme-2026-q2/ \
    --report-output engagements/acme-2026-q2/reports/vulnerability-report.md

Example 2 — Interim report mid-engagement

python3 ./scripts/compose_report.py engagements/acme-2026-q2/ \
    --min-severity high \
    --report-output engagements/acme-2026-q2/reports/interim-2026-06-15.md

--min-severity high produces an interim report covering only HIGH and CRITICAL findings — useful for in-engagement customer syncs.

Example 3 — Regenerate after OWASP mapping

python3 ./scripts/compose_report.py engagements/acme-2026-q2/ \
    --source engagements/acme-2026-q2/findings/all-findings-with-owasp.jsonl \
    --report-output engagements/acme-2026-q2/reports/vulnerability-report-v2.md

Re-run after mapping-findings-to-owasp-top10 has enriched each finding with its OWASP category; the regenerated report includes the OWASP tag in each per-finding subsection.

Output

JSON / JSONL / Markdown per lib/report.py for the skill's own operational findings. The PRIMARY output is the composed vulnerability report, written as standalone Markdown to the --report-output path.

Each operational Finding includes:

  • id — compose::<issue>::<source-file>
  • severity — CRITICAL / HIGH / MEDIUM / INFO
  • category — report-composition
  • summary — what went wrong (or right) during composition
  • evidence — source files, finding counts, dedup stats

Error Handling

  • PATH missing or empty → emits CRITICAL operational finding, exits 1.
  • Source file unparseable → emits HIGH operational finding, skips the file, continues with remaining sources.
  • No findings files found → emits HIGH operational finding, exits 1.
  • Output report path not writable → emits HIGH operational finding, exits 1.
  • Finding missing required fields → emits HIGH operational finding, omits that record from the report, continues.

Resources

  • references/THEORY.md — Vulnerability-report structure history (NIST SP 800-115, OWASP Testing Guide), CVSS v3.1 vector composition, severity scoring tradeoffs (CVSS vs intrinsic vs EPSS), finding-deduplication theory, why fingerprint-based dedup beats title-based
  • references/PLAYBOOK.md — Report-template variants per audience (technical, executive, regulatory), per-finding remediation phrasing patterns, evidence-redaction patterns for distributed reports, cross-reference protocol with the OWASP-mapping and exec-summary skills
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/.curated/composing-vulnerability-report

Default branch

main

Latest commit

e5a6c3b

Tree SHA

c2dc8e8