Skip to content
subheeksh5599Public

About

Mutation testing for Solana Anchor programs. Mutanchor breaks your source on purpose, runs your test suite against each break on LiteSVM, and shows which bugs your tests would miss. Solana + Anchor + LiteSVM.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Mutanchor

CI Docs crates.io License: MIT Language Target Type Status

Line coverage lies. Mutanchor proves which bugs your tests would miss.

Mutanchor is a testing tool for Solana programs built with Anchor. It makes small, deliberate changes to a program's source code, one at a time, then runs the program's test suite against each changed version. If the tests catch the change, it was a good test. If they don't, that change is a bug your tests would miss in production — and the report shows you exactly where it is and what an attacker could do with it.

It is a CLI, not an AI tool and not a skill: every change comes from a fixed rule set, so results are deterministic and reproducible. AI is used to build the tool, never to judge the results.

cargo-mutants has zero Solana support. No Solana mutation tool ships. Mutanchor is that gap.

Docs ↗ · Source ↗ · Architecture ↓ · Run it locally ↓

Mutanchor is a local CLI. The site at mutanchor.vercel.app is documentation only — nothing is uploaded, nothing is executed on the site. Every report is generated on your machine by running mutanchor run locally.

Built for the Solana ecosystem. MIT licensed.


Table of contents


▶ See it in one command

$ cargo run -- --help
Mutation testing for Solana Anchor programs

Usage: mutanchor <COMMAND>

Commands:
  init    Parse the Anchor IDL and locate instruction source files
  run     Mutate, build each mutant, and run the suite on LiteSVM
  report  Emit the mutation report
  ci      CI mode: fail when surviving mutants exceed a threshold
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

That output is real — build the crate and run it. init finds what to change, run does the changing and testing, report produces the scorecard, ci fails the build when too many bugs slip through.


The problem Mutanchor solves

  • Line coverage lies. Coverage counts which lines ran, not whether the checks are real. A suite can touch every line and still miss every bug that matters.
  • Anchor bugs are repetitive. Solana audits keep finding the same classes: missing signer checks, wrong PDA bumps, dropped authority checks. If your tests do not catch one, an attacker can use it.
  • Nothing measures the gap. Existing tools generate tests for you, but nothing checks whether those tests would actually fail when the program is broken.

Mutanchor measures the gap directly: kill a mutant, your tests proved something. Let it survive, and the report shows the exact line an attacker could stand on.


How Mutanchor works

  1. Locate. The tool reads the Anchor program and maps every instruction to its source file.
  2. Mutate. It makes one small, deliberate change to the source code — the kind of mistake Solana audits keep finding. One change per version, never two at once.
  3. Build. Each changed version is compiled with Solana's official build toolchain. One that fails to compile is recorded as build-failed, which is itself useful information.
  4. Execute. The program's own test suite runs against each changed version on LiteSVM, a fast in-memory Solana runtime — no network, no waiting.
  5. Classify. Killed (a test failed), survived (all tests passed), or timed out.
  6. Report. A mutation score per instruction and overall, plus a list of every surviving change with its file and line and what an attacker could do with it.

Architecture

┌─────────────┐   ┌──────────────────────────────────────────┐   ┌──────────────┐
│ Anchor      │──▶│  mutanchor (Rust CLI)                     │──▶│  LiteSVM      │
│ program     │   │                                          │   │  runtime      │
│ (source +   │   │  ┌──────────────┐   ┌──────────────┐     │   │              │
│  tests)     │   │  │ source       │──▶│ build        │──change─▶│  runs the    │
│             │   │  │ rewriter     │   │ (Solana      │  .so  │   │  test suite  │
│             │   │  │ (one change  │   │  toolchain)  │      │   │  per version │
│             │   │  │  per mutant) │   │              │      │   └──────┬───────┘
│             │   │  └──────────────┘   └──────────────┘      │          │
│             │   │  ┌──────────────┐   ┌──────────────┐      │          ▼
│             │   │  │ operator     │   │ report       │◀─────│  mutation score
│             │   │  │ table        │   │ scorecard    │      │
│             │   └──┴──────────────┴───┴──────────────┘      └──────────────
└─────────────┘

Change → build → test → score

Step How What comes out
Read the program mutanchor init instruction → source file map
Make one change source rewriter one changed version per fault
Build the change Solana build toolchain a runnable program per version
Run the tests LiteSVM killed / survived / build-failed / timeout
Score report mutation score per instruction + overall

Operators

Each operator models a bug class from the Solana audit-findings corpus:

Operator Bug class it models
signer check removal missing signer validation
PDA seed swap wrong seeds, wrong account resolution
bump mismatch using the wrong PDA bump
authority check drop missing owner/authority validation
discriminator check removal accepting wrong instruction discriminators
CPI target swap calling the wrong program
close/rent check drop accounts closed or rent-exempt not validated
comparison flip boundary errors (off-by-one, wrong operator)
unchecked math silent arithmetic overflow (checked_add/sub/mul dropped)
realloc check drop unchecked account resize / re-init
close receiver swap close-to-wrong-receiver (lamports leak)
reinit zero guard drop re-initialization with stale bytes

How it uses Solana

Builds. Every changed version is compiled with Solana's official build toolchain, the same one real Anchor programs use.

Executes. Versions run on LiteSVM, the in-memory Solana runtime used by the Anchor test harness — instruction-level tests, no validator, no cluster, no wait.

Reads. The Anchor program's own interface file drives the tool: every instruction is mapped to its source file, so changes land exactly where the audit corpus says bugs live.


Engineering decisions & the hard problems

  • A CLI, not an AI tool. Mutanchor is a standalone command-line tool, not a skill and not an agent. The changes come from a fixed set of rewriting rules — same input, same mutants, every time. AI tools speed up building the tool itself; they never decide whether a change was caught.

  • One fault per mutant. Two changes at once make results impossible to read. Every mutant carries exactly one injected bug, so every survivor is attributable to one rule at one file:line.

  • Meaningless mutants are dropped. A change that cannot affect behavior would drag the score down without meaning, so it is detected and removed.

  • Build-failed is a signal, not a crash. A mutant that does not compile is recorded and the run continues. A mutant that hangs hits a timeout — the run never waits forever.

  • Fast execution is the whole point. LiteSVM runs everything in one process, which is what makes this kind of testing practical for Solana at all.

  • No secrets, no hardcoded paths. The repo carries no keys and no machine paths. Configuration arrives with the runner: environment variables with defaults only.


What's real vs pending — the honesty table

Capability Status
CLI scaffold — init / run / report / ci Real — builds, runs, output above
Fresh-clone build — cargo check on clean clone Real — verified
Docs site — install / quickstart / operators / CLI / report Real — pure documentation at mutanchor.vercel.app. No hosted execution — Mutanchor runs locally.
Source rewriting — one change per mutant Real — engine implemented, unit-tested
Operators — 12 implemented Real — all 12 implemented, unit-tested on fixture programs (signer, PDA seed, bump, authority, discriminator, CPI, close/rent, comparison flip, unchecked math, realloc check, close-receiver swap, reinit-zero-guard drop)
LiteSVM runner — build pipeline + classification Real — implemented; run against the demo program (see below)
Report — scorecard per instruction Real — HTML + JSON + dashboard.json generated by the CLI
Demo program + reproducible report Real — demo/demo-vault: 72.2% mutation score (9 killed, 5 survived, 4 build-failed, 0 timeout, 18 mutants). Reproduce locally with mutanchor run demo/demo-vault; the same run is re-executed by CI in run 33082543614. The 5 survivors are 3× authority_check_drop (L32/L45/L73) + 2× unchecked_math (L34/L47) — the unchecked_math operator immediately surfaced 2 real test gaps in the demo.
Second in-repo program Real — demo/demo-registry (name-registry, 4 instructions, 10-test LiteSVM suite): 80.0% mutation score (8 killed, 3 survived, 4 build-failed, 0 timeout, 15 mutants). Reproduce with mutanchor run demo/demo-registry. Different bug surface from demo-vault: transfer of ownership, checked_add pointer bound, unchecked-math survivor found.
Third-party program evidence Real — the engine also runs on the unmodified favorites program from solana-developers/program-examples (Anchor 1.0, mirrored at demo/favorites/ with ORIGIN.md). Building it surfaced and fixed two real engine bugs; see demo/favorites/ORIGIN.md.
CI — build, test, clippy, fmt, cargo audit Real — GitHub Actions workflow in this repo

Honest limitations

Mutanchor is a developer tool, not an audit. Read this before treating a score as a safety claim.

  • A high mutation score is not a security audit. It measures whether your test suite would catch a fixed catalog of small, syntactic bugs. Real audits cover economic design, protocol composition, and multi-transaction attacks that no single-line mutation can express.
  • The operator set is finite and rule-based. Eight operators cover the most common Anchor bug classes (signer / PDA / bump / authority / discriminator / CPI / close-rent / comparison flip). Bug classes outside this catalog — reentrancy across CPIs, sysvar drift, oracle staleness, arithmetic overflow patterns — are not measured today.
  • Equivalent mutants can inflate "survivors". Some source changes are behaviorally identical to the original and can never be killed by any test. Mutanchor drops obvious duplicates but does not claim to eliminate all equivalents; treat surviving mutants as leads, not verdicts.
  • Reproducibility depends on the Solana toolchain, not just Rust. The authoritative run of the demo uses a machine with a modern Rust; the Solana release channel bundles Cargo 1.79, which cannot compile current anchor-lang dependencies (edition2024). The CI demo-job runs best-effort — see .github/workflows/ci.yml.
  • Demo coverage. The published 72.2% score is on demo/demo-vault alone; the in-repo second program demo/demo-registry (4 instructions, 10-test LiteSVM suite) scores 80.0% on the same pipeline. Numbers on other programs will differ; treat these scores as a working proof of the pipeline, not as a benchmark of arbitrary Anchor code.
  • AI is used to build the tool, never to judge results. Every mutation comes from a deterministic rule, and every verdict comes from a real cargo build-sbf + LiteSVM run. There is no model in the analysis path.

