Skip to content

Releases

v0.3.0 — on every pull request

Choose a tag to compare

@ExploitReplay ExploitReplay released this 06 Oct 18:23

The GitHub Action: it builds the pull request and its base, replays the
library against both, and keeps one comment on the pull request with what
this change introduced — why it is a hole, how to close it, and the line,
linked in the pull request's head. The check fails only on what the pull
request introduced. Proofs are withheld from every public channel unless
the base branch's proofs.public names the program, and never for a program
declared on mainnet or nowhere. A push to the default branch builds and
caches, so a new pull request gets its comment in 132 s on a 3-program
workspace and 144 s on a 2-program one; a cold run is 565 s and 637 s.

Every verdict is still taken on local state: there is no mainnet fork.

v0.2.0 — two exploit classes, and it finds the build itself

Choose a tag to compare

@ExploitReplay ExploitReplay released this 21 Aug 16:53

Two exploit classes now, and the command no longer needs to be told where the
build is.

What's new

A second exploit class. account-substitution-pda (FR-006): a program that
acts on an account of its own type without proving it belongs to the signer in
front of it. The attacker signs legitimately as themselves and hands in the
victim's account of the same type — the signature is real, it just authorises
the wrong account, which is what separates this from missing-signer. Cites
Wormhole, February 2022. On the fixture pair it moves 999,995,000 lamports on
the vulnerable half and is blocked on the fixed one.

The two halves emit byte-identical IDLs apart from the program address: the fix
is a require_keys_eq! inside the handler and shows nowhere in the IDL. A
verdict on this pair cannot have been read from metadata — there is nothing in
it to read.

--artifact is now optional. Without it the working copy is built and the
result cached by a SHA-256 of the sources and the locked dependencies, so a
repeat run with nothing changed skips the build: 59.8s cold against 0.135s warm
on the fixture, same verdict either way.

Sources are followed from the artifact, not searched for. The workspace is
found by walking up from the artifact rather than down from the scan root. A
scan of the vulnerable half used to cite line 41 of the fixed half — a
confident reference to the wrong line, which is worse than none.

Disagreement is now an answer. Where the artifact and the working copy
disagree — the program undeclared, one name at two addresses, or bytes unlike
what these sources build to — the run returns inconclusive with a reason and
exits 2. It never picks a half.

The workspace is read. Anchor.toml is parsed, so deployment is no longer
unknown for every scan; the value decides how much of a proof may be published
(FR-015). Every rule leans one way: anything the parser cannot fully read
resolves to unknown. Being wrong towards local publishes a mainnet exploit;
being wrong towards unknown costs a redaction nobody needed.

Fixed

  • Replays no longer exhaust memory. litesvm 1.3.0 never frees an SVM's
    native memory, so a process replaying many of them climbed until the
    allocator aborted with std::bad_alloc at ~130 MB RSS with gigabytes free.
    Two full scans, or a library walk over more than one scenario, hit it. Each
    replay now runs in a child process that hands the leak back to the OS on exit.
  • --artifact was resolved against the scanned root, so
    scan fixtures/x --artifact fixtures/x/artifacts doubled the path.

Also

  • Fixture reproducibility is now verified across machines. A cold
    ubuntu-latest runner with no build cache, installing the pinned toolchain
    from scratch, produced all four hashes byte for byte.
  • A local demo runner that warms the build cache before a live showing, and
    verifies the warm-up rather than assuming it.

What this release still does not have

  • BLOCKED never means safe. Two scenario classes, two fixture pairs. This tool
    proves presence, never absence, and it always will.
  • No GitHub Action — apps/action is still a stub — no fork mode, no PoC
    redaction.

Upgrading

Nothing to migrate. --artifact keeps working; it is now optional rather than
required.

v0.1.0 — one command proves an exploit by running it

Choose a tag to compare

@ExploitReplay ExploitReplay released this 21 Aug 16:45

exploitrun scan replays a library of known Solana exploit classes against an
Anchor program inside an in-process SVM. On the vulnerable half of a fixture pair
it returns EXPLOITED with the attacker's transaction, the account delta that
proves the gain, and the source line that allowed it, and exits 1. On the fixed
half it returns BLOCKED and exits 0.

BLOCKED means this attack did not get through. It never means the program is
safe: this tool proves presence, never absence.

Running it

Linux or macOS — the SVM runtime ships no Windows binary, so on Windows this runs
under WSL. Node 22+, pnpm 9+. Nothing is published to npm yet, so build from the
tag:

git clone --branch v0.1.0 https://github.com/ExploitReplay/exploitrun.git
cd exploitrun
pnpm install
pnpm build

Then point it at a checkout and hand it the built program:

node apps/cli/dist/index.js scan <path> --artifact <dir>

--artifact takes a directory holding one .so and its IDL. --scenarios <ids>
runs a subset instead of the whole library; --json <file> writes the
machine-readable report.

Exit codes

Code Meaning
0 nothing got through
1 at least one scenario reached EXPLOITED
2 every scenario was inconclusive
64 the invocation itself was wrong

What this release does not have, and it has to be said out loud

  • BLOCKED never means safe. One scenario class (missing-signer), one fixture
    pair. Zero false positives is measured on that pair, not on a library.
  • No GitHub Action, no fork mode, no PoC redaction. apps/action is a stub.
  • The CLI needs --artifact passed by hand, and locates sources by searching the
    path it was given rather than by following the artifact.
  • Fixture reproducibility is verified on one machine. The workflow that would
    verify it across machines has never run.