Safeguard
Developer Tools•

Seed corpus and dictionaries: how to give a fuzzer a head start

Small valid samples and a token dictionary help a fuzzer reach deep code fast. How to pick seeds, trim a corpus, and write a dictionary for your format.

Safeguard Security Team
4 min read

A seed corpus is a folder of small, valid sample inputs the fuzzer starts from. A dictionary is a list of tokens, such as keywords or magic numbers, the fuzzer can insert while mutating. Together they let a fuzzer skip the hours it would spend rediscovering your file format by luck.

Why seeds matter

A fuzzer mutates what it is given. Starting from a valid file means the first mutations already pass the header checks and reach real parsing logic. Starting from nothing works for simple formats, but for anything with a magic number or required fields it can take a long time to find the first valid input.

The OSS-Fuzz guidance for ideal integrations says to store a minimal set of input files that maximizes code coverage in revision control, and to add inputs that previously triggered bugs or reach new code.

How to pick good seeds

  1. Collect real examples: files from your test suite, samples from bug reports, and inputs the format spec provides.
  2. Prefer small. A 50 KB file takes longer to mutate and run than a 200 byte one that covers the same code.
  3. Cover variety. One seed for each major feature or record type beats ten near-identical files.
  4. Do not include secrets or customer data. Seeds live in the repo.

Trim the corpus

Over time a corpus fills with redundant entries. libFuzzer's merge mode keeps only inputs that trigger new coverage: running with -merge=1 and a new folder first, the docs say only inputs that add coverage are added to the first corpus. AFL++ has afl-cmin to drop inputs that cover the same ground and afl-tmin to shrink each input without changing its path. cargo-fuzz exposes cargo fuzz cmin for the same job.

A smaller corpus runs faster in CI, which matters if you replay it on every pull request.

What a dictionary does

Some tokens are very unlikely to be produced by flipping bits. Think of the string Content-Length, the PNG signature, or an SQL keyword. A dictionary hands them to the fuzzer. In libFuzzer you pass it with the -dict flag. In AFL++ you load one with -x. The AFL++ docs also describe compile-time generation of a dictionary and an automatic dictionary in LTO mode, so you may not have to write one by hand.

Keep dictionary entries short and meaningful, and keep the file next to the fuzz target, which is what the OSS-Fuzz guidance recommends.

How to know it helped

Run twice, with and without the seeds or dictionary, for the same time, and compare the coverage number. A real improvement shows up as higher coverage sooner. If nothing changes, the seeds may be too similar to each other, or the blocker is something else like a checksum, which needs a structure-aware approach.

Add every fixed crash to the corpus

When a fuzzer finds a crash and you fix it, keep the input. Go makes this automatic by saving failing inputs under testdata/fuzz where go test replays them. In other tools, copy the file into the seed folder by hand. It becomes a regression test that costs almost nothing to run.

Quick check

Can I fuzz with an empty corpus?

Yes for simple inputs, and the fuzzer will slowly build one. For structured formats, even a single valid file speeds things up a lot.

Is a bigger corpus always better?

No. Redundant, large entries slow every run. Minimize with merge or cmin tools and keep the set small and varied.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.