Tests

The mutation engine ships with a unit test per operator, each running against a small fixture program (demo/fixture), plus property-style checks that every mutant is exactly one fault and that generic/lifetime syntax is never mutated:

cargo test

Currently 37 tests are green (12 engine + 9 CLI + 6 report + 10 demo-vault LiteSVM). Coverage:

  • Per-operator unit tests on demo/fixture.
  • CLI: --help on every subcommand, missing-program-dir failure paths on all four subcommands, --version, dry-run listing.
  • Report contract: publishable JSON shape (matches the frontend types), self-contained HTML, empty-state, all-timeout-scores-zero, verdict wire-format pin, and an aggregate-invariant sweep that asserts killed + survived + build_failed + timed_out == mutants_total and score() ∈ [0, 1] across synthetic reports.
  • Demo program: 10 LiteSVM tests (deposit/withdraw/pay + revert paths + boundary + close/lamport transfer).

Nothing in this README is sample data — every number quoted is the output of a real mutanchor run on the in-repo demo programs, reproducible by cloning and running the tool locally.


Performance

The following numbers are the honest wall-clock output of a real mutanchor run demo/demo-vault on a 2-core / 8 GB laptop, captured 2026-08-27. They are the reference budget; a faster host will beat them linearly.

Metric Value
Program demo/demo-vault (5 instructions, ~140 LoC)
Mutants generated 18 (0 equivalent/duplicate dropped)
Total wall time ~14 min (measured 812 s at 16 mutants; per-mutant cost is stable)
Warm-up (pristine build + test, one-off) ~11.5 min
Per-mutant, steady-state (incremental cache hot) ~11 s (build ~4.5 s + test ~6.5 s; build-fails exit in ~2 s)
Peak memory ~1.6 GB (single scratch tree)

The tool builds one scratch tree and reuses it across every mutant, so cargo build-sbf only recompiles the mutated crate on each iteration. That is the whole trick behind these numbers — a naive "rebuild-everything-per-mutant" implementation would be 40–60× slower here and unusable on this class of hardware.

What to expect on a larger program. Because per-mutant cost is dominated by rebuilding the mutated crate (not the full workspace), runtime scales roughly linearly with mutant count once the cache is warm. On the same 2-core laptop:

Program size (rough) Mutants (est.) Warm-up Per-mutant Total (est.)
Small (5 instructions, demo-vault) 16 11.5 min 11 s 13.5 min (measured)
Medium (15 instructions) ~60 12–15 min 12 s ~25 min
Large (30 instructions) ~150 15–20 min 12 s ~50 min

On a modern 8-core desktop, warm-up and per-mutant both drop 3–4× — a 150-mutant run finishes in ~15 min. Parallel scratch trees are shipped: pass --jobs N (or -j N) to fan mutants out across N worker threads, each with its own warm build tree copied from the primed one. Memory scales linearly with --jobs (≈ 1.5–2 GB per worker), so 2 workers on 8 GB is the sweet spot; a 16 GB / 8-core host runs at --jobs 4.

Honest caveat. These extrapolations are model-based, not benchmark-measured on ≥3 real programs yet — that work is scoped for the next milestone.


Use it as a GitHub Action

Any Anchor repo can run Mutanchor as a one-line CI gate:

- name: Run Mutanchor
  uses: subheeksh5599/mutanchor@v0.1.0
  with:
    program-dir: programs/my_program
    max-survivors: 0

The action installs the modern Agave toolchain, installs the pinned mutanchor CLI, runs it against your program, uploads the report as an artifact, and fails the build if surviving mutants exceed max-survivors. Full example: examples/github-action-usage.yml.

Inputs: program-dir, max-survivors, survivors-only, skip-operators, timeout, solana-version, mutanchor-ref, upload-report, artifact-name.

Outputs: mutation-score, killed, survived, build-failed, timeout-count, report-path.


Run it locally

Prerequisites: Rust stable (2021 edition), Node.js 18+ (docs site only).

git clone https://github.com/subheeksh5599/mutanchor.git
cd mutanchor

# Build the CLI
cargo build --release

# Run it
cargo run -- --help

# (Optional) run the docs site locally
cd frontend && npm install && npm run dev   # :5173

Environment

