---
name: Autopilot
slug: autopilot-2
category: Automation
description: Autopilot orchestrates autonomous security hunting across a target, loading scope and policy files, dispatching specialist agents, and tracking progress until the exhaustion gates are met. It is used for long-running bug-bounty assessments in interactive or fully autonomous mode.
github: "https://github.com/H-mmer/pentest-agents/tree/main/.claude/skills/autopilot"
language: Python
stars: 804
forks: 156
install: "npx degit https://github.com/H-mmer/pentest-agents/tree/main/.claude/skills/autopilot ~/.claude/skills/autopilot"
installs_to: ~/.claude/skills/autopilot
source_path: .claude/skills/autopilot/SKILL.md
collection_size: 25
category_size: 1523
collection_url: "https://dirskills.com/collections/H-mmer/pentest-agents"
added: 2026-08-22T05:22:33.160Z
last_synced: 2026-08-22T05:22:33.160Z
canonical_url: "https://dirskills.com/skills/autopilot-2"
---

# Autopilot

Autopilot orchestrates autonomous security hunting across a target, loading scope and policy files, dispatching specialist agents, and tracking progress until the exhaustion gates are met. It is used for long-running bug-bounty assessments in interactive or fully autonomous mode.

**Install:**

```bash
npx degit https://github.com/H-mmer/pentest-agents/tree/main/.claude/skills/autopilot ~/.claude/skills/autopilot
```

## README

Autonomous hunt on: $ARGUMENTS

Parse flags from arguments:
- `--interactive` (default): Pause after each validated finding for user review
- `--autonomous`: Fully autonomous — no pauses, never auto-submits, produces ready-to-submit reports
- `--20m-off`: Disable 20-minute rotation timer on hunters
- `--resume`: Continue from previous session (read brain state for progress)

ALL agents dispatched by this command MUST use `model: "inherit"` in the Agent tool call.

## SETUP

1. Read `rules/hunting.md` AND `rules/mistakes.md` — both active throughout. `mistakes.md` lists lessons from real engagements ("Top 10 Most Common Mistakes" first); inject the Top 10 block into every subagent preamble because agents repeat these without the reminder.
2. Read `scope.yaml` — verify all targets are in scope
3. Read `policy.md` — extract ALL actionable constraints into a policy preamble:
   - Required HTTP headers (X-Bug-Bounty, User-Agent, custom tracking headers)
   - Account creation rules (email domain, naming conventions, company format)
   - Test environment setup (own instances, test properties, sandboxes)
   - Prohibited actions (DoS, social engineering, accessing customer data)
   - Rate limiting expectations
   - N-day waiting periods, shared responsibility exclusions
   - Credential usage restrictions
   - ANY other program-specific requirements
4. Format the **policy preamble** — this block is injected into EVERY agent dispatch:
   ```
   POLICY CONSTRAINTS (VIOLATION = DISQUALIFICATION/BAN):
   SCOPE AND POLICY MUST BE OBEYED AT ALL TIMES.
   [dynamically extracted constraints from policy.md]
   ALL HTTP requests MUST include required headers.
   ALL accounts MUST follow naming conventions.
   ALL testing MUST stay within scope boundaries.
   ```
5. `uv run python3 $CLAUDE_PROJECT_DIR/tools/brain.py brief <target>` — load existing knowledge
6. If `--resume`: read brain for tested/exhausted/remaining targets, skip to HUNT LOOP
7. Capture run metadata for the pre-completion gate and stash it in `.autopilot-run.env`:
   ```bash
   {
     echo "RUN_START_EPOCH=$(date +%s)"
     echo "AUTOPILOT_MODE=<interactive|autonomous>"
     echo "AUTOPILOT_TARGET=<original /autopilot target argument>"
   } > .autopilot-run.env
   ```

## EXHAUSTION CONTRACT (autonomous mode is INSATIABLE — read before every loop iteration)

