Join our Newsletter — 33% off our NHI Course
Home› Guides› Fuzzing Guide: Finding Bugs with Coverage-Guided and API…
Guide Application Security

Fuzzing Guide: Finding Bugs with Coverage-Guided and API Fuzzing

PDF for newsletter subscribers · free to read online

← All guides
By Lalit Choda, NHI Mgmt Group Updated 24 October 2026 14 min read
On this page

The code most likely to be exploited is the code that parses untrusted input, and it is also the code that unit tests, written from the developer's expectations, cover least. Fuzzing generates large volumes of unexpected, malformed and boundary-case inputs, watches the program for crashes, hangs and sanitizer reports, and keeps the inputs that reach new code. Coverage-guided fuzzers for native code, Go's built-in fuzzing, stateful REST API fuzzers and protocol fuzzers now make the technique practical for ordinary engineering teams, not just security researchers. This guide covers fuzzing types, choosing targets, writing harnesses, sanitizers, API and protocol fuzzing, running fuzzers in CI, and triage. It is for application security engineers, developers who own parsers and services, and AppSec leads deciding where fuzzing fits in the testing programme.

Key takeaways

  • Fuzz the input boundaries first. File and image parsers, decompression, network message handling, deserialisation and API request handling give the best return, especially in memory-unsafe languages.
  • Coverage guidance is what makes fuzzing efficient. Engines such as libFuzzer and AFL++ keep inputs that reach new code and mutate them further, so a good seed corpus and a fast, deterministic harness matter more than raw execution time.
  • A fuzzer is only as good as its bug oracle. Without sanitizers such as AddressSanitizer, many memory errors corrupt silently and are never reported; for APIs, server errors, timeouts and resource leaks are the oracle.
  • Run short fuzzing on every change and long fuzzing in the background. CI integrations fuzz pull requests for minutes using a corpus built by longer batch runs.
  • Every crash becomes a regression test. Minimise the input, fix the root cause and add the case to the seed corpus so the bug cannot return.

Types of fuzzing

TypeHow inputs are producedStrengthsLimitsExamples
Dumb (black-box mutation)Random changes to sample inputs with no feedbackNo source or instrumentation needed; quick to startRarely gets past checksums, magic bytes or length checksSimple byte flippers
Generation-based (smart, grammar-aware)Inputs built from a model, grammar or specificationProduces structurally valid inputs that reach deep logicModel must be written and maintained; blind to unmodelled pathsboofuzz protocol definitions, API specifications
Coverage-guided (grey-box)Mutation steered by code coverage feedbackFinds deep bugs in parsers efficiently; the default for native codeNeeds a build with instrumentation and a harnesslibFuzzer, AFL++, Honggfuzz, Centipede, Go native fuzzing
Stateful API fuzzingRequest sequences inferred from an API definitionReaches states that need prior requests, such as created resourcesDepends on specification quality; can damage dataRESTler
Protocol fuzzingMessages generated against a network or serial protocol modelTests devices and services that only speak a protocolNeeds a way to detect failure and reset the targetboofuzz
Binary-only fuzzingCoverage from emulation or dynamic instrumentationWorks on closed-source componentsMuch slower than compile-time instrumentationAFL++ binary-only modes

Choosing what to fuzz

NIST SP 800-53 SA-11(8) describes fuzz testing as a type of dynamic code analysis that "induces program failures by deliberately introducing malformed or random data into software programs". The question is where to point it.

  • Parsers and decoders: image, document, archive, media, certificate and configuration formats. Split multi-format parsers into one target per format, as the libFuzzer documentation recommends.
  • Length-driven network handling: code that trusts a length or size field from the wire. MongoBleed (CVE-2025-14847) came from a compressed network message whose declared uncompressed size was larger than the real payload, so the server returned uninitialised heap memory, before any authentication took place. Pre-authentication message handling of this kind is a priority fuzz target.
  • Deserialisation and data binding: JSON, XML, protobuf and custom binary formats, particularly where the result drives object creation.
  • Cryptographic and identity inputs: token, certificate and signature parsing, where a crash or bypass has direct security impact.
  • API endpoints: request bodies, query parameters, headers and sequences of calls. Uncontrolled input sizes map to OWASP API4:2023 Unrestricted Resource Consumption.
  • Memory-unsafe code first: C and C++ benefit most because sanitizers turn silent corruption into reports. Memory-safe languages still yield panics, infinite loops, excessive allocation and logic errors.

