# External verification oracles (`--verify`)

The line between a scanner that *guesses* a vulnerability and an operator that
*proves* one is a signal that lives **outside the model**. A stronger model
re-reading a finding is still a model grading a model — useful for triage, but
not proof, and a confident model is wrong in exactly the cases that cost the
most downstream.

Pentest Swarm AI's verification oracles close that gap. When a high-stakes
finding carries a re-proof recipe, an oracle confirms it against the **live
target** with a non-model signal, and that verdict is trusted over any model
claim.

```
  model verifier   → "a stronger model also thinks it's real"   (judgment)
  external oracle  → "the target called back / the responses diverged / it slept" (proof)
```

## The three oracles

| Oracle | Signal (outside any model) | Proves | Refutes |
|---|---|---|---|
| **OOB callback** | the target makes an out-of-band request to infrastructure we control (interactsh) | ✅ a callback fired | ✗ silence ≠ safe (WAF/egress/slow all look silent) |
| **Differential response** | an attack response diverges from a negative-control response | ✅ boundary crossed / indicator present / body diverges | ✅ attack collapses onto the control |
| **Timing** | injected delay moves wall-clock latency beyond the target's noise floor | ✅ induced delay clears the noise floor (and, in ladder mode, *scales* with the payload) | ✅ delayed request ≈ baseline |

Each oracle returns **Proven**, **Disproven**, or **Inconclusive**. A probe error
(tool missing, network failure, out of scope) is always Inconclusive — an
infrastructure failure can neither confirm nor condemn a finding.

### Honesty rails

- **OOB never disproves.** A missing callback is not evidence of safety.
- **Proof outranks confidence.** A proven finding earns attack-graph edge
  exploitability `1.0`, ahead of any higher-CVSS but unproven finding.
- **Opt-in, never required.** A finding with no recipe is simply not
  oracle-probed; it falls through to the model verifier. Nothing regresses.
- **Non-weaponized.** Oracles send crafted but non-destructive requests
  (an extra id, a `SLEEP`, a callback URL), scope-guarded and rate-bounded.

## Using it

```sh
pentestswarm scan https://target --scope target.com --verify
```

`--verify` is opt-in and ignored under `--dry-run` / `--safe-mode` (it sends
real requests). The OOB oracle additionally needs `interactsh-client` on PATH;
without it, the differential and timing oracles still run. At the end of a run
you'll see:

```
verify: external oracles: 7 probe(s) → 4 proven, 1 disproven
```

and each proven finding carries a badge in the report:

```
> ✅ VERIFIED — differential-response: attack 200 vs control 403
```

## How a verdict flows

```
finding (+ ProbeSpec)
   │  classifier handoff
   ▼
Corroborator.DecideWithProof
   │  high-stakes, uncorroborated
   ▼
oracle.Probe  ──►  Proven ─► promote to ≥0.95, stamp Verified + receipt ─► attack-graph edge = 1.0
               ├►  Disproven ─► reject (dropped before the exploit agent)
               └►  Inconclusive ─► fall through to the model verifier / passive gate
```

Scope and budget: every probe host is checked against the engagement scope
before a request leaves the process; probes are capped per campaign
(`MaxOracleProbes`) and each is bounded by a per-probe timeout.

## The re-proof recipe: `ProbeSpec`

An oracle needs a **parameterised** attack/control context — a single concrete
request can't carry one. A `verify.ProbeSpec` is that recipe: a raw HTTP/1.1
base request with an injection marker (`§PAYLOAD§`), plus per-class fields.

| Class | Fields |
|---|---|
| `differential` | `AttackPayload`, `ControlPayload`, optional `Indicator` |
| `oob` | `OOBTemplate` containing `§OOB§` (callback-URL slot) |
| `timing` | `DelayTemplate` containing `§DELAY§` (delay-seconds slot) |

Producers build one with the validated constructors
(`verify.NewDifferentialSpec`, `NewOOBSpec`, `NewTimingSpec`), which reject a
spec that could silently never fire.

## Producer status

The consumer side is complete: the oracle engine, the live HTTP probes, the
scope guard, per-probe timeouts, metrics, the report badge, and the `--verify`
wiring. An oracle fires whenever a candidate carries a valid `ProbeSpec`.

**Producing specs** — attaching a recipe to the findings where injection
context is known — is wired for two finding classes today:

- **BOLA / IDOR** (differential): the exploit agent re-proves a confirmed
  cross-user object access, replaying the harvested victim id (attack) against a
  well-formed non-existent id (control). Producer: `exploit.BOLASpec`.