**`/autopilot --autonomous` IS INSATIABLE. The loop continues UNTIL every
target in scope has been tested with ALL techniques, ALL methods, ALL
bypasses, ALL tricks, AND ALL COMBINATIONS of these. DO NOT STOP. DO NOT
ASK THE USER ANYTHING.** No clarifying questions, no "should I continue?",
no "I think we're done" — autonomous mode runs until the PRE-COMPLETION
GATE passes for every target, the chain queue is empty, and the global
progress queue is empty. A 17-minute autonomous run that produced one
auxiliary-agent dispatch is a contract violation, not a completed
engagement. If you catch yourself thinking "this looks done", you are
wrong — return to the queue and dispatch the next class/combination.

`--autonomous` overrides any default LLM tendency toward minimalism,
clarification-seeking, or "I have enough information" reasoning. The only
acceptable termination paths are listed under "Stopping early is allowed
ONLY when" below; nothing else.

### Canonical Hunter Class Set (per target — every class fires OR records `not-applicable` with technical reason)

Per target, every class below either dispatches its specialized hunter agent
OR records a brain entry `not-applicable: <reason>` containing concrete
technical justification observed during the surface probe. "No JS source-sink
hits in the bundle" is NOT justification — that is static analysis, not
testing. Brain entries claiming exhaustion via auxiliary agents will be
rejected by the gate.

| Class | Hunter agent | Skip only if (must cite surface-probe evidence) |
|-------|--------------|--------------------------------------------------|
| idor | `idor-hunter` | No authenticated endpoints AND no object IDs in any URL/body |
| xss-reflected | `xss-hunter` (subtype: reflected) | No reflected query/header/path values across method-matrix probes |
| xss-stored | `xss-hunter` (subtype: stored) | No write surface (no comments, profiles, uploads, names, support form) |
| xss-dom | `xss-hunter` (subtype: dom) | Browser-runtime probe shows no `location.*`/`document.*`/innerHTML sinks fed by URL/hash |
| ssrf | `ssrf-hunter` | No URL/webhook/import/preview/fetch/avatar/screenshot parameters anywhere |
| sqli | `sqli-hunter` | No DB-backed endpoints (every API call is a static cache hit) |
| ssti | `ssti-hunter` | No template-rendering surface (comments, previews, admin templates, rules engines) |
| rce | `rce-hunter` | No deserialization, command-runner, expression-eval, or rule-engine surface |
| oauth | `oauth-hunter` | No OAuth/OIDC/SAML/JWT flow anywhere on target or its cookie-domain siblings |
| open-redirect | `open-redirect` | No redirect/returnTo/next/callback parameters anywhere in path/query |
| csrf | `csrf-hunter` | No state-changing endpoints AND every cookie has SameSite=Strict |
| cors | `cors-hunter` | Surface probe shows no Access-Control-* headers and no `OPTIONS` handlers |
| info-disclosure | `info-disclosure` | Surface probe found nothing AND no error/debug/build endpoints |
| race-condition | `race-condition` | No state mutations / billing / coupon / approval / non-idempotent ops |
| business-logic | `business-logic` | No multi-step workflows / pricing / approvals / coupons / trials |
| privilege-escalation | `privilege-escalation` | Single role only AND no admin/staff/billing endpoints |
| file-upload | `file-upload` | No upload endpoints AND no media import / avatar / attachment features |
| xxe | `xxe-hunter` | No XML / SOAP / SVG / SAML / DOCX / OPML / RSS parsing surface |
| graphql | `graphql-audit` | No GraphQL endpoint reachable on any sibling host |
| subdomain-takeover | `subdomain-takeover` | DNS probe shows no dangling CNAMEs / dead vendor pointers |
| llm-ai | `llm-ai-hunter` | No chatbot / RAG / agent / MCP / inference / model-server / sandbox surface |
| auth-bypass | `auth-tester` | No authentication / session / MFA / password-reset / SSO surface |
| cache-deception | SURFACE PROBE C + escalate via `auth-tester` if creds-bound HIT found | Probe C ran AND no caching layer present (no `cf-cache-status`, `x-cache`, `age` headers across all variants) |
| header-injection | SURFACE PROBE D + escalate via `xss-hunter`/`open-redirect` if reflection found | Probe D ran AND no `Set-Cookie`/`Location` reflection across all CRLF/Unicode variants |
| h2-desync | SURFACE PROBE F + custom PoC if 421/anomaly found | Probe F ran AND target negotiated HTTP/1.1 only OR no upstream HTTP/2 anomalies |
| method-confusion | SURFACE PROBE B + escalate via `idor-hunter`/`auth-tester` if anomaly found | Probe B ran AND uniform 405 for all non-listed verbs across discovered paths |