Rank targets by exposure (reachable from untrusted input, before authentication), language and how much the code has already been fuzzed. The CWE Top 25 Guide lists the weakness classes, such as out-of-bounds write and use after free, that fuzzing with sanitizers is good at finding.

Writing a good fuzz harness

A harness (fuzz target) is a small function that takes fuzzer-generated bytes and feeds them to the code under test. For libFuzzer the entry point is extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size), built with clang -g -O1 -fsanitize=fuzzer,address. AFL++ can compile the same harness with afl-clang-fast++. In Go 1.18 and later, a fuzz test is a FuzzXxx(f *testing.F) function in a _test.go file with exactly one call to f.Fuzz, run with go test -fuzz=FuzzXxx.

  • Accept anything: empty, huge and malformed inputs must be handled; never call exit() in the harness.
  • Be fast and deterministic: no network calls, no randomness, no time dependence and no global state that carries between runs. Go's fuzzer reports any single execution taking longer than one second as a failure.
  • Keep targets narrow: one API or format per harness gives clearer coverage and simpler triage.
  • Use persistent mode: AFL++ documents 2x to 20x speed-ups when the harness loops over inputs in one process instead of restarting.
  • Seed the corpus: start from small, valid, diverse samples (test fixtures, real files with sensitive data removed). Deduplicate with afl-cmin or libFuzzer -merge=1 and shrink with afl-tmin.
  • Add dictionaries: keywords and magic values (-dict= in libFuzzer, -x in AFL++) help the fuzzer past string comparisons. AFL++ CMPLOG (AFL_LLVM_CMPLOG=1, run with -c) and laf-intel (AFL_LLVM_LAF_ALL=1) solve many comparisons automatically.
  • Bound input size: set -max_len to a realistic maximum so time is not wasted on giant inputs, unless size handling is what you are testing.

Engines, sanitizers and bug oracles

ToolTypeLanguages or targetsNotes
libFuzzerIn-process, coverage-guidedC and C++ via ClangWidely supported harness format; LLVM says it now receives bug fixes only, as its authors moved to Centipede
AFL++Coverage-guided, fork-server or persistentC and C++, plus binary-only modesLTO instrumentation (afl-clang-lto) preferred; parallel -M and -S instances
Go native fuzzingCoverage-guided, in toolchainGoSeed corpus in testdata/fuzz; coverage instrumentation on AMD64 and ARM64
RESTlerStateful REST APIServices with an OpenAPI definitionInfers producer-consumer dependencies between requests
boofuzzGeneration-based protocolNetwork and serial protocolsPython successor to Sulley with process and network monitors
OSS-Fuzz and ClusterFuzzLiteFuzzing infrastructureC, C++, Rust, Go, Python, Java and other JVM languages, and moreRun the engines above continuously or in CI

The bug oracle decides what counts as a failure.

  • AddressSanitizer (ASan): out-of-bounds reads and writes, use after free. The default companion to native fuzzing.
  • MemorySanitizer (MSan): use of uninitialised memory, the bug class behind information leaks.
  • UndefinedBehaviorSanitizer (UBSan): integer overflow and other undefined behaviour.
  • Run one sanitizer per instance: AFL++ advises a single afl-fuzz instance per sanitizer type because sanitizers slow execution considerably; run the rest of the instances without them.
  • Add assertions: invariant checks in the harness (for example, decode then re-encode must round-trip) find logic errors that never crash.
  • API oracles: HTTP 500 responses, timeouts, resource leaks and hierarchy violations, as RESTler's checkers look for.

API, stateful and protocol fuzzing

