Skip to main content

Command Palette

Search for a command to run...

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

Updated
6 min readView as Markdown
I Built an Open-Source Security Linter for Sui Move — Here's What It Found on Real Protocols
M
Smart contract security researcher.

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-*.mjs file


Repo: github.com/mehvetero/move-test-gen Marketplace: move-test-gen coverage check