For the four SURFACE PROBE-driven classes above, "coverage" is satisfied by
running the corresponding probe in the SURFACE PROBE phase and recording
the probe result to brain. Escalation hunters are dispatched only when the
probe finds signal. A skipped probe = uncovered class = GATE FAIL.

### Forbidden hunter substitutes

These agents are **recon/auxiliary** — they CANNOT prove exhaustion of any
class above. Dispatching them does NOT advance the canonical checklist:

- `js-analyzer` (static analysis only — proves nothing about runtime behavior)
- `config-auditor` (header/CORS/cookie audit — feeder, not exhaustion)
- `waf-profiler` (WAF mapping — auxiliary to hunters, never replaces them)
- `sast-*` family (source-code analysis — only when source is locally available, never as DAST proxy)
- `cloud-recon` (asset enumeration — feeds recon, not exhaustion)
- `vuln-scanner` (DO NOT INVOKE — uses nuclei which is banned in this workspace; substitute httpx/ffuf/dirsearch via surface probe)

If the orchestrator dispatches any of these and concludes "exhausted", the
PRE-COMPLETION GATE rejects the run.

### Minimum runtime floors (autonomous mode)

- **Per P1 target**: ≥ 90 minutes wall clock OR until canonical checklist passes — whichever is later
- **Per autonomous run**: ≥ 18 distinct subagent dispatches across recon + hunters + validation
- **Per dispatched hunter class**: ≥ 25 attempt entries recorded in brain (Depth Engine floor)
- **Surface probe phase**: must complete (all of A-I) before any hunter dispatch
- **Per finding pipeline**: validator → browser-verifier (if client-side) → devil's advocate must all run

Stopping early is allowed ONLY when:
- Scope explicitly forbids further testing (policy.md), OR
- Circuit breaker tripped (5 consecutive 403/429 → 60s backoff → rotate host, run continues elsewhere), OR
- Context budget exhausted (> 60% used → checkpoint and request `--resume`), OR
- The PRE-COMPLETION GATE passes.

## RECON (skip if `recon/` data < 7 days old)

7. Dispatch `recon` agent (model: inherit) with policy preamble
8. Brain update: `uv run python3 $CLAUDE_PROJECT_DIR/tools/brain.py record <target> recon "<new endpoints, subdomains, tech stack>" "<recon summary>"`
9. Increment subagent counter
10. **Active recon depth (gated — every artifact below blocks completion unless a
    `recon-skip:<id> policy:<clause>` brain entry cites the policy clause that
    forbids it)**:
    - **DNS brute-force** with a region-aware wordlist into
      `recon/dns-bruteforce.txt`. Don't ship "passive only" — passive misses
      novel-named hosts (`prod-s0-milli-vanilli`, `prod-s0-telefonista`).
    - **Public archive sweep** into `recon/urlscan-cdx.json` (urlscan + Wayback
      CDX) and `recon/public-archives.txt` (any other archive hits). Pull
      historical URL inventory.
    - **GitHub code search** into `recon/github-code.json`. Query each in-scope
      domain for leaked endpoints, secrets, configs.
    - **Mobile decompile** if `scope.yaml` lists any mobile asset / package: pull
      APK/IPA, decompile, extract endpoints into
      `recon/mobile/<package>.endpoints.txt`. Mobile reveals endpoints the web
      surface never exposes.
    - To skip any of the above for genuine policy reasons, record:
      `uv run python3 $CLAUDE_PROJECT_DIR/tools/brain.py record <target> recon "recon-skip:<id> policy:<clause>" "<rationale>"`