Services fail differently from parsers: many bugs appear only after a sequence of calls creates a resource, changes its state and then references it.

  • Start from the specification: RESTler compiles an OpenAPI definition into a grammar, then runs in four modes: Compile, Test (each endpoint once, to check setup and specification coverage), Fuzz-lean (each endpoint once with default checkers) and Fuzz (a broader search). Run Test first and fix authentication and data setup before fuzzing.
  • Never fuzz production: RESTler warns that Fuzz mode may cause outages through resource leaks, performance degradation or backend corruption. Use a dedicated environment with disposable data.
  • Use test identities: give the fuzzer its own low-privilege test accounts and short-lived tokens, not a real service account, and fuzz authorisation by running sequences as two different users. See the API Security Guide.
  • Protocol targets need monitors: boofuzz's process and network monitors detect when a target has crashed and help reset it, so a single hang does not stall the campaign. Embedded and OT devices should be fuzzed on lab units, never on live plant.
  • Complement, do not replace, DAST: scanners check known vulnerability patterns; API fuzzing explores unexpected inputs and sequences. See the Web Application Security Testing Guide.

Fuzzing in CI and at scale

  • Pull request fuzzing: ClusterFuzzLite runs in GitHub Actions, GitLab, Google Cloud Build and Prow, fuzzing code changes before merge with libFuzzer and ASan, MSan or UBSan.
  • Batch fuzzing: longer asynchronous runs find deeper bugs and build the corpus that pull request fuzzing reuses. Keep the corpus in storage, prune it regularly with corpus merge, and back it up: it is the accumulated value of the campaign.
  • Open source dependencies: OSS-Fuzz, launched in 2016 after Heartbleed, runs libFuzzer, AFL++, Honggfuzz and Centipede on more than 1,000 open source projects and had found over 10,000 security vulnerabilities and 36,000 bugs as of August 2023. Check whether your critical dependencies are enrolled, and contribute harnesses where they are not. See the Software Supply Chain Security Guide.
  • Regression corpus: Go writes each minimised failing input to testdata/fuzz/FuzzXxx/, and plain go test then runs it as a unit test. Do the same for other engines by committing crash reproducers to the seed corpus.
  • Knowing when to stop: AFL++ suggests that when no new paths appear for a long time, such as a day or a week, a campaign may no longer be productive. Use coverage reports (afl-plot, afl-showmap -C, ClusterFuzzLite coverage) to find code the fuzzer never reaches, then improve the harness or seeds rather than running longer.

AI-assisted fuzzing

Large language models are now used to write harnesses, the most labour-intensive part of fuzzing. Google's OSS-Fuzz-gen project reports generating valid fuzz targets with coverage increases for 160 C and C++ projects, up to 29% more line coverage than existing human-written targets, and 30 new bugs reported, including CVE-2024-9143 in OpenSSL. It scores generated targets on whether they compile, crash at runtime and add coverage. Treat AI-generated harnesses like any generated code: review them, check they exercise real entry points, and discard targets that report crashes caused by the harness itself. Fuzzing the AI model itself (adversarial prompts and inputs) is a different discipline; see the AI Red Teaming Guide.

Triage and remediation

  1. Reproduce: rerun the crashing input against a sanitizer build and record the stack trace.
  2. Deduplicate: group crashes by top stack frames and sanitizer report type so one bug does not become fifty tickets.
  3. Minimise: use afl-tmin, libFuzzer minimisation or Go's built-in minimisation to get the smallest reproducer.
  4. Assess severity: out-of-bounds writes and use after free are usually more serious than reads or null dereferences; reachability from untrusted input before authentication raises priority. See the Vulnerability Prioritisation Guide.
  5. Fix the root cause: correct the length or bounds handling rather than special-casing the input.
  6. Lock it in: add the reproducer to the regression corpus and confirm the fuzzer no longer reports it.
  7. Handle disclosure: crashes found in third-party code go to the maintainer privately; reproducers can be weaponised, so store them with restricted access.

Implementing a fuzzing programme

  1. Inventory input boundaries: list parsers, protocol handlers and public APIs per product, with language and exposure.
  2. Pilot one target: pick a high-exposure parser, write a harness, add seeds and a dictionary, and run it with ASan for a few days.
  3. Measure and tune: review coverage, fix harness bottlenecks and triage the first crashes with the owning team.
  4. Add to CI in report-only mode: run pull request fuzzing for a few minutes per change without blocking merges, to measure noise and runtime.
  5. Enforce: once stable, block merges on new reproducible crashes in fuzzed components; keep a documented override for false positives with an owner.
  6. Scale out: add batch fuzzing infrastructure, API fuzzing in a test environment, and protocol fuzzing for devices.
  7. Roll back if needed: if fuzzing jobs destabilise pipelines or shared test environments, return them to report-only or move them to nightly runs while you fix isolation.

