# Pentest Engagement

> Run a professional penetration engagement OR a network vulnerability scan from a scope. WEB mode (apex domains / app URLs) — mandatory surface expansion, systematic OWASP attack-class coverage, reversible active exploitation, authoritative validation, Transilience PDF. NETWORK mode (a list of IPs/CIDRs, e.g. 1500 hosts) — machine-prudent BATCHED tool-first nmap sweep (host discovery + bounded common+less-common port/service scan + CVE surfacing) with a uniform per-IP output tree, then bounded deep-dives on the highest-value hosts. The general (non-CTF) analogue of hackthebox/htb-solve.

- Skill: `transilienceai/pentest-engagement` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add transilienceai/pentest-engagement`
- Raw SKILL.md: https://api.skillmd.com/api/skills/transilienceai/pentest-engagement/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: transilienceai (https://skillmd.com/u/transilienceai)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/transilienceai/pentest-engagement

---


# Pentest Engagement

Orchestrates a scoped pentest end-to-end via the `pentest-engagement` workflow. It is the breadth-complete, coverage-gated counterpart to the flag-shaped `htb-solve` — same engine (`coordinator-loop`, now with **interleaved per-finding validation** built into the loop), but driven by an attack-class **coverage matrix** instead of a flag, with surface expansion and root-cause severity baked in.

## When to use
A real (non-CTF) engagement defined by a **scope** — either:
- **WEB** — web / API / cloud apps defined by apex domains / asset URLs, **or**
- **NETWORK** — a list of **IPs / CIDRs / ranges** (e.g. 1500 hosts) to scan for live services and vulnerabilities.

The workflow **auto-detects** the mode in Setup (`engagement_kind`): predominantly IPs/CIDRs → network; apex domains / app URLs → web. For HackTheBox/CTF use `hackthebox` (htb-solve) instead.

## Run it
**WEB** — from a scope file or inline:
```
Workflow('pentest-engagement', { scope_file: 'projects/pentest/<engagement>-scope.md' })
Workflow('pentest-engagement', { scope: { engagement_name, apex_domains:[], assets:[...], creds_env:[...], roe, business_tier } })
```
**NETWORK** — inline IP/CIDR list or a scope file containing one (a plain newline list of IPs/CIDRs is accepted):
```
Workflow('pentest-engagement', { targets: ['10.0.0.0/24', '192.0.2.0/24', '198.51.100.7'] })
Workflow('pentest-engagement', { scope_file: 'projects/pentest/<engagement>-ips.txt', scan_profile: 'standard' })
```
Options (shared): `maxConcurrent` (default = **prudent, derived from CPU cores** — ~half the cores, capped 2–8; never hundreds/thousands of parallel tasks), `dryRun`, `max_experiments`, `business_tier`, `report` (default true).
Options (network): `scan_profile` `light` (bounded `1-1024` + curated less-common, for large/fast sweeps) / **`standard`** (full-range `-p-` on every reachable host, DEFAULT; two-stage SYN→`-sV` on found-open ports, host-count-guarded, message-bus + non-443-TLS aware) / `full` (`-p-` + bounded UDP); `udp` (top-50 UDP; off for light/standard, auto-on for full); `slice_size` (hosts per scan worker, auto ≈64 IP-list / 2 CIDR-heavy); `deepen_top` (deep-dive the N highest-value hosts, default 10, `0` to skip); `geo_vantages` (≤2 gcp zones for the 2nd-vantage allowlist re-probe; overrides the US+EU default), `auto_provision` (default true; `false` = detect+flag only, no cloud spend). On a source-IP/geo-allowlist signature the workflow auto-provisions a 2nd-geography vantage and re-probes the filtered hosts before concluding "no surface."

Write the scope file per [`reference/scope-file-format.md`](reference/scope-file-format.md). Credentials are referenced by **env-var name only** and read from the repo `.env` via `python3 tools/env-reader.py` — never inline secret values.

## Phases (what the workflow does)
1. **Setup** — `env-reader` creds, parse scope, **classify kind** (web|network), **read CPU cores → prudent parallel-task cap**, `OUTPUT_DIR = projects/pentest/<date>_<engagement>/`, STARTED Slack (gated).
2. **Expand** *(WEB, the #1 fix)* — MANDATORY CT-log / passive-DNS / origin-discovery across every in-scope apex (`crt.sh`, `certspotter`, `subfinder`, origin-discovery for CDN/WAF-fronted hosts). Scope = the **discovered** surface, not the handoff. Builds the per-asset work list + seeds each asset's coverage matrix.
   **Scan** *(NETWORK, replaces Expand)* — slice the IP/CIDR set into machine-prudent batches; **one nmap worker per slice** (agents scale with *slices* ≈ dozens, never with IP count) runs the SAME pipeline: host discovery (reachability is unknown) → bounded common+less-common port/service scan → CVE surfacing (`nmap --script vulners`, `nuclei`, each CVE-ID enriched via `tools/nvd-lookup.py`) → writes a **uniform per-IP tree** `hosts/<ip>/{recon,host.json,findings}` + a merged `recon/inventory/`.
3. **Assess** *(single interleaved stage — no separate downstream validation pass)* — WEB: each asset → `coordinator-loop` (**coverage mode**), which validates **each candidate the instant it is materialized** on fresh blind agents (strict per-finding cure/drop loop) before search continues. NETWORK: bounded `coordinator-loop` deep-dives on **only** the `deepen_top` highest-value hosts, same interleaved per-finding validation (everything else is the uniform tool-scan, not a per-host agent). **Coverage-by-VALID:** a class is covered only by a `VALID`/`REPAIRED` finding, a justified N/A, or a genuine negative — a class whose candidates were all rejected/dropped stays `pending` and search keeps going.
4. **Correlate** — `attack-path-stitcher` + `risk-prioritiser` across all validated findings → ranked org roadmap.
5. **Report** *(deterministic — no agent authors the report)* — JS hands the resolved engagement block + exact commands to ONE finalize runner: `tools/report_data_build.py` merges the namespaced interim finding-JSONs into the canonical `report_data.json` (the sole-owner assembly), then the **format-dispatched** renderer runs — `transilience` → the canonical `generate_report.py` PDF skill, `custom` → `custom_report_cmd` (or a Markdown fallback). JS then **hard-gates**: `report_data` assembled ∧ (transilience: `WROTE` ∧ `bytes>0` ∧ the `[assets: …/formats/transilience-report-style]` provenance tag), retry-once → `BLOCKED`. Only `VALID`/`REPAIRED` findings appear (**drop-entirely** — `validated/` is confirmed-only by construction); **REJECTED** (`false-positives/`) and uncured **DROPPED** (`dropped/`) never appear and there is no gaps/assurance section. The finalize runner also runs `network_coverage_map.py` (swept-host tail) + `coverage_gate.py` over the whole engagement, writes `reports/coverage-matrix.json`, and the deliverable includes a deterministic **Attack Pattern Coverage** section (surface-unit × attack-class). **COMPLETE is a hard 100% gate**: it requires the report to assemble+render AND the coverage gate to report `complete:true` (every applicable cell covered) — for BOTH web and network (network additionally requires scan-completion). Any untested applicable cell → `INCOMPLETE_coverage` / `BLOCKED`.
6. **Package & deliver** — a verified `<report_id>_deliverable.zip` (`reports/ input/ logs/ artifacts/`), a short **stats summary** (`summary.md`: agents, findings by severity, elapsed; tokens/cost render `unavailable — no runtime token counter`), and the workflow returns `slack_offer: true`.

**Post-run (main loop, outside the workflow):** because coordinators must not call `AskUserQuestion`, the *invoking* agent shows the returned `summary` and asks whether to post the `deliverable_zip` to Slack; on **yes**, `python3 tools/slack-send.py --channel "$PENTEST_SLACK_CHANNEL_ID"` (gated on a successful, COMPLETE engagement).

**Report options:** `report_format` `transilience` (default) | `custom`; `custom_report_cmd` (the custom renderer, receives the `report_data.json` path + `reports/` dir); `prior_report` (a prior PDF or `report_data.json` — its title/sector/scope are **metadata-only**, never seeding the work list) + `version` to mint the cover version + "Supersedes" line.

## Determinism (be honest)
The decision layer is a **frozen pure-JS** `computeVerdict` (parity-guarded, fixture-pinned), its operands are **frozen per engagement** via NVD/KEV `--cache-dir` snapshots (`artifacts/nvd-cache/`, `artifacts/kev-snapshot.json`), and the adversarial quorum is raised to **3**. Given identical inputs the verdict is provably identical. But a **fresh** live run is **highly reproducible, not 100%** — LLM sampling produces the booleans/numbers that feed the verdict, and live-target drift moves the inputs; that ceiling is inherent and stated plainly, not papered over. Byte-identical results are a guarantee **100% only on replay** of a frozen evidence set (the Phase-2 replay cache, `artifacts/validation-cache/`) — that is the sole context in which "same request → same result" is a guarantee rather than a strong tendency.

## Boundaries
- Orchestrator only — never run the coordinator loop inline; the coverage/bookkeeping discipline needs the workflow boundary.
- A **missing credential is not a global block** — the unauthenticated surface is always tested; only a no-reachable-asset scope blocks.
- Reversible own-org/own-tenant writes are authorized by default (create-then-delete is non-destructive); destructive ops, DoS, brute force, and out-of-scope tenants are prohibited (set in RoE).

## References
- [scope-file-format.md](reference/scope-file-format.md) — scope file schema + worked example
- [coverage-matrix.md](../coordination/reference/coverage-matrix.md) — the canonical attack-class coverage contract (completion gate)
- [principles.md](../coordination/reference/principles.md) — scope-is-the-surface, reversible active exploitation, real-tools-first, root-cause severity
- [pentest-report.md](../../formats/transilience-report-style/pentest-report.md) — Transilience report structure + §7.1 root-cause severity
- [validator-role.md](../coordination/reference/validator-role.md) — engagement-validator attack-class coverage check (8)

