$ 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 json the 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.