planetis-m/skills/nim-fuzzing
Claude Code and agent skill by planetis-m/skills
- community
Checking that the SKILL.md file is still in the repository.
Set up and run libFuzzer-based fuzz targets for Nim code, including harness wiring, compilation flags, corpus management, structure-aware mutators, and crash triage. Use when adding fuzzing to a Nim project, building a fuzz harness for a parser/protocol/format handler, or reproducing and minimizing a fuzzer-found crash.
Install
npx vskill@latest install planetis-m/skills/nim-fuzzingSKILL.md
Nim Fuzzing
This skill covers wiring code to libFuzzer — harness structure, compilation, corpus, mutators, and crash triage. It does NOT cover writing the code under test.
Rules
Harness structure: the two required procs
Every Nim fuzz target needs these two exported procs:
initialize is called once at startup. It bootstraps Nim's runtime via NimMain(). Without it the target will segfault before any inputs are tested.
testOneInput is called by libFuzzer with a raw byte buffer. It must return
- Use
raises: []— any uncaught exception will crash the fuzzer process,
which is treated as a finding.
Minimum working example
Data extraction patterns
How to convert ptr UncheckedArray[byte] + len to Nim types:
| Target format | Extraction code | |
|---|---|---|
| raw byte buffer | data.toOpenArray(0, len-1) | |
| string | var s = newString(len); if len > 0: copyMem(addr s[0], data, len) | |
| seq[T] via copyMem | let n = len div sizeof(T); var s = newSeq[T](n); if n > 0: copyMem(addr s[0], data, n * sizeof(T)) | |
| plain fixed-layout type | if len >= sizeof(T): var value: T; copyMem(addr value, data, sizeof(T)) |
For the copyMem patterns, the fuzzer-provided buffer is a flat byte array. Check the length before taking element addresses or copying bytes.
Error handling in the harness
Compile fuzz targets with --panics:on. This turns Defect subtypes (IndexDefect, OverflowDefect, AssertionDefect, etc.) into non-catchable panics that immediately crash the process — which libFuzzer interprets as a finding. You don't need a manual except Defect block.
testOneInput must have raises: []. Catch only specific expected error types (e.g., except ValueError) and let everything else crash. Never use except Exception — it catches Defect even with panics off, masking real bugs. Bare except is equivalent to except CatchableError and does not catch Defect.
Compilation
Build config file (recommended)
Name the config file <harnessname>.nims (e.g. my_fuzzer.nims for my_fuzzer.nim) or config.nims.
| Flag | Purpose | |
|---|---|---|
--cc: clang | libFuzzer requires Clang. | |
--panics: on | Defects crash the process immediately. | |
--noMain: on | libFuzzer provides its own main. | |
--define: noSignalHandler | Prevents Nim's signal handler from masking crashes before ASan. | |
--define: useMalloc | Uses C malloc so ASan tracks all allocations. | |
--passC:"-fsanitize=fuzzer,address,undefined" | Enables libFuzzer + ASan + UBSan. | |
--passL:"-fsanitize=fuzzer,address,undefined" | Links the sanitizer runtimes. | |
--debugger: native | Embeds DWARF debug info in crash reports. |
Corpus management
libFuzzer needs a corpus directory. Start with at least one seed input:
The fuzzer stores new coverage-discovering inputs into the corpus automatically. When restarting, pass the corpus directory to retain progress:
Seed quality
Provide 1–5 small, valid inputs. Invalid seeds waste cycles. For protocol fuzzers, seed with minimal valid examples. For binary formats, use small well-formed files. Seeds should exercise the main code path, not degenerate cases.
Running the fuzzer
Basic run:
Useful flags (passed as CLI args to the compiled binary, not to nim c):
| Flag | Effect | |
|---|---|---|
-fork=N | Run N worker processes. Omit while debugging. | |
-ignore_crashes=1 | In fork mode, keep fuzzing after a crash. | |
-max_len=N | Cap input size at N bytes. | |
-runs=N | Execute exactly N runs then exit. Use for CI/regression. | |
-close_fd_mask=1 | Close stdout; useful when running many workers. | |
-max_total_time=N | Run for N seconds then exit. | |
-dict=DICT_FILE | Use a dictionary of keywords/tokens to guide mutations. |
Example:
Standalone mode: reproduce crashes without libFuzzer
Build a standalone binary that replays inputs from files instead of linking libFuzzer. Add this at the bottom of the harness file:
Compile:
Run against a crash input:
The standalone binary reads each command-line argument as a file path, feeds its contents to testOneInput, and prints progress to stderr. Use this to reproduce findings without the full fuzzer engine.
Structure-aware fuzzing with custom mutators
For structured formats (protocols, serialized types, typed data), raw byte mutation is inefficient — most mutations produce invalid inputs rejected early. Add customMutator and optionally customCrossOver to mutate at the domain level.
customMutator signature
Called by libFuzzer to mutate data in-place. Must:
- Read the typed structure from
data[0..<len] - Apply a type-aware mutation (change/add/delete element, shuffle, flip bit)
- Write the result back into
data[0..<maxLen] - Return the new byte length (≤
maxLen)
Use the seed for deterministic randomness. On failure, return the original len.
Defining customMutator replaces libFuzzer's built-in mutations. Include operations that change, add, and remove data.
customCrossOver signature
Combines two inputs into res. Same constraints as customMutator.
When to add custom mutators
Add a custom mutator when:
- The format has a clear structural boundary (fields, chunks, typed elements)
- Raw byte mutation mostly produces invalid inputs
- Coverage stalls despite a good seed corpus
Start without one. Add only when the fuzzer fails to make progress.
See references/structure_aware_fuzzing.md for a worked example.
Crash triage workflow
- Save the artifact. libFuzzer writes crash inputs as files named
crash-<hash>, slow-unit-<hash>, etc. Copy them to a permanent location.
- Reproduce with the standalone binary. Compile with
-d:fuzzStandaloneand
feed the crash input. Confirm the crash is deterministic.
- Minimize the input. Run the fuzzer with
-minimize_crash=1:
``bash ./my_fuzzer -minimize_crash=1 crash_input ``
This produces a smaller, still-crashing input.
- Isolate with sanitizers. Rebuild with only one sanitizer at a time
to understand the bug type:
```bash # ASan only nim c --cc:clang -d:noSignalHandler -d:useMalloc --noMain:on \ --passC:"-fsanitize=fuzzer,address" \ --passL:"-fsanitize=fuzzer,address" -g my_fuzzer.nim
# UBSan only nim c --cc:clang -d:noSignalHandler -d:useMalloc --noMain:on \ --passC:"-fsanitize=fuzzer,undefined" \ --passL:"-fsanitize=fuzzer,undefined" -g my_fuzzer.nim ```
- Write a minimal Nim reproducer. Extract the failing operation into a
standalone .nim file with a doAssert-based test. Keep the original crash input and sanitizer report as evidence.
- Classify findings using the framework from the
nim-defect-analysis
skill. A fuzzer finding with an artifact and sanitizer report is CONFIRMED.
Coverage reporting
Build with Clang source-based coverage:
Workflow
- Identify the fuzz target. Pick a function or module that parses,
deserializes, or processes untrusted byte input. It must be callable from a single entry point.
- Set up project files. Create the harness
.nimfile with
initialize and testOneInput. Create a <harnessname>.nims config with the fuzzer flags. Optionally add the fuzzStandalone replay block at the bottom of the harness.
- Create a seed corpus. Provide 1–5 minimal valid inputs in
corpus/.
- Build and smoke-test. Compile with the fuzzer config. Run with
-runs=1 to verify the harness starts and processes a seed.
- Start fuzzing. Run with
-fork=Nfor parallel workers. Let it run.
Check ls corpus/ periodically to see new coverage-discovering inputs.
- Inspect for slow units. Files named
slow-unit-*indicate inputs
that take unusually long. Review whether the code has algorithmic complexity issues.
- Triage crashes. For each crash artifact, follow the crash triage
steps above: confirm, minimize, isolate sanitizer, write reproducer.
- Iterate. If coverage stalls and the format is structured, add a
custom mutator. If the harness rejects too many inputs, relax validation in the fuzz path.
Common Mistakes
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingnim-fuzzing/SKILL.md
Popularity
- Repository stars
- 47
- Repository forks
- 3
- 7-day trend score
- 19
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/planetis-m/skills/nim-fuzzing)