Skip to content
Back to skills

Fuzzing In Ci

ASecurity

Detect candidate fuzz targets and emit CI recipes for libFuzzer/AFL/cargo-fuzz/atheris/fast-check harnesses, plus OSS-Fuzz integration patterns

  • 207 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 27, 2026
data-aipythonrustgojavarubyswiftc++bashnodedocker

Works with

  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add jmagly/aiwg --skill fuzzing-in-ci --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fuzzing In Ci?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fuzzing In Ci
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jmagly-fuzzing-in-ci/badge)](https://www.skillsdirectory.com/skills/jmagly-fuzzing-in-ci)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
namespace: aiwg
name: fuzzing-in-ci
platforms: [all]
description: Detect candidate fuzz targets and emit CI recipes for libFuzzer/AFL/cargo-fuzz/atheris/fast-check harnesses, plus OSS-Fuzz integration patterns
requires:
  - toolchain: at least one supported language detected (C, C++, Rust, Python, Node)
  - ci-platform: known CI platform (Gitea Actions, GitHub Actions, GitLab CI)
ensures:
  - harness-scaffolds: starter fuzz harness(es) at .aiwg/security-engineering/fuzzing/{language}/
  - ci-recipe: PR-gating short-form fuzz job(s) at .aiwg/security-engineering/fuzzing/{ci-platform}/
  - long-form-guide: documentation for OSS-Fuzz integration and dedicated-runner patterns
errors:
  - no-supported-language: project has no language this skill can wire fuzzers for
  - no-candidate-targets: skill scanned for fuzz-worthy functions but found none; suggest manual seeding
invariants:
  - PR-gating fuzz runs are bounded (default 2 minutes per target) — never blocks PR indefinitely
  - long-form fuzzing is documented separately and never runs inline in PR jobs
script:
  entrypoint: scripts/emit.mjs
  runtime: node
  cwd: project-root
  argsHint: "[--language c|cpp|rust|go|python|node|auto] [--ci gitea|github|gitlab|auto] [--seconds-per-target N] [--coverage] [--oss-fuzz]"
commandHint:
  argumentHint: "[--language c|cpp|rust|go|python|node|auto] [--ci gitea|github|gitlab|auto] [--seconds-per-target N] [--coverage] [--oss-fuzz]"
  allowedTools: Read, Write, Bash, Glob, Grep
  model: sonnet
  category: security
  orchestration: false
---

# Fuzzing in CI

**You are the Fuzzing Integration Engineer** — identify functions that take untrusted input, scaffold fuzz harnesses for them, and wire short-form PR-gating fuzz jobs that complement sanitizer-enabled builds.

## Core Philosophy

"Coverage-guided fuzzing finds bugs the test suite never imagined." Fuzzers are the highest-leverage way to find input-handling bugs in parsers, deserializers, and protocol decoders. Wired into CI on every PR (with a small budget — minutes, not hours), they catch regressions immediately. Long-form fuzzing (OSS-Fuzz, dedicated runners) finds the deeper bugs over days and weeks.

## Natural Language Triggers

- "set up fuzzing"
- "add libFuzzer to CI"
- "fuzz our parser"
- "OSS-Fuzz integration"
- "property-based tests"

## Language Coverage (cycle 1)

| Language | Fuzzer            | Property-based alternative |
|----------|-------------------|----------------------------|
| C / C++  | libFuzzer (Clang), AFL++ | — |
| Rust     | cargo-fuzz (libFuzzer-backed), AFL (afl.rs) | proptest, quickcheck |
| Python   | atheris (libFuzzer-backed) | Hypothesis |
| Node.js  | jazzer.js (libFuzzer-backed) | fast-check |
| Java/JVM | jazzer (libFuzzer-backed) | jqwik |

Cycle-2 additions: Go (native go-fuzz), Swift, Ruby.

## Execution Flow

### Phase 1: Detect languages

Same `lib/toolchain-detect.sh` helper as `sanitizer-in-ci`.

### Phase 2: Identify candidate targets

Heuristics for functions that benefit most from fuzzing:

- Functions taking byte-string or `bytes`/`&[u8]`/`Buffer` input
- Functions named `parse_*`, `deserialize_*`, `decode_*`, `unmarshal_*`
- Functions with `from_str` / `from_bytes` constructors
- Public API entry points that accept untrusted input

The skill scans declarations using ripgrep:

```bash
# C/C++ — look for functions taking const uint8_t*/size_t pairs
rg -n --type c --type cpp -e 'parse_|decode_|deserialize_' src/

# Rust
rg -n --type rust -e 'fn (parse|decode|deserialize)_|FromStr|TryFrom<&\[u8\]>' src/

# Python
rg -n --type py -e 'def (parse|decode|deserialize)' src/

# Node
rg -n --type js --type ts -e 'function (parse|decode|deserialize)' src/
```

Report candidates to the operator for confirmation before scaffolding harnesses.

### Phase 3: Scaffold harnesses

Reference emitter:

```bash
agentic/code/frameworks/security-engineering/skills/fuzzing-in-ci/scripts/emit.sh \
  --language auto --ci auto --seconds-per-target 120
```

#### Example: C libFuzzer harness

`.aiwg/security-engineering/fuzzing/c/fuzz_parse.c`:

```c
// LLVMFuzzerTestOneInput is the entry point libFuzzer expects.
// Compile with: clang -fsanitize=fuzzer,address,undefined -o fuzz_parse fuzz_parse.c parse.c
#include <stdint.h>
#include <stddef.h>
#include "parse.h"

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // Constrain input shape if your parser has a minimum
    if (size < 4) return 0;

    parse_result_t *r = parse_buffer(data, size);
    if (r) {
        parse_result_free(r);
    }
    return 0;
}
```

`.aiwg/security-engineering/fuzzing/c/seed-corpus/` — populate with a few representative valid inputs.

#### Example: Rust cargo-fuzz harness

`fuzz/fuzz_targets/fuzz_parse.rs` (cargo-fuzz layout):

```rust
#![no_main]
use libfuzzer_sys::fuzz_target;
use mycrate::parse;

fuzz_target!(|data: &[u8]| {
    let _ = parse(data);  // ignore Result; we want it to not panic/UB
});
```

Project-side: `cargo install cargo-fuzz` then `cargo fuzz init && cargo fuzz add fuzz_parse`.

#### Example: Python atheris harness

`.aiwg/security-engineering/fuzzing/python/fuzz_parse.py`:

```python
import atheris
import sys

with atheris.instrument_imports():
    from mypkg import parse

def TestOneInput(data: bytes) -> None:
    try:
        parse(data)
    except (ValueError, mypkg.ParseError):
        pass  # expected on invalid input

atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
```

#### Example: Node jazzer.js harness

`.aiwg/security-engineering/fuzzing/node/fuzz_parse.js`:

```js
const { parse } = require("../../../src");

module.exports.fuzz = function (data /* Buffer */) {
  try {
    parse(data);
  } catch (e) {
    // Only catch the parser's documented error class; let everything else propagate
    if (!(e instanceof require("../../../src").ParseError)) throw e;
  }
};
```

### Phase 4: PR-gating CI recipe

Short-form fuzzing on every PR. Default budget: **2 minutes per target**, with a corpus that grows over time.

#### Example: C libFuzzer in CI

```yaml
# .aiwg/security-engineering/fuzzing/github/c.yaml
name: Fuzz (PR)
on:
  pull_request:
  push:
    branches: [main]

jobs:
  libfuzzer:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5  # v4.3.1
      - name: Install clang
        run: apt-get update && apt-get install -y clang
      - name: Cache fuzz corpus
        uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684  # v4.2.3
        with:
          path: .aiwg/security-engineering/fuzzing/c/corpus
          key: fuzz-c-corpus-${{ github.run_id }}
          restore-keys: fuzz-c-corpus-
      - name: Build fuzz target
        run: clang -fsanitize=fuzzer,address,undefined -O1 -g -o fuzz_parse \
                .aiwg/security-engineering/fuzzing/c/fuzz_parse.c src/parse.c
      - name: Fuzz for 120 seconds
        run: |
          mkdir -p .aiwg/security-engineering/fuzzing/c/corpus
          ./fuzz_parse -max_total_time=120 \
            .aiwg/security-engineering/fuzzing/c/corpus \
            .aiwg/security-engineering/fuzzing/c/seed-corpus
      - name: Upload crashes
        if: failure()
        uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882  # v4.4.3
        with:
          name: fuzz-crashes-c
          path: crash-*
```

### Phase 5: OSS-Fuzz integration guide

`.aiwg/security-engineering/fuzzing/OSS-FUZZ.md`:

```markdown
# OSS-Fuzz Integration

OSS-Fuzz runs continuous fuzzing on Google infrastructure for free, for qualifying open-source projects.

## Eligibility

- Open-source project with significant impact (usage in production, dependency of widely-used software, etc.)
- Submit via https://github.com/google/oss-fuzz/blob/master/docs/getting-started/new_project_guide.md

## Required files

For a project named `myproject`, OSS-Fuzz needs:

1. `projects/myproject/project.yaml` — homepage, language, sanitizers
2. `projects/myproject/Dockerfile` — build environment
3. `projects/myproject/build.sh` — compiles fuzz targets

Example:

```yaml
# project.yaml
homepage: "https://example.com/myproject"
language: c
primary_contact: "security@example.com"
auto_ccs:
  - "another-maintainer@example.com"
sanitizers:
  - address
  - undefined
  - memory  # optional, requires MSan-clean deps
```

## Workflow

1. OSS-Fuzz builds your fuzz targets daily from your repo
2. Runs them continuously on shared infrastructure
3. New crashes filed as private bugs to your maintainer-contact list
4. Crashes auto-disclose 90 days after report OR when patched (whichever first)

## Local testing

```bash
# Mirror what OSS-Fuzz does locally:
python infra/helper.py build_image myproject
python infra/helper.py build_fuzzers --sanitizer address myproject
python infra/helper.py run_fuzzer myproject fuzz_parse
```
```

### Phase 6: Property-based testing recipes

For languages or codebases where coverage-guided fuzzing is awkward (high-level Python, async-heavy JS, JVM with lots of reflection), property-based testing is often easier to adopt and complementary.

`.aiwg/security-engineering/fuzzing/python/property_test.py`:

```python
from hypothesis import given, strategies as st
from mypkg import parse, serialize

@given(st.binary(min_size=0, max_size=10_000))
def test_parse_never_panics(data):
    try:
        parse(data)
    except mypkg.ParseError:
        pass

@given(st.text())
def test_roundtrip(s):
    assert parse(serialize(s)) == s
```

`.aiwg/security-engineering/fuzzing/node/property_test.js` (fast-check):

```js
const fc = require("fast-check");
const { parse, serialize } = require("../../../src");

test("roundtrip", () => {
  fc.assert(fc.property(fc.string(), (s) => parse(serialize(s)) === s));
});

test("parse handles arbitrary bytes", () => {
  fc.assert(
    fc.property(fc.uint8Array(), (bytes) => {
      try { parse(Buffer.from(bytes)); } catch (e) { /* expected */ }
    })
  );
});
```

## Composition

- `sanitizer-in-ci` — fuzzers find inputs that trigger UB; sanitizers detect the UB. Pair them.
- `ci-action-pinning` — all action SHAs are pinned.

## Implementation Status

- `scripts/emit.sh` emits starter harnesses under `.aiwg/security-engineering/fuzzing/{language}/`.
- PR-gating recipes are emitted under `.aiwg/security-engineering/fuzzing/{ci-platform}/`.
- The emitter also creates starter `OSS-FUZZ.md` and `merge_corpus.sh` placeholders.
- Project-specific target binding is still required: replace placeholder parser calls with real APIs and seed corpora.

## References

- @$AIWG_ROOT/agentic/code/frameworks/security-engineering/skills/sanitizer-in-ci/SKILL.md — Companion sanitizer skill
- `.aiwg/security/curl-checklist-gap-analysis.md` row 16
- libFuzzer: https://llvm.org/docs/LibFuzzer.html
- cargo-fuzz: https://github.com/rust-fuzz/cargo-fuzz
- atheris: https://github.com/google/atheris
- jazzer.js: https://github.com/CodeIntelligenceTesting/jazzer.js
- AFL++: https://aflplus.plus/
- OSS-Fuzz: https://google.github.io/oss-fuzz/
- Hypothesis: https://hypothesis.readthedocs.io/
- fast-check: https://github.com/dubzzz/fast-check
- proptest (Rust): https://github.com/proptest-rs/proptest

Files in this skill

  • SKILL.md11 KB
  • scripts/emit.mjs4.8 KB
  • scripts/emit.sh135 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.