See .env.example for the full list. Mutanchor's CLI needs no environment variables — run sets the one it uses itself:

Variable Required Used by Purpose
MUTANCHOR_PROGRAM_SO only for cargo test on a program mutanchor run (set automatically) + the demo program's LiteSVM test suite Path to the compiled program.so the suite loads into LiteSVM. mutanchor run points each mutant's test run at that mutant's freshly built .so. You normally never set this by hand.

There are no secrets in this project. Nothing is read from the environment that isn't documented here, and the repo carries no keys.


Deploy

Docs site mutanchor.vercel.app — Vercel, single-page docs (install / quickstart / operators / CLI / report). No hosted execution.
Source github.com/subheeksh5599/mutanchor

Mutanchor runs entirely on your machine. The site is documentation only: no uploaded reports, no server-side state. To view a real run, execute mutanchor run locally and open target/mutanchor/report.html in a browser — it is a fully self-contained scorecard.

# rebuild and redeploy the docs site (Vercel static build of frontend/)
cd frontend && npm ci && npm run build && vercel --prod

Rollback. Every deploy is a stateless static build of frontend/. Revert to any prior good commit and rebuild:

git checkout <last-good-commit> -- frontend/
cd frontend && npm ci && npm run build && vercel --prod
# or, via the Vercel dashboard: Deployments → pick a previous build → "Promote to Production"

Project layout

mutanchor/
├── src/
│   ├── main.rs            # CLI entry — init / run / report / ci
│   ├── engine.rs          # orchestration: scan → mutate → run → report
│   ├── ops.rs             # the 8 mutation operators (deterministic rules)
│   ├── init.rs            # instruction → source-file mapping
│   ├── runner.rs          # build each mutant + execute on LiteSVM
│   ├── report.rs          # HTML + JSON report rendering
│   └── model.rs           # shared data model (Mutant, Verdict, Report)
├── demo/
│   ├── fixture/           # tiny fixture program for operator unit tests
│   └── demo-vault/        # demo Anchor program + LiteSVM test suite
├── frontend/              # docs site (Vite + React + Tailwind v4, single page)
├── .github/workflows/     # CI: build, test, clippy, fmt, audit, demo-job
├── Cargo.toml
├── LICENSE
└── README.md

Tech stack

  • CLI: Rust
  • Mutation engine: source rewriting with a fixed rule set — deterministic, no AI in the analysis path
  • Execution: LiteSVM (anchor-litesvm) + Solana's official build toolchain
  • Report: generated by the CLI — self-contained HTML + JSON, viewable straight from disk
  • Docs site: Vite, React, Tailwind v4 — self-hosted fonts, light mode, single page

Roadmap

  • Mutation engine: all 12 operators, unit-tested on fixture programs
  • LiteSVM runner: build each mutant + run the suite on LiteSVM, k/s/bf/t classification
  • Report: per-instruction scores, surviving-mutant annotations with exploit sketches, JSON + HTML + dashboard artifact
  • Demo: demo/demo-vault (real Anchor program, 10-test LiteSVM suite) with a reproducible mutation report, 72.2% score (18 mutants, 9 killed / 5 survived / 4 build-failed / 0 timeout — reproduced locally and by CI).
  • Second in-repo Anchor program: demo/demo-registry (name-registry with claim/transfer/set_pointer/reclaim, 10-test LiteSVM suite). 80.0% mutation score (15 mutants, 8k/3s/4bf).
  • Hosted GitHub Action: uses: subheeksh5599/mutanchor@v0.2.0 — one-line CI gate for any Anchor repo (composite action in action.yml).
  • 12 operators: unchecked_math, realloc_check_drop, close_receiver_swap, reinit_zero_guard_drop added — the first immediately surfaced 2 real test gaps on demo-vault.
  • CI/CD: build, test, clippy, fmt, cargo audit; demo-job publishes the report artifact (workflow in .github/workflows/ci.yml)
  • Third-party program with full kill-path: demo/favorites (mirrored from solana-developers/program-examples, Anchor 1.0, hand-written Rust LiteSVM test suite via anchor-litesvm 0.4, no-entrypoint feature to avoid SPL-deps duplicate-symbol linker collision — engine passes --test-features no-entrypoint).
  • v0.1.0 tagged; release workflow packages + cargo publish --dry-run verified in CI (workflow: .github/workflows/release.yml)
  • Parallel scratch trees: --jobs N fans mutants out across N worker threads (each with its own warm build tree). Serial semantics preserved when N=1.
  • crates.io publish: crates.io/crates/mutanchor — install with cargo install mutanchor.

License

MIT — see LICENSE.

About

Mutation testing for Solana Anchor programs. Mutanchor breaks your source on purpose, runs your test suite against each break on LiteSVM, and shows which bugs your tests would miss. Solana + Anchor + LiteSVM.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages