--- name: composing-vulnerability-report description: | 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". allowed-tools: - Read - Write - Bash(python3:*) - Glob disallowed-tools: - Bash(rm:*) - Bash(curl:*) - Bash(wget:*) - Write(.env) - Edit(.env) version: 3.30.0 author: Jeremy Longshore license: MIT compatibility: Designed for Claude Code tags: - security - reporting - vulnerability-report - cvss - pentest --- # 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 | Finding | Severity | Threshold | Affected control | |---|---|---|---| | Source file unparseable | **HIGH** | JSON/JSONL parse fails | (operational) | | Finding missing required field | **HIGH** | A finding record is missing title, severity, target, detail, or remediation | (operational) | | Duplicate fingerprint across files | **INFO** | Same finding appears in N>1 sources; reported as deduplication count | (informational) | | Source file has zero findings | **INFO** | Empty or all-info-only file; reported but not an error | (informational) | | Report generated cleanly | **INFO** | Positive 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 ```bash 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 ```bash 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 ```bash 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 ```bash 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 ```bash 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::::` - `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