---
name: recording-pentest-engagement
description: |
  Package an engagement's findings, scan outputs, evidence, and
  signed ROE into a timestamped archive with a SHA-256 manifest
  covering every file. Establishes chain of custody so legal
  counsel, internal audit, or an outside SOC can verify the archive
  hasn't been modified after closeout. Optionally signs the
  manifest with GPG for cryptographic attestation.
  Use when: closing an engagement, snapshotting evidence after
  each scan day, before handing artifacts to customer, or after
  an emergency-stop event.
  Threshold: file in tree without a manifest entry, hash mismatch,
  out-of-tree path referenced in findings, unsigned manifest when
  signing was requested.
  Trigger with: "record engagement", "archive evidence", "create
  chain of custody", "package pentest artifacts".
allowed-tools:
  - Read
  - Bash(python3:*)
  - Bash(tar:*)
  - Bash(gpg:*)
  - Glob
disallowed-tools:
  - Bash(rm:*)
  - Bash(curl:*)
  - Bash(wget:*)
  - Write(.env)
  - Edit(.env)
version: 3.0.0-dev
author: Jeremy Longshore <jeremy@intentsolutions.io>
license: MIT
compatibility: Designed for Claude Code
tags:
  - security
  - engagement-governance
  - evidence
  - chain-of-custody
  - pentest
---

# Recording Pentest Engagement

## Overview

A penetration test produces a lot of artifacts: scan outputs in
multiple formats, screenshots showing the state of vulnerable
pages, raw tool logs (nmap, Burp, custom scripts), the ROE and any
amendments, exec-summary docs, and the findings themselves. Six
months after the engagement closes, a question arises — sometimes
benign ("can you remind me what we found?"), sometimes adversarial
("the customer claims we accessed an out-of-scope system; show us
the logs"). The answer needs to be: "yes, here's the archive, and
here's cryptographic proof it hasn't been touched since the
engagement closed."

This skill packages the engagement directory into a single
timestamped archive with a SHA-256 manifest covering every file.
Optionally signs the manifest with GPG. The result is a portable,
self-verifying record of what the engagement produced.

The skill also surfaces inconsistencies that would weaken the
chain-of-custody claim: out-of-tree paths referenced in findings
(meaning the finding refers to a file not actually in the archive),
files in the directory not listed in the manifest, manifest
entries whose hashes don't match. These are HIGH findings — they
mean the archive is incomplete or has been modified after the
fact, neither of which is acceptable for evidence purposes.

## When the skill produces findings

| Finding | Severity | Threshold | Affected control |
|---|---|---|---|
| File in tree not in manifest | **HIGH** | Found during walk; manifest entry missing | (evidence integrity) |
| Manifest entry hash mismatch | **CRITICAL** | Computed SHA-256 differs from manifest | (evidence integrity) |
| Manifest entry without file | **HIGH** | Manifest lists a path that doesn't exist | (evidence integrity) |
| Findings reference out-of-tree path | **MEDIUM** | A finding's `evidence` field points to a file not in the archive | (evidence completeness) |
| Symlink in tree | **MEDIUM** | Symlinks break archive portability and integrity | (evidence integrity) |
| Empty file in tree | **INFO** | 0-byte file; possibly an export error | (operational) |
| Archive package complete | **INFO** | All checks pass | (positive confirmation) |
| Manifest signed | **INFO** | GPG signature present and valid form | (positive confirmation) |

## Prerequisites

- Python 3.9+
- An engagement directory laid out as recommended (see structure
  below)
- Optional GPG installed for manifest signing

## Recommended engagement directory structure

```
engagements/acme-2026-q2/
├── roe.yaml
├── roe.amendments/
│   └── amendment-001-20260615.yaml
├── scope/
│   ├── allowed-ips.txt
│   └── normalized-targets.json
├── findings/
│   ├── cluster1-tls-2026-06-05.json
│   ├── cluster1-headers-2026-06-05.json
│   ├── cluster3-secrets-2026-06-07.json
│   └── ...
├── evidence/
│   ├── screenshots/
│   ├── tool-logs/
│   └── raw-scan-output/
├── reports/
│   ├── vulnerability-report.md
│   ├── owasp-mapping.json
│   └── executive-summary.md
└── manifest.sha256        # produced by this skill
```

## Instructions

### Step 1 — Identify the engagement directory

```bash
python3 ./scripts/record_engagement.py engagements/acme-2026-q2/
```

The skill walks the directory recursively, builds a SHA-256
manifest, and produces a chain-of-custody report.

### Step 2 — Run the packager

Options:

```
Usage: record_engagement.py PATH [OPTIONS]

Options:
  --output FILE              Findings output
  --format FMT               json | jsonl | markdown (default: markdown)
  --min-severity SEV         default info
  --manifest FILE            Manifest output path (default: PATH/manifest.sha256)
  --tar FILE                 Also create a .tar.gz archive at this path
  --sign                     Sign the manifest with GPG (uses default identity)
  --signer KEY               GPG key ID to sign with
  --exclude GLOB             Skip files matching glob (repeatable)
```

### Step 3 — Verify the manifest

The skill emits a SHA-256 manifest in standard `sha256sum` format
(one line per file: hash + two-space + path). The manifest is
verifiable independent of the skill:

```bash
sha256sum -c engagements/acme-2026-q2/manifest.sha256
```

Anyone with access to the archive can run this check; if the
output is "all OK," the archive is intact.

### Step 4 — Sign the manifest (optional)

```bash
python3 ./scripts/record_engagement.py engagements/acme-2026-q2/ --sign
```

The skill shells out to `gpg --detach-sign` against the manifest,
producing `manifest.sha256.asc`. Later verification:

```bash
gpg --verify manifest.sha256.asc manifest.sha256
sha256sum -c manifest.sha256
```

Both checks together: the manifest's contents match the archive,
AND the manifest itself is signed by an identifiable party.

### Step 5 — Create the portable archive (optional)

```bash
python3 ./scripts/record_engagement.py engagements/acme-2026-q2/ \
    --tar engagements/archives/acme-2026-q2.tar.gz
```

The tarball contains the engagement directory + manifest +
signature. Hand the tarball to legal / archive / customer.

## Examples

### Example 1 — End-of-engagement closeout

```bash
python3 ./scripts/record_engagement.py engagements/acme-2026-q2/ \
    --sign \
    --tar engagements/archives/acme-2026-q2.tar.gz \
    --output engagements/acme-2026-q2/chain-of-custody.md
```

### Example 2 — Daily snapshot during a long engagement

```bash
DATE=$(date +%Y%m%d)
python3 ./scripts/record_engagement.py engagements/acme-2026-q2/ \
    --manifest engagements/acme-2026-q2/manifest-$DATE.sha256 \
    --output engagements/acme-2026-q2/snapshot-$DATE.md
```

Daily snapshots build a time-series of the engagement's state
which can be useful in incident-investigation contexts.

### Example 3 — Pre-handoff integrity check

```bash
# Customer claims the archive is incomplete; verify before disputing
python3 ./scripts/record_engagement.py engagements/acme-2026-q2/ --min-severity high
```

If exit code is 0, the archive is internally consistent.

## Output

JSON / JSONL / Markdown per `lib/report.py`. Exit codes: 0 clean,
1 high/critical, 2 error.

Each Finding includes:

- `id` — `evidence::<issue>::<path>`
- `severity` — CRITICAL / HIGH / MEDIUM / INFO
- `category` — `evidence-chain`
- `summary` — what's wrong with the artifact
- `evidence` — file path, expected hash, observed hash, manifest entry

## Error Handling

- **Path doesn't exist** → exits 2 with operational error.
- **Permission denied reading a file** → emits HIGH finding,
  continues walking other files.
- **GPG not installed** with `--sign` requested → emits HIGH
  finding, manifest written unsigned, exits 1.
- **GPG signing fails** (no default identity, etc.) → emits HIGH
  finding, manifest written unsigned, exits 1.
- **tar command fails** with `--tar` requested → manifest still
  written; archive creation failure surfaces as HIGH finding.

## Resources

- `references/THEORY.md` — Chain of custody as a legal concept,
  evidence-integrity standards (NIST SP 800-86), SHA-256 vs
  SHA-3 vs SHA-512 tradeoffs, GPG detached-signature semantics,
  archive format choices (tar vs zip vs WORM), retention horizons
  per jurisdiction
- `references/PLAYBOOK.md` — Engagement directory templates per
  engagement type, daily-snapshot cron pattern, customer-handoff
  protocol, dispute-resolution playbook (when the customer
  challenges the archive), long-term storage and access-control
  patterns