## WAF/CF DETECTION (run once per target, cache in brain)

10. Detect WAF and CF protection across live hosts:
    ```bash
    for host in $(cat recon/live-hosts.txt | head -20); do
      WAF=$(curl -sI "$host" 2>/dev/null | grep -iE "cf-ray|cloudflare|x-sucuri|x-akamai|x-datadome|server: awselb" | head -1)
      [ -n "$WAF" ] && echo "$host: $WAF"
    done
    ```
11. Record WAF map in brain: `uv run python3 $CLAUDE_PROJECT_DIR/tools/brain.py record <target> waf-map "<host→waf mappings>" "<counts by waf vendor>"`
12. If ANY hosts are CF-protected:
    - Start camofox: `$CLAUDE_PROJECT_DIR/tools/camofox_ctl.sh status || $CLAUDE_PROJECT_DIR/tools/camofox_ctl.sh start`
    - Verify stealth: check `/health` shows `browserRunning: true`
    - Record in brain: `camofox: running`
    - CF-protected hosts are P1 in ranking (less competition, stealth browser gives us edge)

## RANK

13. Dispatch `recon-ranker` agent (model: inherit) with:
    - Recon data + brain knowledge
    - WAF map (so ranker can factor CF-protection into P1/P2/Kill decisions)
    - Instruction: "CF-protected hosts WITH camofox available = P1 (competitive advantage). CF-protected hosts WITHOUT stealth browser = P2."
14. Parse P1/P2/Kill list from agent output
15. Brain update with ranking results
16. Increment subagent counter

## SURFACE PROBE (mandatory for every P1 host before HUNT LOOP — no agent dispatch, just curl)

The surface probe runs CHEAP inline curl-based checks (no agent dispatch
needed) to map dimensions hunter agents do not auto-discover. Every probe
either becomes a hunter dispatch with concrete payload OR is recorded to
brain as a coverage entry. This phase MUST run for every P1 host (every
host marked P1 in `ATTACK_SURFACE_RANKING.md` AND every novel-named live
host per Rule 30) before any hunter is dispatched. Refusing to run it is
a PRE-COMPLETION GATE fail.

**The loop is per-HOST, not per-TARGET.** A wildcard target like
`*.example.com` decomposes into N live hosts, each of which gets its own
`evidence/<host>/surface/` directory and its own A-I run. Cross-host
inference ("BR was hardened so CO is too") is forbidden by Rule 30 —
every host produces its own probe artifacts.

```bash
# Build the P1 host list
P1_HOSTS=$(awk '/^P1[: ]/{print $2}' ATTACK_SURFACE_RANKING.md \
            || cat recon/p1-hosts.txt 2>/dev/null \
            || cat recon/live-hosts.txt)

for TARGET in $P1_HOSTS; do
  mkdir -p "evidence/$TARGET/surface"
  # ... run A-I below with this TARGET ...
done
```

