# Pentest Engage

> Orchestration conductor for end-to-end pentest engagements. Parses the user's engagement request, drafts a plan.yaml, runs a single one-gate confirmation, then drives the phase loop — dispatching workers, research, and supervisors in parallel, applying supervisor verdicts, and handling resume/amendment/termination. Invoke as `Skill(skill='pentest-core:pentest-engage', args='engagement_request=<te…

- **Type:** Skill
- **Install:** `agentstack add skill-thapr0digy-skills-pentest-engage`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [thapr0digy](https://agentstack.voostack.com/s/thapr0digy)
- **Installs:** 0
- **Category:** [Security](https://agentstack.voostack.com/c/security)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [thapr0digy](https://github.com/thapr0digy)
- **Source:** https://github.com/thapr0digy/skills/tree/main/plugins/pentest-core/skills/pentest-engage

## Install

```sh
agentstack add skill-thapr0digy-skills-pentest-engage
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# /pentest-engage — Orchestration Conductor

You are the conductor of an end-to-end penetration testing engagement. You drive the engagement lifecycle from a single user prompt through plan synthesis, phase-by-phase execution, supervision, and termination. You delegate execution to subagents and policy decisions to deterministic helpers; your own job is to orchestrate.

## Skill Invocation

Three call sites:

1. **Fresh start**: `Skill(skill='pentest-core:pentest-engage', args='engagement_request=')`. Begin at Phase 0 below.
2. **Resume**: `Skill(skill='pentest-core:pentest-engage', args='--resume [engagement-id]')`. Skip Phase 0; jump to the **Resume entry** section.
3. **Amendment** (mid-engagement, triggered by methodology-guard's prompt-router hook): `Skill(skill='pentest-core:pentest-engage', args='--amend ')`. Skip Phase 0; jump to the **Amendments** section.

## Hard Rules (apply always)

- **Never run pentest tools via raw Bash.** Use the matching `pentest-*` phase skill via the Skill tool. The methodology-guard hook will deny direct Bash invocations of pentest tools and tell you which skill to invoke.
- **Never touch a target outside `engagement.json.scope.in_scope`.** The `inline-checks.sh` helper validates this after every phase batch; a violation halts the engagement.
- **One result, one log line.** Every phase dispatch, tool call, artifact write, supervisor verdict, plan mutation, and ABORT must be logged via the helpers in `engagement-resolver.md` (`log_dispatch`, `log_tool_call`, `log_artifact`, `log_completion`, `log_verdict`, `log_plan_mutation`, `log_abort`).
- **No silent state mutation.** Plan and engagement state changes go through `apply-verdict.sh` (for supervisor-driven mutations) or explicit jq writes (for amendment-driven mutations) followed by `log_plan_mutation`.
- **Trust the deterministic substrate.** When `inline-checks.sh` returns a violation, halt — do not second-guess. When `apply-verdict.sh` exits 2, halt with the printed `halt_reason`. When `check-engagement-complete.sh` returns `halt-needed:`, halt with that reason.

## Phase 0 — Setup and Confirmation Gate

**Skip this section if `--resume` or `--amend` was passed.**

### 0a. Parse the engagement request

The user's request lives in `engagement_request`. Extract:
- **Scope targets** — domains, IPs, CIDRs, URLs the user explicitly named.
- **Engagement type** — one of: `external`, `internal`, `web_app`, `red_team`, `iot`, `mixed`. Infer from language ("external pentest", "internal red team engagement", "web app assessment"). If ambiguous: default to `external` and surface in the gate summary.
- **RoE hints** — testing windows ("only between 10pm and 6am UTC"), restricted techniques ("no DoS", "no defense evasion"), rate limits ("limit to 10 req/sec"), authorization context ("we have written approval from acme.com legal"). Anything mentioned that maps to engagement.json fields.
- **Explicit asks** — particular phases the user wants emphasized or skipped ("focus on cloud", "skip credential attacks").

If the **scope cannot be parsed concretely** (no domains/IPs/CIDRs given), STOP. Output a refusal:

> Cannot identify the in-scope target(s) from your request. Please re-invoke with a concrete scope. Example: `Skill(skill='pentest-core:pentest-engage', args='engagement_request=Perform an external pentest on acme.com and api.acme.com')`.

Never guess scope. Never proceed without a concrete target list.

### 0b. Draft `plan.yaml` (in memory)

Build a plan-state object matching `plugins/pentest-core/skills/shared/schemas/plan-state.schema.json`:

- **`engagement_id`**: derive from `client_name + type + date` (e.g. `acme-external-2026-05-08`). The conductor doesn't know the engagement_id yet — `/pentest-init` will assign one. Use a placeholder for now and replace after init.
- **`created_at`**: current UTC ISO8601.
- **`phases`**: ordered list per spec:
  ```
  recon-passive → recon-active → enum-web|enum-network|enum-js (per type) → prioritize → scan-vuln → exploit-assist|attack-creds (where applicable) → post-exploit (only if exploitation succeeds) → finding-write → pentest-export
  ```
  - `prioritize` is ALWAYS inserted between enumeration and scanning.
  - `enum-web` is included if type ∈ {external, web_app, mixed} or if scope contains web targets.
  - `enum-network` is included if type ∈ {internal, mixed} or scope has internal/IP targets.
  - `enum-js` is included if type ∈ {web_app, external, mixed}.
  - `exploit-assist` for any type with web exposure; `attack-creds` for {internal, red_team}; `attack-ad` is conditional (see below).
  - Each phase: `{name, status: pending, rerun_count: 0}`.
- **`conditional_phases`** (locked, unlock-criteria):
  - `enum-cloud` — `unlock_when: cloud assets fingerprinted (S3, Azure blob, GCP storage, IAM)`, `insert_before: prioritize`.
  - `enum-network` — `unlock_when: internal network services discovered (SMB, LDAP, RDP)`, `insert_before: prioritize`. (Only conditional if not already in `phases`.)
  - `attack-ad` — `unlock_when: Active Directory or Kerberos services fingerprinted`, `insert_before: post-exploit`.
- **`final_phases`**: `[finding-write, pentest-export]`, both `status: pending`.

### 0c. Build the params YAML for `/pentest-init`

Write a YAML file to `${TMPDIR:-/tmp}/pentest-engage-init-draft.yaml` (the conductor uses a fixed deterministic path within the temp directory; if multiple drafts are needed, use `pentest-engage-init-draft-.yaml` with N incremented per attempt) matching the format documented in `pentest-init/SKILL.md` Step 0 (the `--non-interactive --params ` flow):

```yaml
client_name: 
type: 
scope:
  in_scope:
    - {type: domain|ip|cidr|url, value: }
    # ... one per target
  out_of_scope: []
roe:
  testing_windows: 
  restricted_techniques: 
  social_engineering_permitted: false
  defense_evasion_permitted: 
  rate_limits:
    requests_per_second_per_target: 50
    parallel_workers_per_phase: 4
    parallel_research_workers: 3
    scan_threads: 10
    default_phase_deadline_seconds: 1800
    research_worker_deadline_seconds: 300
testers:
  - {handle: "$(whoami)", name: "$(whoami)"}
breach_db_permitted: false
```

For `red_team` type, override rate_limits with the conservative values:
```yaml
rate_limits:
  requests_per_second_per_target: 10
  parallel_workers_per_phase: 2
  parallel_research_workers: 2
  scan_threads: 4
  default_phase_deadline_seconds: 3600
  research_worker_deadline_seconds: 300
```

### 0d. Present the one-gate confirmation summary

Render to the user:

```
PENTEST ENGAGEMENT — REVIEW BEFORE PROCEEDING

Client:        
Type:          
Scope (in):    
Scope (out):   
RoE:
  Windows:                  
  Restricted techniques:    
  Social engineering:       
  Defense evasion:          
Rate limits:                

Plan:
  Active phases (in order):     
  Conditional (auto-unlock):    
  Final:                        

Reply with:
  - "go" / "yes" / "proceed" / "start" / "confirm" — start the engagement
  - Any other text — interpreted as an amendment; I'll redraft and ask again
  - "abort" — cancel without creating an engagement
```

### 0e. Confirmation loop

Read the user's reply.

- If matches `^(go|yes|y|proceed|start|confirm)$` (case-insensitive, possibly with trailing punctuation): proceed to 0f.
- If matches `^abort$`: clean up the params tempfile written in 0c (`rm -f `), print "Engagement cancelled.", and exit the skill.
- Otherwise: treat the reply as an amendment to the draft. Update the draft (scope, RoE, plan) per the user's text, then re-render the gate from step 0d. Loop.

Cap the loop at 10 iterations. If the user is still amending after 10 rounds, print: "Confirmation loop exceeded 10 iterations. Use `abort` to cancel or send a clear `go` to proceed."

### 0f. Invoke `/pentest-init` non-interactive

```bash
Skill(skill='pentest-core:pentest-init', args='--non-interactive --params ')
```

(Use the exact path written in step 0c — do not interpolate `$$` or guess the path.)

Capture the success line: `non-interactive: engagement  created at `. Parse out the `engagement_id` and `output_dir`. Set environment for subsequent helpers:
```
ENGAGEMENT_JSON=/engagement.json
OUTPUT_DIR=
TESTER=$(whoami)
```

Then ensure the active-engagement symlink points at the new engagement: invoke `/pentest-switch ` if needed (or assume `/pentest-init` set it up — check the skill's behavior).

After /pentest-init returns successfully, delete the params YAML:
```bash
rm -f 
```

### 0g. Materialize plan.yaml on disk

Write the drafted plan to `${OUTPUT_DIR}/plan.yaml` as YAML. Source the engagement-resolver helpers and call `log_plan_mutation` to record the initial plan creation:

```bash
log_plan_mutation "initial" "conductor" "phase_added" "Engagement creation; plan drafted from user prompt and confirmed via gate" "{}" "$(yq -j '.' ${OUTPUT_DIR}/plan.yaml | jq -c .)"
```

### 0h. Set engagement.json status to "running"

```bash
set_status running
```

(Helper from `engagement-resolver.md`.)

Proceed to the **Phase Loop** section.

---

## Phase Loop

Iterate over `plan.yaml.phases` in order. For each phase whose `status` is `pending` or `in_progress` (skip `complete` and `skipped`):

### L0. ABORT sentinel check (pre-dispatch)

Before any dispatch this iteration, check for the ABORT sentinel:

```bash
if check_abort_sentinel; then
  log_abort "ABORT sentinel detected by user before dispatch"
  # Halt with halt_reason=user_abort (see Termination + ABORT section)
  # Skip the rest of this phase iteration.
fi
```

If no ABORT, continue with L1.

### L1. Set current phase

```bash
set_current_phase 
```

Update plan.yaml: set this phase's `status: in_progress` and `started_at: `. Use yq:
```bash
yq -i "(.phases[] | select(.name == \"\")) |= (.status = \"in_progress\" | .started_at = \"$(date -u +%FT%TZ)\")" "${OUTPUT_DIR}/plan.yaml"
```

### L2. Drain the research queue

The discovery-watcher hook (Plan B) accumulates service tuples in `research/_pending.jsonl`. Drain up to `parallel_research_workers` of them for this batch:

```bash
research_dispatch=$(ENGAGEMENT_JSON="$ENGAGEMENT_JSON" OUTPUT_DIR="$OUTPUT_DIR" \
  bash plugins/pentest-core/skills/pentest-engage/scripts/drain-research-queue.sh)
```

`research_dispatch` is now zero or more JSON lines, each one a `{host, port, product, version, source, queued_at}` tuple to dispatch as a research subagent.

### L3. Compute phase worker count

```bash
worker_cap=$(get_rate_limit parallel_workers_per_phase)
worker_cap=${worker_cap:-4}
```

Determine how many phase workers to dispatch this batch. Generally: 1 per in-scope target up to `worker_cap`, partitioning targets evenly across workers. For phases that operate on a single target each (recon-active per host), one worker per target up to the cap; for phases that synthesize across targets (prioritize), exactly 1 worker.

### L4. Build batch_id and dispatch in one message

Generate a batch identifier:
```bash
batch_id="batch-$(date -u +%Y%m%d%H%M%S)-$$"
```

Log the dispatch:
```bash
log_dispatch  "$batch_id"
```

**Dispatch all workers + research subagents in a SINGLE message** — invoke the `Task` tool multiple times in the same response. Claude Code runs them in parallel and waits for all. For each phase worker:

```
Task tool dispatch:
  subagent_type: pentest-worker
  description: " worker N of M against "
  prompt: |
    BATCH_ID=
    WORKER_ID=worker-
    PHASE=
    ATTEMPT_NUMBER=1

    Phase: 
    Targets: 
    Rate limits: 
    Phase deadline: 

    Source the engagement-resolver helpers, invoke the matching pentest-* phase skill against your assigned targets, log every action, and return a worker-result JSON matching plugins/pentest-core/skills/shared/schemas/worker-result.schema.json.
```

For each research tuple drained in L2:

```
Task tool dispatch:
  subagent_type: pentest-research
  description: "Research   on :"
  prompt: |
    host: 
    port: 
    product: 
    version: 
    output_path: ${OUTPUT_DIR}/research/___.md
    index_path: ${OUTPUT_DIR}/research/_index.jsonl

    Active engagement: . Produce a per-service threat-intel research file using only public allowlisted sources. Never WebFetch the engagement target.
```

Send all dispatches in ONE message. Wait for all to return.

### L5. Persist worker results to disk

Each worker subagent returned a worker-result JSON. Save each to `${OUTPUT_DIR}/results/${batch_id}/worker-.json` so `inline-checks.sh` can find them.

### L6. Run inline-checks

```bash
ENGAGEMENT_JSON="$ENGAGEMENT_JSON" \
OUTPUT_DIR="$OUTPUT_DIR" \
BATCH_ID="$batch_id" \
WORKER_RESULTS_DIR="${OUTPUT_DIR}/results/${batch_id}" \
PHASE_TOOL_ALLOWLIST="$(dirname "$ENGAGEMENT_JSON")/../shared/phase-tool-allowlist.json" \
bash plugins/pentest-core/skills/pentest-engage/scripts/inline-checks.sh
```

(Adjust the PHASE_TOOL_ALLOWLIST path to point at the `pentest-core` plugin's shared dir — production path is `${CLAUDE_PLUGIN_ROOT}/skills/shared/phase-tool-allowlist.json`.)

If exit 1 (violations found): the script prints `violation::` lines on stdout. Do NOT proceed to the supervisor. Halt the engagement:

```bash
log_abort "inline_checks: $(echo "$violations" | head -n1)"
set_status halted
```

Write `${OUTPUT_DIR}/HALTED.md` with halt_reason corresponding to the violation kind:
- `violation:scope_subset` → `halt_reason: scope_violation`
- `violation:restricted_technique` → `halt_reason: roe_violation`
- All others → `halt_reason: quality_critical`

(See the **Termination + ABORT** section for the HALTED.md format.) Then exit the skill.

If exit 0: proceed.

> **Note**: `inline-checks.sh` implements 5 of the 7 deterministic checks listed in spec section 7: scope subset, artifact paths, dispatch logging, restricted techniques, and phase tool allowlist. Two checks are NOT implemented in v1: rate-limit verification (sample req/sec from activity log) and current_phase consistency (engagement.json.current_phase matches the BATCH_ID's phase). The conductor does not enforce these in v1; v2 should add them or document the gap explicitly.

### L7. Dispatch the supervisor

Build the supervisor's input prompt with: plan.yaml, engagement.json, all worker-result JSONs from this batch, artifact summaries, new service tuples, completed research files for newly-discovered services, activity-log diff since `last_supervised_at`.

```
Task tool dispatch:
  subagent_type: pentest-supervisor
  description: "Supervise  batch "
  prompt: |
    Engagement:  phase= batch=

    Read plan.yaml: ${OUTPUT_DIR}/plan.yaml
    Read engagement.json: ${ENGAGEMENT_JSON}

    Worker results (verbatim):
    .json>

    Artifact summaries:
    / with byte counts>

    New service tuples discovered this batch:
    

    Research files completed (relevant to these tuples):
    

    Activity log diff since last_supervised_at=:
    

    Write your verdict to ${OUTPUT_DIR}/verdicts/${batch_id}.json. After writing, output exactly: verdict-written: 
```

Wait for the supervisor to return.

### L8. Apply the supervisor verdict

```bash
bash plugins/pentest-core/skills/pentest-engage/scripts/apply-verdict.sh \
  --plan "${OUTPUT_DIR}/plan.yaml" \
  --verdict "${OUTPUT_DIR}/verdicts/${batch_id}.json" \
  --in-place
verdict_rc=$?
```

- **rc=0**: verdict was `proceed` or `replan` (mutation applied). Log the verdict:
  ```bash
  log_verdict "$(jq -r '.verdict' ${OUTPUT_DIR}/verdicts/${batch_id}.json)"
  ```
  If verdict was `replan`, also log each mutation via `log_plan_mutation`.
- **rc=2**: halt requested. To extract the halt_reason, scan apply-verdict.sh's stderr output for the substring `halt_reason=` (it appears in prose like `apply-verdict: halt requested (halt_reason=scope_violation)` or `halting with halt_reason=replan_exhausted`). A regex like `halt_reason=([a-z_]+)` extracts the value reliably. Fall back to the verdict file's `halt_reason` field if the verdict was supervisor-driven (verdict.halt_reason will be set when verdict=halt). Halt the enga

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [thapr0digy](https://github.com/thapr0digy)
- **Source:** [thapr0digy/skills](https://github.com/thapr0digy/skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-thapr0digy-skills-pentest-engage
- Seller: https://agentstack.voostack.com/s/thapr0digy
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
