I Built an Open-Source Security Linter for Sui Move — Here's What It Found on Real Protocols

I've been building move-test-gen, a security tool for Sui Move smart contracts. It started as a test generator and grew into a coverage checker with mutation testing and a security linter. This post covers what it does, what it found when I pointed it at production DeFi protocols, and how to use it in your own CI.
The Problem
Sui's built-in linter has 6 rules — all focused on object handling (self-transfer, share-owned, etc.). Nothing about arithmetic overflow, missing access control, or unsafe type casting. MoveScanner exists but is closed-source and commercial.
If you're writing Move and want a slither-equivalent that checks security patterns on every PR — there isn't one. Or there wasn't.
What move-test-gen Does
Three layers, each independent:
Layer 1 — Assert pairing. Scans every assert! and abort in your source modules, every #[expected_failure] in your tests, and tells you which abort paths have no test coverage. Runs in seconds, needs only Node.js.
Layer 2 — Mutation testing. Injects deterministic bugs (flip a < to >=, comment out an assert!) and checks whether your test suite catches them. If a mutant survives, the test that should have caught it is too weak. 7 operators, exhaustive mode. Needs sui CLI.
Layer 3 — Security lint. 4 rules that scan for common vulnerability patterns:
| Rule | Severity | What it catches |
|---|---|---|
| MOV-001 | HIGH | public fun with &mut but no capability, key, or witness — missing access control |
| MOV-002 | HIGH | u64 * u64 without u128 promotion — arithmetic overflow risk |
| MOV-003 | MEDIUM | Division by a variable without prior zero-check |
| MOV-004 | MEDIUM | (expr as u64) downcast from u128/u256 without overflow assert |
MOV-002 and MOV-004 use a lightweight Move parser (scripts/move-parser.mjs) that tracks variable types through declarations, casts, and naming conventions. So it knows that numerator1 assigned from liquidity_u256 << RESOLUTION is u256 — no false positive on the multiplication.
What It Found on Real Protocols
I ran the linter against four production Sui DeFi protocols:
Kriya DEX — 1 finding
🔴 HIGH spot_dex.move:187 [MOV-001]
public function modifies state without capability check:
`update_pool` takes &mut but no capability parameter
This is the same access control gap I reported manually in a 6-finding security review. The rule catches it in under a second on 3 source files.
Scallop Lending (172 files) — 2 findings
🔴 HIGH liquidation_evaluator.move:143 [MOV-002]
`LIQUIDATION_CAP_DIVISOR * debt_price_raw` — overflow risk
🟡 MEDIUM limiter.move:189 [MOV-004]
`(i as u64)` — downcast without overflow check
Initial run hit 82 false positives. Generic functions fun name<X, Y>() were invisible to the regex, test modules were scanned, Sui-specific patterns like Witness<T> and Version params weren't recognized. 9 commits to fix all of that. After hardening: these 2 legitimate findings, zero false positives on production code.
Bucket Protocol — Fix PR Submitted
MOV-002 flagged three sites in compute_collateral_value_to_buck and compute_buck_value_to_collateral where mul_factor (which safely promotes to u128 internally) is followed by a raw u64 multiplication that undoes the protection. I submitted a fix PR — three lines changed, u128 intermediate cast.
The overflow doesn't affect current deployments (SUI is 9 decimals = BUCK decimals, so the scaling factor is 1), but adding a collateral type with higher decimals would break liquidation, redemption, and health checks. The OtterSec V1 audit caught a related conversion issue but not the overflow in the fix itself.
How to Use It
One-liner (no install)
npx mehvetero/move-test-gen sources tests --lint
In CI — GitHub Action
# .github/workflows/move-security.yml
name: move-security
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mehvetero/move-test-gen@v1.3.0
with:
sources: sources
tests: tests
lint: 'true'
Layer 1 + lint runs in seconds with zero dependencies. For mutation testing, add mutate: 'true' and install sui — there's a nightly schedule example in the repo.
Marketplace: move-test-gen coverage check
As an Agent Skill
The tool is also an agentskills-compatible skill. Install:
npx skills add mehvetero/move-test-gen
Then ask your coding agent: "Generate edge-case tests for sources/vault.move" — it produces #[test] and #[expected_failure] functions covering boundary values, arithmetic overflows, access control violations, and economic edge cases.
Agent Skill listing: agentskills Discussion #451
How It Compares
| move-test-gen | Sui built-in linter | MoveScanner | Aptos move-mutation-tools | |
|---|---|---|---|---|
| Open source | Yes | Yes | No | Yes |
| Security rules | 4 (MOV-001~004) | 0 (object-handling only) | Many (undisclosed) | 0 |
| Mutation testing | Yes (7 operators) | No | No | Yes (Aptos only) |
| Sui support | Yes | Yes | Yes | No (Aptos only) |
| CI integration | GitHub Action | CLI flag | Commercial | CLI |
| Parser | Lightweight (function-level types) | Full compiler | Full compiler | Full compiler |
| Price | Free | Free | Paid | Free |
The gap this fills: there's no open-source tool that does security-focused static analysis on Sui Move. The built-in linter handles object patterns. MoveScanner is commercial. Aptos has mutation tools but they don't work on Sui. This tool covers the middle ground — regex + parser based, not compiler-grade, but catches the patterns that actually show up in exploits.
Measured, Not Trusted
The tool has an eval lab (eval/) with 5 campaigns, 13 scenarios, and 47 rounds of testing. Frozen prompt templates, retirement-by-saturation protocol, dated records. The methodology is borrowed from TheColliery — full lineage in the RESULTS.md.
The lint rules were validated against Kriya DEX, Scallop (172 files), Bucket Protocol, and Turbos CLMM. Every false positive fix gets a selftest pin before it ships — 11 regression cases, all enforced in CI.
What's Next
More rules — clock staleness, upgrade policy, shared object access patterns
Parser improvements — tracking types through function calls, not just declarations
More protocol scans — each scan either validates the rules or finds a new pattern to handle
Contribution: if you have a Move security pattern that should be a rule, open an issue or PR a
rules/mov-NNN-*.mjsfile
Repo: github.com/mehvetero/move-test-gen Marketplace: move-test-gen coverage check


