· via GitHub Blog
GitHub Security Lab's Taskflow Agent runs fuzzing campaigns from harness to bug report
GitHub has open-sourced a Fuzzing Taskflow that uses its Taskflow Agent framework to autonomously pick fuzz targets, write harnesses, run AFL++ and triage crashes for C/C++ repositories.

Closing the human gap in fuzzing
GitHub's Security Lab has released a Fuzzing Taskflow, an autonomous fuzzing pipeline for C and C++ projects built on its Taskflow Agent framework for LLM-driven security automation. According to the GitHub Blog, you point the pipeline at a GitHub repository and it handles the rest: it identifies suitable entry points, analyses the build system, writes fuzz harnesses, runs AFL++, reads coverage reports, improves the harnesses, triages every crash and writes a vulnerability report for each unique bug, with no human in the loop.
The post starts from an observation: continuous fuzzing is not self-sustaining. Even projects enrolled in OSS-Fuzz for years can hide critical bugs, because someone still has to watch coverage, write harnesses for code nobody reaches and triage crashes. The taskflow is an experiment in how much of that work an LLM agent can take over.
Running it
The code lives in the GitHubSecurityLab/seclab-taskflows-fuzzing repository, and the simplest route is a Codespace. One script drives a campaign, taking an owner/repo slug as its argument:
./scripts/fuzzing/run_fuzzing.sh PROJECT
The post uses tukaani-project/xz as an example and suggests a small library such as DaveGamble/cJSON for a quick smoke test. The agent installs what it needs, including AFL, clones the repository, picks the most relevant functions and creates fuzz targets for them.
One warning stands out: the taskflow runs afl-fuzz, clang and arbitrary build commands chosen by the LLM directly on the host, with no container in between, so a prompt-injected agent could do anything the user can. GitHub advises running it only in a disposable environment, without elevated privileges.
Architecture and model choice
The pipeline has three layers: a shell driver that chains the stages, one taskflow YAML per stage (essentially the prompt telling the agent what to do at that step), and a set of MCP tools that do the actual work, such as running AFL, compiling a harness, storing a crash or reading a coverage report.
The core design rule is separation of responsibility: the agent owns decisions, the tools own execution. It never invokes AFL or clang directly, composing primitives like run_afl_for and compile_harness instead. All state lives in a SQLite database, fuzz_context.db, so stages share data only through the database rather than in memory.
The default model is Claude Sonnet 5, chosen because it passed GitHub's internal tests without issues; some frontier models impose output guardrails that interfere. Another model can be set in the pipeline's model_config.yaml.
The coverage feedback loop
This is the part that automates the manual improve-the-coverage workflow. For each harness, an iteration runs AFL within a time budget, replays the resulting queue against a coverage-instrumented binary and reads the uncovered branches. The agent then chooses an action: add a seed crafted to reach an uncovered branch, edit the harness source to call an additional API, enrich the AFL dictionary with the magic constants a guard compares against, or skip the gap when it is a cold error path or vendor code.
Time budgets double every iteration, from 30 seconds up to 960 seconds, roughly 32 minutes per target, so cheap early rounds grab low-hanging coverage while later rounds get time to push through hard guards. Stopping relies on plateau detection: when two consecutive iterations each gain less than a configurable threshold, 1% absolute line coverage by default, the loop moves on.
Every harness is also built twice. An .afl binary, compiled with afl-clang-lto and address and undefined sanitizers, does the fuzzing, while a .cov binary, compiled with Clang's coverage flags, replays AFL's queue afterwards to produce readable source-line and branch coverage, since AFL's edge instrumentation is useless for human-facing reports.
Structure-aware inputs
AFL's byte-level mutations suit binary formats but struggle with structured, text-based input, and hand-writing custom mutators for each format is tedious. The taskflow ships four mechanisms. For recognized formats, including JSON, XML, regex, PNG and length-prefixed binary TLV, it provides pre-built dictionaries and LLVMFuzzerCustomMutator files: the JSON mutator does token splicing and balanced-bracket duplication, the XML one knows about tags, entities and billion-laughs tokens, and the regex one carries real ReDoS patterns. Each delegates half its mutations back to AFL's byte mutator to keep the engine's randomization. For formats it does not recognize, it scans the target's own C source for string literals and 32-bit constants from #define, case and enum declarations, on the reasoning that a parser's magic values are usually written down in its own code. A further mechanism builds an AFL dictionary dynamically, driven by coverage.
Why it matters
Fuzzing is one of the highest-value ways to find memory-safety bugs and also one of the most labour-intensive, because it depends on skilled humans writing harnesses and triaging crashes. The taskflow is a working demonstration that an LLM agent can absorb that loop — picking targets, iterating on coverage and producing per-bug reports — inside an architecture that keeps decisions in the agent and execution in bounded tools. The caveats are real: it executes LLM-chosen build commands directly on the host, making a disposable environment mandatory, and its reports still warrant human review. For C and C++ maintainers, though, a campaign that writes and improves its own harnesses could sharply lower the cost of finding bugs that years of continuous fuzzing have missed.
- #fuzzing
- #security
- #llm-agents
- #github
- #open-source