FuzzMing
LLM-powered fuzzing assistant for any language, any fuzzer
Point it at a project. It thinks, it fuzzes, it finds bugs.
**Current stack: Solidity + Foundry.** The first supported target is Solidity smart contracts fuzzed with Foundry. But FuzzMing is not a Foundry tool, it is built on hexagonal architecture specifically so that new languages and fuzzers plug in as adapters without touching the core. Rust + cargo-fuzz, Vyper + Echidna, Move + any fuzzer: each is a set of adapters away. The orchestrator, session loop, LLM integration, and report format are language and fuzzer agnostic.
## Contents
- [What FuzzMing offers](#what-fuzzming-offers)
- [Prerequisites](#prerequisites)
- [Install](#install)
- [Quick start](#quick-start)
- [Supported LLM providers](#supported-llm-providers)
- [fuzzming.config](#fuzzmingconfig)
- [Subcommands](#subcommands)
- [How it works](#how-it-works)
- [Limitations](#limitations)
- [Case Study](#case-study)
- [Logging](#logging)
- [Contributing](#contributing)
- [Contributors](#contributors)
- [About this project](#about-this-project)
- [License](#license)
## What FuzzMing offers
- **Zero boilerplate:** give it a `.sol` file, it generates the full handler + invariant test suite from scratch
- **Continuous audit:** bugs don't stop the session, FuzzMing strips broken invariants, keeps hunting, and accumulates every finding across all rounds
- **Multi-contract sessions:** target multiple contracts in one run, each gets its own concurrent fuzzing lane
- **Any capable LLM:** OpenRouter, Groq, OpenAI, Anthropic, one flag switches providers
- **Compile error recovery:** a pre-flight `forge build` catches compile errors immediately before the full test run; the error is fed back to the LLM and retried next round
- **Isolated test execution:** the `[profile.fuzzming]` section in `foundry.toml` sets `test = "test/fuzzming"` so forge only runs FuzzMing-generated tests: your existing suite is never touched
- **Bug deduplication:** each unique breaking invariant is recorded once regardless of how many rounds it fires; the final report is never inflated with duplicates
- **Invariant code in reports:** every confirmed finding includes the full Solidity invariant function alongside the shrunk call sequence: drop it directly into a Foundry regression test
- **Coverage feedback:** after each passing round, LCOV coverage gaps are fed back to the LLM so it writes better invariants next time
- **Iterative security analysis:** patch rounds include a dedicated LLM audit pass that reviews fuzz output + confirmed bugs and prints a clean findings summary at the end of the session
- **Interactive or headless:** guided prompts for first-time users, `--defaults` / `--from-config` for CI pipelines
- **Non-destructive config patching:** only updates the fuzzming profiles in `foundry.toml`, preserving the rest of your config
- **Demo mode:** `fuzzming run --demo` runs the full UI with mock adapters, no LLM calls, no tokens spent
## Prerequisites
| Requirement | Install |
|---|---|
| Rust stable (2021 edition) | [rustup.rs](https://rustup.rs) |
| Foundry (`forge`): required for the Solidity stack | `curl -L https://foundry.paradigm.xyz \| bash` |
| An LLM API key | OpenRouter, Groq, OpenAI, or Anthropic |
## Install
cargo install fuzzming
Or build from source:
git clone https://github.com/AchrefHemissi/fuzzming
cd fuzzming
cargo install --path .
## Quick start
Navigate to your Foundry project, then run:
fuzzming run
FuzzMing will prompt you for the target contract(s), model, and API key, then save your answers to `fuzzming.config` so you don't have to repeat them.
### Non-interactive (CI / scripted)
fuzzming run \
--targets src/Vault.sol \
--max-rounds 5 \
--model openrouter/anthropic/claude-3.5-sonnet \
--llm-key $OPENROUTER_KEY \
--defaults
### Read everything from config
# First interactive run saves settings to fuzzming.config
fuzzming run
# All subsequent runs skip every prompt
fuzzming run --from-config
### Multiple contracts
fuzzming run --targets src/Vault.sol src/Token.sol src/Pool.sol --defaults
## Supported LLM providers
The `--model` prefix selects the provider. Pass the matching API key via `--llm-key` or `LLM_KEY`:
| Prefix | Provider | Example model |
|---|---|---|
| `openrouter/` | OpenRouter | `openrouter/anthropic/claude-3.5-sonnet` |
| `groq/` | Groq | `groq/llama-3.3-70b-versatile` |
| `openai/` | OpenAI | `openai/gpt-4o` |
| `anthropic/` | Anthropic | `anthropic/claude-3-5-sonnet-20241022` |
Sensitive values can be provided via environment variables to keep them out of shell history:
export LLM_MODEL=groq/llama-3.3-70b-versatile
export LLM_KEY=$GROQ_KEY
fuzzming run --targets src/Vault.sol --defaults
## fuzzming.config
On first run FuzzMing creates a `fuzzming.config` file in the current directory:
targets=src/Vault.sol
max_rounds=5
model=openrouter/anthropic/claude-3.5-sonnet
llm_key=sk-...
workspace_root=.
max_tokens=0
llm_timeout_secs=120
full_coverage_rounds=2
prompt_mode=guided
View it (API key masked):
fuzzming config
Delete it and re-prompt:
fuzzming config --reset
## Subcommands
| Command | Description |
|---|---|
| `fuzzming run` | Start a fuzzing session |
| `fuzzming guide` | Print the full CLI reference in the terminal |
| `fuzzming report` | Print a summary of the last run's artifacts |
| `fuzzming config` | View or reset the saved `fuzzming.config` |
### Global flags
| Flag | Description |
|---|---|
| `--help`, `-h` | Print the full CLI reference |
| `--version` | Print the installed version |
### `fuzzming run`
| Flag | Default | Description |
|---|---|---|
| `--targets
` | - | Paths to target `.sol` files |
| `--max-rounds ` | 10 | Maximum fuzzing rounds per contract |
| `--model ` | - | LLM model identifier (`LLM_MODEL` env var) |
| `--llm-key ` | - | API key for the model's provider (`LLM_KEY` env var) |
| `--workspace-root ` | `.` | Foundry project root |
| `--max-tokens ` | unlimited | Max tokens the LLM may generate per call |
| `--llm-timeout-secs ` | 120 | Per-call LLM timeout in seconds |
| `--full-coverage-rounds ` | 2 | Consecutive 100%-coverage rounds before stopping |
| `--defaults` | false | Skip all prompts; use flags and env vars |
| `--from-config` | false | Skip all prompts; read everything from `fuzzming.config` |
| `--interactive` | false | Force interactive prompts even when config exists |
| `--demo` | false | Mock run: full UI, no LLM calls, no tokens spent |
| `--verbose` | false | Enable verbose trace logs |
fuzzming run # interactive: prompts for missing values
fuzzming run --targets src/Vault.sol --max-rounds 5 # explicit flags, no prompts
fuzzming run --defaults --targets src/Vault.sol # skip prompts, use flags/env vars
fuzzming run --from-config # skip prompts, read from fuzzming.config
fuzzming run --interactive # force prompts even if config exists
fuzzming run --demo # mock run, no LLM calls
### `fuzzming guide`
Print the full CLI reference and examples to stdout. No flags.
fuzzming guide
### `fuzzming report`
| Flag | Default | Description |
|---|---|---|
| `--workspace-root ` | `.` | Foundry project root to read artifacts from |
fuzzming report
fuzzming report --workspace-root ./my-project
### `fuzzming config`
View or reset the saved `fuzzming.config`. Without flags: prints all saved keys with the API key masked. With `--reset`: deletes the file so the next run re-prompts for all settings.
| Flag | Default | Description |
|---|---|---|
| `--reset` | false | Delete `fuzzming.config`; next run will re-prompt |
fuzzming config # view saved settings (API key masked)
fuzzming config --reset # delete config and re-prompt on next run
## How it works
Each fuzzing round follows this sequence:
The session ends on **full coverage or round exhaustion**, not on the first bug. When an invariant breaks, FuzzMing records it, removes it from the next round's test, and keeps hunting for more bugs.
### Round outcomes
| Outcome | Action |
|---|---|
| Bug confirmed | Record bug, strip broken invariant, continue |
| Compile error | Feed compiler output to LLM, retry next round |
| Developer test failed | Feed error to LLM, retry next round |
| Full coverage reached | Stop: no more gaps to cover |
| Round budget exhausted | Report all bugs found across all rounds |
## Limitations
FuzzMing finds bugs by generating thousands of random call sequences and checking that properties hold after every step. This approach has known blind spots: classes of bug that invariant fuzzing structurally cannot detect regardless of how many rounds run.
### 1. Bugs with no observable behavioral difference
If a bug changes which internal code path executes but always produces the same output, no invariant can fail. There is no state where the buggy version and the correct version disagree on a return value or storage change.
### 2. Bugs in code that never executes during testing
Some code paths are gated on `tx.origin`: the original wallet that started a transaction. In Foundry invariant tests, `tx.origin` is always the test contract's own address, not a real user wallet. If the buggy code only runs when a specific registered address is `tx.origin`, the fuzzer will never trigger it: the test contract is never in the relevant mapping, so the condition is always false, and the code block is skipped on every single call.
FuzzMing handles this via Rule 21 and a dedicated `tx_origin_paths` analysis field: when `tx.origin` is detected in the source, the LLM is instructed to call the target from inside a handler using `vm.prank(addr, addr)`: the two-argument form sets both `msg.sender` and `tx.origin`: then store the result in a ghost variable for the invariant to check. This pattern successfully confirmed the discount-related bugs in the DynamicSwapFeeModule case study.
**Remaining risk:** contracts where the `tx.origin`-dependent path is never reached regardless of caller identity, or where the required state preconditions are too narrow for the fuzzer to stumble upon within the round budget.
### 3. Bugs that require chain-specific knowledge
### 4. Bugs that require two adversarial actors
FuzzMing's invariant testing uses a single actor calling functions randomly. It has no model of one address deliberately trying to harm another. Attacks that require coordinated multi-transaction sequencing: where an attacker moves state before a victim's transaction to cause the victim to pay more or receive less: are invisible to a single-actor model regardless of how many rounds run.
**Example:** A fee formula that uses the live spot price instead of a time-averaged price. An attacker can execute a large swap to push the spot price far from the average, inflating the fee charged to any swap that follows in the same block. The attacker loses money: it is a pure griefing attack. Discovering it requires two actors: one that moves state adversarially, and one that checks whether the victim paid above a fair threshold. This is closer to game-theoretic simulation than property testing and would require a dedicated multi-actor adversarial mode.
### Summary
| Limitation | Status | What would catch it |
|---|---|---|
| Bug produces no observable difference | Open | Static analysis: code linter or formal verifier |
| `tx.origin`-gated code paths | Handled: Rule 21 + `vm.prank(addr, addr)` | Confirmed discount bugs in DynamicSwapFeeModule |
| Wrong constant for a specific chain | Open | `--chain` flag with known chain parameters |
| Attack requires two adversarial actors | Open | Multi-actor adversarial simulation mode |
These limitations are documented in detail in the [DynamicSwapFeeModule case study](docs/case-study-dynamicswapfeemodule.md), which benchmarks FuzzMing against a professional audit and Claude Web on the same 161-line contract.
## Case Study
Five independent methods were benchmarked against the same 161-line Solidity contract (`DynamicSwapFeeModule.sol`). Full analysis: per-method findings, head-to-head comparisons, strengths and limitations, and a five-way aggregation table: is in the case study:
**[docs/case-study-dynamicswapfeemodule.md](docs/case-study-dynamicswapfeemodule.md)**
## Logging
# Round-by-round progress
fuzzming run --verbose --targets src/Vault.sol ...
# Fine-grained tracing (via RUST_LOG)
RUST_LOG=debug fuzzming run --targets src/Vault.sol ...
## About this project
We are grateful to everyone who guided us through this journey:
**Academic Supervisor**
- **Ms. Lilia Sfaxi**
**Industry Mentors**
- **Mr. Nadhir Abdelatif**
- **Mr. Ayoub Amer**
- **Mr. Anas Hammou**
Their expertise, feedback, and encouragement made this project possible. Thank you ❤️
## License
Licensed under the [Apache License, Version 2.0](LICENSE).