Inside the per-host loop, perform A through I in order. Save raw output
to `evidence/$TARGET/surface/` (the directory is created above). After I
completes, write the per-host completion marker (see "Persist surface
completion" at the end of this section).

### A. Path / file enumeration

```bash
TARGET="<host>"  # replace with the host being probed
mkdir -p "evidence/$TARGET/surface"
for path in robots.txt sitemap.xml security.txt humans.txt \
            .well-known/security.txt .well-known/openid-configuration \
            .well-known/jwks.json .well-known/oauth-authorization-server \
            .well-known/webfinger .well-known/host-meta \
            .well-known/change-password .well-known/assetlinks.json \
            .well-known/apple-app-site-association \
            .git/config .git/HEAD .env .env.local .env.production \
            crossdomain.xml clientaccesspolicy.xml \
            CHANGELOG.md README.md package.json composer.json \
            yarn.lock package-lock.json \
            __nextjs_original-stack-frame _next/static/chunks/webpack.js \
            api/ admin/ _admin/ api/v1/ api/v2/ api/internal/ \
            api/admin/ auth/ oauth/ saml/ graphql v1/ v2/ \
            internal/ _internal/ debug/ status/ health/ \
            metrics actuator actuator/health server-status \
            wp-admin/ wp-login.php phpmyadmin/ console/; do
  curl -s -o /dev/null -w "$path %{http_code} %{size_download}\n" \
    "https://$TARGET/$path"
done | tee evidence/$TARGET/surface/discovery.txt
```

If Next.js detected, enumerate `_next/data`:

```bash
BUILD_ID=$(curl -s "https://$TARGET/" \
  | grep -oE '"buildId":"[^"]+"' \
  | head -1 \
  | cut -d'"' -f4)
[ -z "$BUILD_ID" ] && BUILD_ID=$(curl -s "https://$TARGET/" \
  | grep -oE '_next/static/[a-zA-Z0-9_-]{6,}/' | head -1 \
  | cut -d'/' -f3)
for page in index sandbox stores/create stores login dashboard \
            admin profile settings billing api/auth/me; do
  curl -s -o /dev/null -w "_next/data/$BUILD_ID/$page.json %{http_code}\n" \
    "https://$TARGET/_next/data/$BUILD_ID/$page.json"
done | tee -a evidence/$TARGET/surface/discovery.txt
```

Record any 200/301/302/401/403 (anything non-404) as ATTACK SURFACE for
hunter dispatch. A 401 on `/api/admin/` is a P1 — not a 404.

### B. HTTP method matrix per discovered path

For every path that returned 200/301/302/401/403:

```bash
: > "evidence/$TARGET/surface/method-matrix.txt"
awk '$2 ~ /^(200|201|204|301|302|303|307|308|401|403)$/ {print $1}' \
  "evidence/$TARGET/surface/discovery.txt" | sort -u | while read -r PATH; do
  for METHOD in OPTIONS HEAD GET POST PUT PATCH DELETE TRACE CONNECT \
                PROPFIND COPY MOVE MKCOL LOCK UNLOCK; do
    curl -s -o /dev/null -w "$METHOD $PATH: %{http_code} %{size_download}\n" \
      -X $METHOD "https://$TARGET/$PATH"
  done
done | tee -a "evidence/$TARGET/surface/method-matrix.txt"
```

Method-specific 200/302/500 (vs. 405 elsewhere) = method confusion or
hidden verb routing → DISPATCH `auth-tester` (subtype: methods) and
`idor-hunter` with explicit verb list.

### C. Cache deception probes

```bash
for path_variant in "/profile.css" "/profile.js" "/profile.png" \
                    "/profile/index.css" "/profile;.css" "/profile%23.css" \
                    "/profile?ext=.css" "/profile/../profile.css" \
                    "/profile.css/" "/profile//.css" \
                    "/api/me.css" "/dashboard.json"; do
  RESP=$(curl -sI "https://$TARGET$path_variant" \
    -H "Cookie: session=test" \
    | grep -iE "cache-control|cf-cache-status|x-cache|age|content-type")
  echo "$path_variant: $RESP"
done | tee evidence/$TARGET/surface/cache-deception.txt
```

Sensitive content body returned with `cf-cache-status: HIT` or `age: > 0`
after a cookie request → web cache deception confirmed → DISPATCH
`auth-tester` (subtype: cache) for poisoning verification.

### D. Response header / response splitting probes

```bash
for vec in "%0d%0aSet-Cookie:%20pwn=1" \
           "%0aLocation:%20//evil.tld" \
           "%E5%98%8A%E5%98%8DSet-Cookie:%20pwn=1" \
           "%0d%0aX-XSS-Protection:%200%0d%0a"; do
  for param in next returnTo url to callback continue rd dest redirect; do
    curl -sI "https://$TARGET/?$param=$vec" \
      | grep -iE "set-cookie|location|x-xss"
  done
  curl -sI "https://$TARGET/$vec/" | grep -iE "set-cookie|location"
done | tee evidence/$TARGET/surface/header-injection.txt
```

Any injected `Set-Cookie` or unsafe `Location` reflection → DISPATCH
`auth-tester` (subtype: response-splitting) and record open-redirect
candidate for chain-builder.

### E. CORS preflight matrix

```bash
for origin in "https://evil.tld" "null" "https://$TARGET.evil.tld" \
              "http://$TARGET" "https://attacker.$TARGET" \
              "https://${TARGET}.attacker.tld" "file://" \
              "https://evil.tld%60.${TARGET}" "https://${TARGET}%23.evil.tld"; do
  for path in / api/ api/v1/ api/me api/user graphql auth/me; do
    curl -sI -X OPTIONS "https://$TARGET/$path" \
      -H "Origin: $origin" \
      -H "Access-Control-Request-Method: GET" \
      -H "Access-Control-Request-Headers: authorization,content-type" \
      | grep -iE "access-control-allow"
  done
done | tee evidence/$TARGET/surface/cors-matrix.txt
```

Any `allow-origin` reflecting attacker origin AND `allow-credentials: true`
→ DISPATCH `cors-hunter` immediately.

### F. HTTP/2 desync indicators

```bash
{
  curl --http2 -sI "https://$TARGET/" \
    -H "Transfer-Encoding: chunked" -H "Content-Length: 0"
  curl --http2-prior-knowledge -sI "https://$TARGET/" \
    -H "Connection: close"
  curl --http2 -sI "https://$TARGET/" \
    -H "Transfer-Encoding: chunked, identity"
  curl --http2 -s "https://$TARGET/" \
    -H "Content-Length: 0" -H "Content-Length: 5" -X POST -d 'X'
} | tee "evidence/$TARGET/surface/h2-desync.txt"
```

Mismatched `content-length` vs `transfer-encoding` handling, 421
Misdirected Request, or 400 with body marker leakage → DISPATCH
`auth-tester` (subtype: smuggling) with desync template.

### G. Subdomain takeover on the target itself

```bash
{
  dig +short CNAME $TARGET
  HOST_RESP=$(curl -sI "https://$TARGET/" 2>&1)
  echo "$HOST_RESP" \
    | grep -iE "no such bucket|repository not found|domain not configured|herokuapp|github\.io|s3-website|trafficmanager|cloudfront|fastly|netlify|surge.sh|readme\.io|helpjuice|tumblr|unbouncepages|wpengine"
} | tee "evidence/$TARGET/surface/takeover.txt"
```

Any vendor "no such resource" pattern → DISPATCH `subdomain-takeover` with
the dangling pointer.

### H. Cloudflare-specific probes (only if CF detected)

```bash
{
  curl -sI "https://$TARGET/" -H "Accept-Encoding: gzip" | grep -iE "cf-cache|vary"
  curl -sI "https://$TARGET/" -H "Accept-Encoding: deflate" | grep -iE "cf-cache|vary"
  curl -s "https://$TARGET/" -H "CF-Connecting-IP: 127.0.0.1" \
    -o /dev/null -w "%{http_code}\n"
  curl -s "https://$TARGET/" -H "X-Forwarded-For: 127.0.0.1" \
    -o /dev/null -w "%{http_code}\n"
  ORIGIN_IP=$(dig +short $TARGET | head -1)
  curl -sk "https://$ORIGIN_IP/" -H "Host: $TARGET" \
    -o /dev/null -w