- **Blind SQL injection** (timing): the recon agent re-proves a sqlmap-confirmed
  injection with a DBMS-appropriate `SLEEP`/`WAITFOR`/`PG_SLEEP` payload at the
  injectable parameter, in ladder mode (the induced latency must *scale* with
  the injected delay). The injection point (place/parameter/DBMS) is sourced
  from sqlmap's structured output, not its raw log. Producer:
  `exploit.SQLiTimingSpec` + `exploit.SQLiInjectionPoints`.
- **Mass assignment** (differential): the exploit agent re-proves a chain that
  smuggled a privileged field (`"role":"admin"`, `"is_admin":true`, …) in a
  JSON body, replaying that body (the attack) against the same body with the
  field removed (the control). A distinctive privileged value reflected in the
  attack response but not the control is the proof. Producer:
  `exploit.MassAssignmentSpec`.
- **SSRF** (out-of-band): the exploit agent's adaptive sweep points every
  discovered URL/host sink parameter (`url`, `redirect`, `webhook`, `image`, …,
  or any URL-shaped value) at an interactsh callback. A server that dereferences
  the parameter calls back to infrastructure we control — Proven; silence is
  never a finding (OOB never disproves). Needs `interactsh-client` on PATH.
  Producers: `exploit.DetectSSRFParams` + `exploit.SSRFOOBSpec`.
- **Access-control bypass (403→200)** (differential): the exploit agent's
  adaptive sweep takes every endpoint recon saw return 401/403 and tries
  path-normalization variants (trailing slash, dot segment, doubled slash,
  path-parameter `;`, trailing dot, case-folded segment) plus header tricks
  (`X-Original-URL`/`X-Rewrite-URL` URL-override, `X-Forwarded-For`/`X-Real-IP`
  IP-spoof). A variant served while the canonical path is denied is a proven
  front-end-ACL vs back-end-routing mismatch. Producers:
  `exploit.AuthBypassVariants` + `exploit.AuthBypassSpec` +
  `exploit.AuthBypassHeaderSpecs`.
- **SSTI** (differential): the exploit agent's adaptive sweep injects an
  arithmetic template payload (`191*193`) into reflected parameters. A polyglot
  gates cheaply (one probe pair); on a hit, per-engine specs name the engine
  (Jinja2/Twig, Freemarker/EL, Smarty, ERB, Velocity, Razor). The engine
  evaluating the expression to `36863` — present in the attack response, absent
  in the literal control — is the proof. Producers: `exploit.SSTISpecs` +
  `exploit.SSTIPolyglotSpec`.

Each producer builds the spec and runs a scope-guarded, rate-limited oracle via
a verifier injected under `--verify`, then stamps `Verified` only on a Proven
verdict — a no-op otherwise, never a downgrade.

- **Blind OS command injection** (out-of-band): the exploit agent's adaptive
  sweep injects a shell polyglot (`;curl §OOB§;` and backtick/subshell/pipe
  breakouts) into query parameters. The target's machine fetching the callback
  is the proof — no response signal needed. Needs `interactsh-client` on PATH.
  Producer: `exploit.CmdInjectionOOBSpec`.
- **NoSQL authentication bypass** (differential): the adaptive sweep probes
  discovered login endpoints with a MongoDB operator-object credential
  (`{"$gt":""}`) across common field pairs (user/pass, username/password,
  email/password). An operator-object login that succeeds where wrong string
  creds are denied is the proof. Producers: `exploit.NoSQLAuthBypassSpec` /
  `NoSQLAuthBypassSpecs`.
- **XXE** (out-of-band): the adaptive sweep POSTs an XML external-entity payload
  to XML-accepting endpoints (path contains xml/import/soap/rpc); a parser that
  resolves the entity calls back. Needs `interactsh-client`. Producer:
  `exploit.XXEOOBSpec`.
- **LDAP injection** (differential): the adaptive sweep sends a wildcard filter
  value to LDAP-ish search endpoints (path contains ldap); the wildcard result
  set diverging from a specific control proves the filter is injectable.
  Producers: `exploit.LDAPInjectionSpec` / `LDAPInjectionSpecs`.
- **Open redirect** (header-aware): the adaptive sweep points redirect-sink
  parameters (url/redirect/next/return/...) at a safe sentinel URL
  (evil.pentestswarm.invalid). A 3xx whose Location points at that attacker host
  proves it — read from the response HEADER by a dedicated non-redirect-following
  oracle. Producer: `exploit.OpenRedirectSpec`.
- **Path traversal / LFI** (differential): the adaptive sweep traverses file-ish
  parameters (file/path/page/doc/...) to /etc/passwd; the `root:` signature in
  the attack response but not the control proves arbitrary file read. Producers:
  `exploit.PathTraversalSpec` / `PathTraversalSpecs`.

Thirteen classes now have live producers. The spec API (`verify.NewOOBSpec` /
`NewDifferentialSpec` / `NewTimingSpec`) remains open for further classes
(header-aware open-redirect/CORS, LDAP injection, …).
