$ adversarial scan --replay run.json
Replayable scan runs
Security findings are usually testimony: a screenshot, a pasted log, "trust me, the
scan said so." As of today every adversarial scan run can be saved as a
recording that anyone can replay — same inputs, same engine, byte-identical result —
or get a precise, machine-checked drift report when something changed.
Published — engineering notes from the adversarial.sh repo
$ record
One flag turns a scan into evidence
$ adversarial scan . --offline --record run.json TRIAGE REQUIRED RULE SEVERITY FILE LINE MESSAGE ────────────────────────────────────────────────────────────────────────────────────────────── ADV-0013-L2 critical .github/workflows/ci.yml 2 possible pull_request_target token theft (ADV-0013) note: scan recording written to run.json
--record composes with every scan mode — offline regex engine, the
keyless --agent triage, or the
hosted API — and the normal report still prints. What lands in run.json is
the run's identity and result, not your source code:
{
"format": "adversarial-scan-recording",
"version": 1,
"tool": { "name": "adversarial", "version": "0.5.0" },
"engine": {
"kind": "regex-offline",
"attack_library_sha256": "5d79b97f…75d37",
"attack_library_entries": 56
},
"scan": { "paths": ["…/repo"] },
"inputs": [
{ "path": ".github/workflows/ci.yml", "sha256": "e532b1e7…", "bytes": 33 },
{ "path": "README.md", "sha256": "3ee6a979…", "bytes": 13 }
],
"result": { "exit_code": 10, "matches": [ … ] }
}
Digests, not contents. No timestamps in the equality path. A recording replays identically whether you run it a minute or a year later, because equality means three things only: the same engine, the same attack library, the same input bytes.
$ replay
Replaying is a proof, not a promise
$ adversarial scan --replay run.json
recording reproduces # exit 0
$ echo 'changed' >> README.md
$ adversarial scan --replay run.json
replay drift detected:
input modified: README.md (digest changed)
# exit 12
Three outcomes, three exit codes:
- exit 0 — reproduces. The scan re-executed and the result matched the recording exactly: same inputs, same matches, same verdicts.
- exit 12 — drift. Something changed. The diff names it precisely:
inputs added, removed, or modified; matches that appeared or disappeared; agent
verdicts that flipped. With
--format jsonthe same diff is machine-readable. - exit 2 — can't replay. Recording unreadable, format version unsupported, or the wrong engine (an agent-mode recording needs the same agent on PATH; scan paths must match the recording's).
That exit-code contract is the point: replay slots straight into CI as a gate. A recorded clean run that no longer reproduces fails the job with the exact divergence printed — no he-said-she-said between "it passed in my pipeline" and "it failed in yours."
$ simulate
Attack attempts execute in a sandbox — and the recording proves it
$ adversarial scan . --offline --simulate note: executed 1 attack attempt(s) in the WASM sandbox (1 confirmed, 0 refuted, 0 partial, 0 inconclusive) FINDINGS RULE SEVERITY FILE LINE MESSAGE ────────────────────────────────────────────────────────────────── ADV-0001-L1 high prompt.txt 1 possible Direct instruction override (ADV-0001) 1 matches WASM sandbox: executed 1 attack attempt(s) — 1 confirmed, 0 refuted (evidence only; not real exploit execution) ADV-0001-L1 confirmed terminal=canary-would-reach-attacker
--simulate hands every match to a WASM guest — compiled from
crates/sandbox-guest, executed in an isolated wasmi instance with
zero host imports, a fuel budget, and no clock, no RNG, no I/O. The guest
re-verifies each technique pattern against the actual matched bytes, then
walks a deterministic model of the technique's attack chain: a
pull_request_target finding becomes
unsafe-trigger → untrusted-checkout → credential-in-scope → token-theft-chain-complete,
or it doesn't, and the trace names the steps that fired. The verdict —
confirmed, partial, refuted, or
inconclusive — is reported evidence, not exit-code input: a
finding stays a finding either way.
Honest scope: the guest simulates the attack chain — it is not a real exploit against a real target. What makes its output evidence is that the trace is re-executable: recordings capture the full traces, replay re-runs the same guest under the same fuel limit, and a changed verdict or step is replay drift with exit 12 — exactly as testable as a changed input digest.
# scope
What this is — and what it isn't
What shipped: replayable scan runs — the deterministic
engine behind the attack library, the candidate→verdict
agent triage, and --simulate: attack attempts executed inside an
isolated WASM sandbox, with the executed attack traces recorded and
re-executed on replay. Every claim on this page is exercised by tests
in the repo.
What didn't: the sandbox simulates attack chains — it does not execute real exploits against real targets, and it runs a generic per-category chain model rather than per-technique payloads. Live-fire sandboxed verification and richer per-technique simulations are real engineering we may build on top of this recording format, and when they exist we'll say so here. Until then, we'd rather hand you a command that proves what a scan saw than a whitepaper about what it someday might.
Read the companion pieces: why every number on this site re-derives from committed data, and keyless scanning through the agent you already have.