Decision guidance

  • Coverage-guided or generation-based: use coverage-guided fuzzing whenever you can build the code; use generation-based fuzzing for protocols, devices and closed systems, or combine both by writing structure-aware mutators.
  • libFuzzer or AFL++: both accept the same harness format; AFL++ offers more instrumentation options and binary-only modes, while libFuzzer is the simplest in-process start. Many teams run both through OSS-Fuzz or ClusterFuzzLite.
  • Language-native fuzzing: for Go, start with the built-in fuzzer; it needs no extra tooling and its failures become unit tests.
  • Small teams: fuzz the one or two components that parse external input, run in CI, and rely on OSS-Fuzz coverage of dependencies.
  • Large organisations: a central fuzzing platform, harness ownership by product teams, coverage targets for input-handling code, and fuzzing as a release gate for high-risk components. See the Secure Software Development Lifecycle (SSDLC) Guide.

Detection and measurement

  • Harness coverage of input boundaries: percentage of listed parsers and public APIs with an active fuzz target.
  • Code coverage reached: line or edge coverage of each target component, tracked over time.
  • Unique crashes found and fixed: deduplicated crash count, with median time from report to fix.
  • Regression escapes: fixed crashes that reappear; should be zero once reproducers are in the corpus.
  • Executions per second: a sudden drop usually means a slow code path or a harness problem.
  • CI fuzzing health: share of pull request fuzz jobs completing within their time budget.

Framework mapping

FrameworkReferenceRelevance
NIST SP 800-53 Rev. 5SA-11, SA-11(8), SA-15Developer testing; dynamic code analysis, which names fuzz testing; development process and tools
NIST CSF 2.0PR.PS-06, ID.RA-01Secure development practices integrated and monitored; vulnerabilities identified and recorded
NIST SP 800-218 (SSDF) v1.1PW.8Test executable code to identify vulnerabilities
ISO/IEC 27001:2022 Annex AA.8.29 Security testing in development and acceptanceSecurity testing processes in the development life cycle
OWASP API Security Top 10 2023API4:2023 Unrestricted Resource ConsumptionOversized and unbounded inputs that API fuzzing exercises

Common failure patterns

  • Fuzzing without sanitizers: memory corruption that does not crash goes unreported, and leaks of uninitialised memory, the bug class behind MongoBleed, are easy to miss without MemorySanitizer.
  • Harnesses that never pass validation: missing seeds or dictionaries mean the fuzzer spends days stuck on a checksum or magic number. Check coverage, not just runtime.
  • Non-deterministic harnesses: network calls, time or global state produce crashes that cannot be reproduced and are eventually ignored.
  • Fuzzing shared or production environments: stateful API fuzzing corrupts data or causes outages, and the programme is shut down.
  • Losing the corpus: rebuilding CI runners without persistent corpus storage throws away weeks of accumulated coverage.
  • Treating one campaign as done: code changes reopen bugs; fuzzing has to run continuously with the regression corpus.

Practitioner checklist

  • List every component that parses untrusted input and rank it by exposure and language.
  • Write narrow, fast, deterministic harnesses with seed corpora and dictionaries.
  • Build with ASan for native code and run separate MSan and UBSan instances.
  • Fuzz pull requests in CI and run batch fuzzing to grow the corpus.
  • Store and back up corpora, and commit crash reproducers as regression tests.
  • Run API and protocol fuzzing only in isolated environments with test identities.
  • Review coverage reports and improve harnesses where code is never reached.
  • Check whether critical open source dependencies are fuzzed by OSS-Fuzz.
  • Track harness coverage of input boundaries and time to fix fuzzing crashes.

Standards and references

Related NHI Mgmt Group resources: Web Application Security Testing Guide · Secure Software Development Lifecycle (SSDLC) Guide · CWE Top 25 Guide · Vulnerability Scanning Guide · Penetration Testing Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 24 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.