Continuous fuzzing — running fuzzers automatically on every commit or on a scheduled cadence — is one of the highest-return security investments a development team can make. Google's OSS-Fuzz runs continuous fuzzing on over 1,000 open-source projects and has found more than 10,000 bugs since 2016. Microsoft mandates fuzzing in their Security Development Lifecycle.
Two Approaches to CI Fuzzing
- Short runs on every PR — 60–300 seconds to catch obvious regressions and verify the harness still compiles. Not enough to find subtle bugs, but catches crashes introduced by the PR immediately before it merges.
- Continuous background fuzzing — long-running campaigns on dedicated infrastructure that run 24/7, accumulate corpora, and report crashes asynchronously. This is where serious bug discovery happens.
GitHub Actions: Short Runs on Every PR
name: Fuzz
on:
pull_request:
paths: ['src/**', 'fuzz/**']
jobs:
fuzz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build fuzz target
run: |
clang -g -O1 -fsanitize=address,undefined,fuzzer \
fuzz/fuzz_parse.c src/parser.c -o fuzz_parse
- name: Run fuzzer (120 seconds)
run: |
mkdir -p corpus artifacts
./fuzz_parse corpus/ -max_total_time=120 \
-artifact_prefix=artifacts/
ls artifacts/crash-* 2>/dev/null && exit 1 || exit 0
- uses: actions/upload-artifact@v4
if: always()
with:
name: fuzz-results
path: artifacts/AFL++ in a Nightly Scheduled Job
name: AFL++ Nightly
on:
schedule:
- cron: '0 2 * * *'
jobs:
afl-fuzz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install -y afl++
- run: echo core | sudo tee /proc/sys/kernel/core_pattern
- name: Build with AFL++
env:
AFL_USE_ASAN: "1"
run: afl-cc -o my_parser_afl src/parser.c
- name: Restore corpus cache
uses: actions/cache@v4
with:
path: corpus/
key: afl-corpus
- name: Run AFL++ for 5 minutes
run: |
mkdir -p findings corpus
timeout 300 afl-fuzz -i corpus/ -o findings/ \
-V 290 -- ./my_parser_afl @@ || true
- name: Fail on crashes
run: ls findings/default/crashes/id:* 2>/dev/null && exit 1 || true
- name: Save corpus
run: cp findings/default/queue/id:* corpus/ 2>/dev/null || trueCorpus Persistence
Short fuzzing runs in CI are only effective if the fuzzer builds on previous progress. Without corpus persistence, each run starts from scratch. Options:
- GitHub Actions cache: use
actions/cachekeyed on a date or hash. Works well for corpora under 500 MB. - Git LFS: store the corpus in the repo. Ensures reproducibility but requires discipline about corpus size.
- External object storage: S3 or GCS. Most flexible for large corpora — pull at job start, push a minimized corpus at the end.
- Managed fuzzing platform: platforms like Fuzze.rs handle corpus storage automatically, accumulating seeds across all runs.
Integrating via API
- name: Start fuzzing job
run: |
JOB_ID=$(curl -sS -X POST https://api.fuzze.rs/v1/jobs \
-H "Authorization: Bearer $FUZZE_RS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"binary_url":"'"$BINARY_URL"'","fuzzer":"afl++","cores":8}' \
| jq -r '.job_id')
echo "JOB_ID=$JOB_ID" >> $GITHUB_ENV
- name: Fail on crashes
run: |
CRASHES=$(curl -sS https://api.fuzze.rs/v1/jobs/$JOB_ID/crashes \
-H "Authorization: Bearer $FUZZE_RS_API_KEY" | jq '.total')
[ "$CRASHES" -gt 0 ] && exit 1 || exit 0What to Fuzz First
Prioritize targets that process untrusted external input:
- File parsers: image decoders, document parsers, archive extractors, config readers.
- Protocol implementations: anything parsing network packets, HTTP requests, or binary protocols.
- Deserialization code: JSON parsers, protobuf decoders, XML parsers, binary format readers.
- API input handling: code that processes user-supplied parameters before reaching business logic.
Making It Sustainable
Continuous fuzzing only works if the team responds to crashes. Build the process around it:
- Route crash notifications to your existing bug tracker and security channel.
- Triage crashes weekly and track the fix rate as a team metric.
- Add crash reproducers to the regression test suite after each fix.
- The goal isn't zero crashes — it's fast detection and response.