Eine LLM-gesteuerte Fuzzing-Pipeline, angetrieben durch den GitHub Security Lab Taskflow Agent
Eine LLM-gesteuerte Fuzzing-Pipeline im OSS-Fuzz-Stil für native C/C++-Projekte. AFL++ für die Ausführung, clang+lcov für die Coverage, ein LLM-Agent für das Schreiben von Harnesses, Coverage-Feedback-Entscheidungen, Triage und Reporting.
Dieses Repository enthält den Fuzzing-Taskflow für den
GitHub Security Lab Taskflow Agent.
Es hängt vom
seclab-taskflows
Begleit-Repository für einige gemeinsame Bausteine ab
(fetch_source_code-Taskflow, local_file_viewer / gh_file_viewer
Toolboxes und die Standard-model_config) — diese werden
automatisch als Python-Abhängigkeit installiert.
Beiträge sind willkommen! Bitte siehe CONTRIBUTING.md für Richtlinien.
aptgh)pip install git+https://github.com/GitHubSecurityLab/seclab-taskflows-fuzzing
Dies zieht `seclab-taskflow-agent` und `seclab-taskflows` (Parent)
transitiv mit ein, sodass jede punktierte Referenz der Form
`seclab_taskflows.taskflows.audit.*`,
`seclab_taskflows.toolboxes.local_file_viewer`,
`seclab_taskflows.toolboxes.gh_file_viewer` und
`seclab_taskflows.configs.model_config` zur Laufzeit aus der Parent-
Distribution aufgelöst wird.
---
## Inhaltsverzeichnis
1. [Was das ist](#what-this-is)
2. [Schnellstart](#quick-start)
3. [Architektur](#architecture)
4. [Die Pipeline, Stufe für Stufe](#the-pipeline-stage-by-stage)
5. [Die Coverage-Feedback-Schleife](#the-coverage-feedback-loop)
6. [Strukturbewusstes Fuzzing](#structure-aware-fuzzing)
7. [Persistenter Korpus über Iterationen und Kampagnen hinweg](#persistent-corpus-across-iterations-and-campaigns)
8. [Triage und Schwachstellenberichte](#triage-and-vulnerability-reports)
9. [Live-Dashboard](#live-dashboard)
10. [Ausgabedateien](#output-files)
11. [Datenbankschema](#database-schema)
12. [MCP-Tools (das Vokabular des Agenten)](#mcp-tools-the-agents-vocabulary)
13. [Einstellbare Parameter (Umgebungsvariablen)](#tunable-knobs-environment-variables)
14. [Erweiterung der Pipeline](#extending-the-pipeline)
15. [Benchmark-Projekte und Ergebnisse](#benchmark-projects-and-results)
16. [Einschränkungen und Fallstricke](#limitations-and-gotchas)
17. [Sicherheitswarnung](#security-warning)
18. [Entwicklung: Testen, Linting, Mitwirken](#development-testing-linting-contributing)
19. [Glossar](#glossary)
---
## Was das ist
Dieser Taskflow ist eine vollständig autonome Fuzzing-Pipeline. Ausgehend von einem GitHub-Repo
eines nativen C/C++-Projekts wird sie:
1. AFL++ + clang/llvm/lcov + ctags/cscope/graphviz installieren, falls nicht vorhanden,
2. den Quellcode abrufen,
3. Kandidaten für Fuzz-Ziele identifizieren (Parser, Decoder, Validatoren, …),
4. das Build-System analysieren,
5. einen oder mehrere Harness-Kandidaten pro Ziel schreiben, jeden sowohl als
AFL-instrumentierte `.afl`-Binärdatei als auch als coverage-instrumentierte `.cov`-Binärdatei bauen,
6. (optional) Kandidaten durch 60-sekündige Coverage qualifizieren und die besten behalten,
7. eine Fuzz/Coverage/Improve-Schleife mit sich verdoppelnden Zeitbudgets ausführen,
8. jeden Absturz triagieren, bestätigen, dass zuvor bekannte Abstürze weiterhin reproduzierbar sind, und
pro Absturz Markdown-Schwachstellenberichte mit Urteilen, Ausnutzbarkeit,
vorgeschlagenen Patches und Skizzen für Regressionstests schreiben,
9. einen Call-Graph + Untouched-API-Bericht im Fuzz-Introspector-Stil für die
nächste Kampagne erstellen,
10. alles in einem Live-HTML-Dashboard veröffentlichen.
Die Pipeline ist im Geiste **OSS-Fuzz-artig**: Sie verwendet viele derselben
Techniken (Mutators und Dictionaries pro Format, strukturbewusstes Token-
Splicing, coverage-getriebene Harness-Verbesserungen, maschinenlesbare Berichte,
deduplizierte stack-gehashte Abstürze), ist aber viel kleiner und eigenständig.
---
## Schnellstart```bash
# Inside the codespace (or a host with python + git available):
./scripts/fuzzing/run_fuzzing.sh tukaani-project/xz
Das ist die gesamte Schnittstelle. Das Skript ist autonom; es installiert AFL++
beim ersten Lauf und steuert dann den Rest des Taskflows. Ausgabedateien werden
nach ~/.local/share/seclab-taskflow-agent/seclab-taskflows/ geschrieben.
Das Dashboard startet automatisch im Hintergrund; in einem Codespace wird Port 8765
automatisch weitergeleitet — öffne ihn in einem beliebigen Browser, um den Fortschritt live zu verfolgen.
Für einen schnellen Smoke-Test verwende ein kleines Ziel:```bash ./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON
## Architektur
Drei Schichten, von oben nach unten:```
┌────────────────────────────────────────────────────────────────────┐
│ scripts/fuzzing/run_fuzzing.sh │
│ shell driver; chains the taskflow stages with `set +e` │
└────────────────────┬───────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────┐
│ src/seclab_taskflows/taskflows/fuzzing/*.yaml │
│ LLM agent prompts; one YAML per pipeline stage │
└────────────────────┬───────────────────────────────────────────────┘
│ (calls MCP tools)
▼
┌────────────────────────────────────────────────────────────────────┐
│ src/seclab_taskflows/mcp_servers/ │
│ ├ fuzz_context.py persistence (SQLite via SQLAlchemy) │
│ └ fuzz_runner.py subprocess wrappers (AFL, clang, lcov, ...) │
│ │
│ scripts/fuzzing/dashboard.py │
│ read-only HTML view of fuzz_context.db │
└────────────────────────────────────────────────────────────────────┘
Wichtige Designregeln:
fuzz_context.db.run_afl_for, compile_harness,
store_crash usw. bereit.afl-clang-lto -fsanitize=address,undefined (die .afl-Binärdatei)
und einmal mit clang -fprofile-instr-generate -fcoverage-mapping (die .cov-Binärdatei).
Die .afl-Binärdatei fuzzt; die .cov-Binärdatei spielt die AFL-Queue
erneut ab, um echte Coverage auf Quellzeilen-/Funktions-/Branch-Ebene zu erzeugen.| # | Stufe | Taskflow-YAML |
|---|---|---|
| 1 | AFL++ + Tooling installieren | scripts/fuzzing/install_afl.sh |
| 2 | Quellcode abrufen | seclab_taskflows.taskflows.audit.fetch_source_code |
| 3 | Fuzz-Targets identifizieren | seclab_taskflows_fuzzing.taskflows.fuzzing.identify_fuzz_targets |
| 4 | Build-System analysieren | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_build_system |
| 5a | Initiale Harnesses schreiben (×N Kandidaten, falls angefordert) | seclab_taskflows_fuzzing.taskflows.fuzzing.write_initial_harnesses |
| 5b | Harnesses bauen (AFL + Coverage) | seclab_taskflows_fuzzing.taskflows.fuzzing.build_harnesses |
| 5c | Kandidaten qualifizieren (wenn HARNESS_CANDIDATES > 1) | seclab_taskflows_fuzzing.taskflows.fuzzing.qualify_harnesses |
| 6 | Fuzz-/Coverage-/Verbesserungs-Schleife (×N Iterationen) | seclab_taskflows_fuzzing.taskflows.fuzzing.fuzz_iteration |
| 7 | Crashes triagieren | seclab_taskflows_fuzzing.taskflows.fuzzing.triage_crashes |
| 8 | Bestätigen, dass zuvor bekannte Crashes weiterhin reproduzierbar sind | seclab_taskflows_fuzzing.taskflows.fuzzing.confirm_fixed_crashes |
| 9 | Call Graph + Untouched-API-Bericht erstellen | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_call_graph |
| 10 | Pro-Crash-Schwachstellenberichte schreiben | seclab_taskflows_fuzzing.taskflows.fuzzing.write_vuln_reports |
| 11 | Kampagnenbericht schreiben | seclab_taskflows_fuzzing.taskflows.fuzzing.write_report |
Jede Stufe ist ein eigenständiges Taskflow-YAML, das der Agent
Ende-zu-Ende ausführt. Die Stufen kommunizieren ausschließlich über die SQLite-Datenbank
in fuzz_context.db — es gibt keine Übergabe im Speicher.
Dies ist das Herzstück der Pipeline. Die Zeitbudgets verdoppeln sich mit jeder Iteration:``` 30s → 60s → 120s → 240s → 480s → 960s (≈ 32 min/target)
Pro Iteration, pro Harness führt der Agent Folgendes aus:
1. Fragt `get_persistent_corpus_dir(harness_id)` für das stabile
Corpus-Verzeichnis dieses Harness ab.
2. Ruft `run_afl_for(afl_binary_path, seed_dir=<persistent corpus>,
output_dir=<run dir>, seconds=<budget>, dictionary=<auto.dict>)` auf.
3. Ruft `run_coverage(cov_binary_path, inputs_dir=<run>/default/queue,
output_dir=<run>/coverage)` auf, um eine LCOV-Tracefile und einen HTML-Report zu erzeugen.
4. Ruft `store_coverage_from_lcov(run_id, lcov_path, html_path)` auf, um
eine `coverage_report`-Zeile + pro unentdecktem Element `coverage_gap`-Zeilen zu persistieren.
5. Ruft `fold_queue_into_persistent_corpus(...)` auf, um die Iterations-Queue von AFL in den persistenten Corpus zu mergen und `cmin` auszuführen, um die Größe begrenzt zu halten.
6. Liest `get_coverage_summary` + `get_coverage_gaps` und dann entweder:
- fügt einen neuen Seed hinzu (getaggt mit `coverage_feedback`), um einen unentdeckten
Branch zu erreichen,
- bearbeitet den Harness-Quellcode, um eine zusätzliche API aufzurufen,
- ruft `enrich_dictionary_from_uncovered(...)` auf, um automatisch Dictionary-
Einträge für die Magic-Konstanten hinzuzufügen, die AFL benötigt, um einen Guard zu erfüllen, oder
- überspringt die Lücke (kalter Fehlerpfad / Vendor-Code).
7. Ruft `store_iteration_note(repo, iteration_number, harness_id, note=<one
line summary>)` auf, damit die Iterations-Timeline des Dashboards nachverfolgt, was
sich geändert hat.
**Plateau-Erkennung.** Die Schleife wird früh beendet, sobald zwei aufeinanderfolgende Iterationen beide < `FUZZ_PLATEAU_THRESHOLD_PCT` (Standard `1.0`) absolute
Prozentpunkte an Zeilenabdeckung hinzugewonnen haben.
---
## Strukturbewusstes Fuzzing
Drei komplementäre Mechanismen erzeugen stärkere Eingaben als reine Byte-Mutation.
### 1. Format-spezifische Dictionaries + Custom-Mutatoren
Für Targets, deren `input_kind` einem bekannten Format entspricht, liefert die Taskflow
vorgefertigte Dictionaries und `LLVMFuzzerCustomMutator`-C-Quelldateien:
| Format | Dictionary | Mutator | Hinweise |
|--------|------------|---------|-------|
| `json` | `json.dict` | `json_mutator.c` | Token-Splice, balancierte Klammer-Dup/Drop, Typ-Flip |
| `xml` | `xml.dict` | `xml_mutator.c` | Tags, Entities, DTDs, Billion-Laughs-Token |
| `regex` | `regex.dict` | `regex_mutator.c` | Anker, Klassen, Quantifizierer, echte ReDoS-Muster |
| `binary_tlv` | _(keine)_ | `binary_tlv_mutator.c` | Längenpräfixierte Records: Längenüberlauf / Dup / Drop |
| `png` | `png.dict` | _(verwendet binary_tlv wieder)_ | PNG-Dictionary + binary_tlv-Mutator |
Diese werden automatisch von `write_initial_harnesses` (Dictionary
neben Seeds kopiert) und `build_harnesses` (Mutator in die AFL-Binary gelinkt) aufgenommen. Jeder Mutator delegiert 50 % der Mutationen an den Standard-Byte-Mutator von AFL, damit wir die Randomisierung der Engine nicht verlieren.
Um ein neues Format hinzuzufügen: Legen Sie eine `<name>.dict` und/oder eine `<name>_mutator.c` in
`src/seclab_taskflows/dictionaries/` ab und registrieren Sie es in der
`_FORMAT_ASSETS`-Map am Ende von `fuzz_runner.py`.
### 2. Quellcode-bewusster (projektspezifischer) Smart-Mutator
Für unbekannte Formate oder wann immer Sie stärkere projektspezifische
Token wünschen, scannt `generate_smart_mutator` die eigenen `.c`/`.h`-Dateien des Ziel-Repos und erzeugt eine `LLVMFuzzerCustomMutator`-C-Datei, deren Splice-Dictionaries extrahiert werden aus:
- String-Literalen mit ≥3 alphabetischen Zeichen (nach Filterung von Compiler-/Lizenz-
Rauschen, Pfaden, Headern, Asm-Constraints, Format-Spezifizierern),
- 32-Bit-numerischen Konstanten aus `#define`, `case` und `enum` (nach
Filterung von generischem Small-Int-Rauschen wie 0, 1, 256, 0xff…).
Drei Fokusse sind verfügbar:
| Fokus | Was gespliced wird | Wann zu verwenden |
|-------|-----------------|-------------|
| `strings` | Nur Projekt-String-Literale | Textformate (JSON, XML, YAML, CSV) |
| `constants` | Nur 32-Bit-numerische Magic-Werte | Binärprotokolle, Header mit Magic Numbers |
| `combined` | Beides | Standard; meist am besten |
Kombinieren Sie `generate_smart_mutators(...)` (Plural) mit `HARNESS_CANDIDATES >= 3`,
damit jeder Fokus zu einem Kandidaten-Harness in der Qualifier-Runde wird.
### 3. Projektbewusstes AFL-Dictionary + Coverage-getriebene Anreicherung
Zwei komplementäre Tools bauen und erweitern ein AFL-`-x`-Dictionary im
Verlauf der Kampagne:
- **`generate_project_dictionary(source_root, output_path)`** — läuft einmal
vor Iteration 1, extrahiert statisch denselben Quell-Token-Satz, der vom
Smart-Mutator verwendet wird, und schreibt ihn als AFL-Dictionary. Numerische Konstanten
werden in BEIDEN Endianness ausgegeben, damit der Fuzzer
`memcmp(x, &magic, 4)` unabhängig von der Host-Byte-Reihenfolge erfüllen kann.
- **`enrich_dictionary_from_uncovered(source_root, dictionary_path,
uncovered_locations)`** — läuft nach dem Coverage-Schritt jeder Iteration,
scannt den umgebenden Quellcode nach bedingten Guards
(`strncmp/memcmp/strstr`, `case 0xN:`, `== 0xN`, `== 'X'`) in der Nähe der
unentdeckten Zeilen und HÄNGT alle neuen Token an das Dictionary an. Idempotent:
fügt niemals einen Eintrag erneut hinzu, der bereits vorhanden ist.
### 4. Corpus-Splice-Operation
Wenn `corpus_dir` an `generate_smart_mutator` übergeben wird, erhält das generierte C
auch einen Corpus-Splice-Operator: Beim ersten Aufruf lädt es bis zu 64 Dateien
aus diesem Verzeichnis (jeweils auf 4 KiB begrenzt), und kann von da an zufällige Teilbereiche dieser Dateien in die mutierte Eingabe splicen. Dies gibt
dem Mutator einen Rekombinations-Operator, den AFLs Standard-Havoc nicht
gut beherrscht. Kombinieren Sie dies mit `get_persistent_corpus_dir(...)`, damit die Splice-Bibliothek
"remixen kann, was AFL bereits entdeckt hat".
---
## Persistenter Corpus über Iterationen und Kampagnen hinweg
Jeder Harness hat ein stabiles Corpus-Verzeichnis unter:```
<workspace>/corpus/harness_<id>/
Das ist, was fuzz_iteration als seed_dir für run_afl_for verwendet (anstelle von <harness>/seeds). Am Ende jeder Iteration führt fold_queue_into_persistent_corpus(...) die Iterations-Queue von AFL in dieses Verzeichnis zusammen und ruft afl-cmin auf, um sie begrenzt zu halten.
Das Ergebnis: Die Queue von gestern wird in den heutigen Lauf übernommen UND über erneute Läufe desselben Projekts hinweg. Ein Stoppen und Neustarten der Kampagne verliert keinen Fortschritt.
Nachdem die Fuzz-/Coverage-/Improve-Schleife abgeschlossen ist, laufen automatisch drei Phasen:
triage_crashesFür jede Crash-Datei in <run>/default/crashes/:
afl-tmin zur Minimierung der Eingabe,replay_under_asan zum Erfassen eines Stack-Trace und stack_top_hash
(oberste N normalisierte Frames; Templates, libcxx-Inline-Namespaces, anonyme
Namespaces und LTO-numerische Suffixe werden entfernt, damit semantisch
identische Crashes identisch gehasht werden),crash-Zeile mit Bug-Klassen-Klassifizierung +
Konfidenzhinweis (hoch / mittel / niedrig).confirm_fixed_crashesSpielt jeden zuvor klassifizierten Crash (dessen Urteil nicht bereits
fixed/duplicate/non_reproducible ist) durch die aktuelle AFL+ASan-Binärdatei erneut ab.
Wenn er nicht mehr abstürzt, wird verdict="fixed" gesetzt. Nützlich beim erneuten Ausführen einer
Kampagne gegen ein Projekt, bei dem seit der
letzten Kampagne Upstream-Fixes angewendet wurden.
write_vuln_reportsFür jeden eindeutigen Crash liest der Agent den Harness-Quellcode + den Quellcode der abstürzenden Funktion, durchläuft die Aufrufkette von der öffentlichen API aus und weist dann eines von zehn Urteilen im OSS-Fuzz-Stil zu und schreibt einen Markdown-Schwachstellenbericht:
| Urteil | Bedeutung |
|---|---|
vulnerability | Echt, über eine öffentliche API ausnutzbar |
library_hardening | Echter Bug, aber kein realistischer Pfad über die öffentliche API; die Bibliothek sollte sich dennoch selbst schützen |
harness_bug | Der Bug liegt in unserem Harness, nicht in der Bibliothek |
non_reproducible | Das Replay reproduziert den Crash mit der minimierten Eingabe nicht |
oom | Out-of-Memory; Schwachstelle nur, wenn die angreiferkontrollierbare Größe unbegrenzt ist |
timeout | DoS durch algorithmische Explosion |
assertion_failure | assert() ausgelöst; Sicherheitsrelevanz variiert |
fixed | Wird von confirm_fixed_crashes gesetzt: Eingabe reproduziert nicht mehr |
duplicate | Gleiche Grundursache wie ein anderer Crash mit einem anderen Stack-Hash |
needs_investigation | Konnte nicht bestimmt werden; zur menschlichen Überprüfung markiert |
Jeder Schwachstellenbericht enthält:
Das Dashboard wird automatisch im Hintergrund von
run_fuzzing.sh gestartet. Deaktivieren mit FUZZ_NO_DASHBOARD=1; Überschreiben des Ports
mit FUZZ_DASHBOARD_PORT (Standard 8765).
In einem Codespace wird Port 8765 automatisch weitergeleitet — öffnen Sie die weitergeleitete URL in
einem beliebigen Browser. Die Seite aktualisiert sich automatisch alle 5 s und zeigt:
fuzz_runvulnerability zuerst), mit Links
zu jedem Schwachstellenbericht und minimierten EingabenDas Dashboard stellt außerdem eine kleine schreibgeschützte JSON-API für Skripte bereit:```bash
curl 'http://127.0.0.1:8765/api/json?repo=kkos/oniguruma' | jq .
---
## Ausgabedateien
Alle unter `~/.local/share/seclab-taskflow-agent/seclab-taskflows/`.
| Pfad | Inhalt |
|------|----------|
| `fuzz_context/fuzz_context.db` | SQLite — Ziele, Harnesses, Läufe, Coverage, Crashes, Verdicts, Call Graphs, Harness-Vorschläge, Iterationsnotizen |
| `fuzz_runner/builds/` | Gebaute `.afl`- und `.cov`-Binärdateien |
| `fuzz_runner/runs/` | AFL-Ausgabeverzeichnisse + LCOV-Dateien + HTML-Coverage-Berichte |
| `fuzz_runner/corpus/harness_<id>/` | Persistenter Korpus pro Harness (bleibt über Iterationen & Kampagnen hinweg erhalten) |
| `fuzz_runner/repo/<owner>__<repo>/REPORT.md` | Markdown-Kampagnenzusammenfassung, Crashes gruppiert nach Verdict |
| `fuzz_runner/repo/<owner>__<repo>/vuln_<crash_id>.md` | Markdown-Schwachstellenbericht pro Crash |
| `fuzz_runner/repo/<owner>__<repo>/call_graph.{dot,svg,md}` | Statischer Call Graph + Overlay für erreichte/nicht erreichte Funktionen |
---
## Datenbankschema
Tabellen in `fuzz_context.db` (SQLite via SQLAlchemy):
| Tabelle | Relevante Spalten |
|-------|--------------------|
| `fuzz_target` | `repo, file, function, signature, input_kind` |
| `harness` | `target_id, repo, harness_path, afl_binary_path, cov_binary_path, build_status, version, sanitizers` |
| `seed_corpus` | `target_id, source, path, bytes_count, added_in_iteration` |
| `fuzz_run` | `harness_id, iteration_number, exec_per_sec, paths_total, crashes_count, status, output_dir, started_at, ended_at` |
| `coverage_report` | `run_id, lines_total, lines_hit, line_pct, fns_*, branches_*, lcov_path, html_path` |
| `coverage_gap` | `report_id, file, function, line, kind, reason_hint` |
| `crash` | `run_id, input_blob_path, minimized_path, stack_top_hash, sanitizer_output, verdict, bug_class, cwe, severity, vuln_report_path, reproducer_path, classification, notes` |
| `call_graph` | `repo, target_id, dot_path, svg_path, functions_total, functions_in_graph, functions_reached, functions_unreached, untouched_surface_json` |
| `harness_suggestion` | `repo, function_name, file, rationale, input_kind, priority` |
| `iteration_note` | `repo, harness_id, iteration_number, note, created_at` |
Schema-Migrationen befinden sich in `_migrate()` in `fuzz_context.py`. Neue TABLES werden
automatisch von `Base.metadata.create_all()` erstellt; nur neue COLUMNS benötigen
PRAGMA-basiertes `ALTER TABLE`.
---
## MCP-Tools (das Vokabular des Agenten)
Der Agent ruft niemals AFL oder clang direkt auf — er setzt die Pipeline zusammen,
indem er MCP-Tools aufruft. Der vollständige Satz, gruppiert nach Zweck:
### Persistenz (`fuzz_context.py`)
- `store_fuzz_target`, `get_fuzz_targets`
- `store_harness`, `update_harness_build`, `get_harnesses`
- `store_seed`, `start_fuzz_run`, `finish_fuzz_run`, `get_fuzz_runs`
- `store_coverage_from_lcov`, `get_coverage_summary`, `get_coverage_gaps`,
`coverage_plateau_reached`
- `store_crash`, `update_crash_verdict`, `get_crashes`,
`get_crashes_grouped`, `suggest_severity`
- `store_call_graph`, `get_call_graphs`, `get_repo_reached_functions`
- `store_harness_suggestion`, `get_harness_suggestions`
- `store_iteration_note`, `get_iteration_notes`
### Build / Fuzz / Coverage (`fuzz_runner.py`)
- `check_tooling`, `workspace_paths`
- `compile_harness` — baut `.afl`- und `.cov`-Binärdateien
- `run_afl_for`, `cmin`, `tmin`, `replay_under_asan`, `reproduce_crash`
- `run_coverage` — spielt die AFL-Queue gegen die `.cov`-Binärdatei ab, exportiert LCOV
- `extract_dictionary` — extrahiert druckbare Strings aus einer Binärdatei
- `package_reproducer` — bündelt einen Single-Crash-`.tgz`
### Persistenter Korpus (v8)
- `get_persistent_corpus_dir`, `fold_queue_into_persistent_corpus`
### Format-Assets (C5)
- `list_format_assets`, `get_format_dictionary`, `write_format_mutator`
### Smart Mutator + projektbewusstes Wörterbuch
- `generate_smart_mutator`, `generate_smart_mutators`
- `generate_project_dictionary`, `enrich_dictionary_from_uncovered`
Tool-Funktionen sind mit `@mcp.tool()` (FastMCP) dekoriert. In Tests
ruft man sie über das `.fn`-Attribut auf, z. B.
`fr.run_afl_for.fn(afl_binary_path=..., ...)`.
---
## Einstellbare Parameter (Umgebungsvariablen)
| Variable | Standardwert | Zweck |
|----------|---------|---------|
| `HARNESS_CANDIDATES` | `1` | Anzahl der Kandidaten-Harnesses, die pro Ziel geschrieben werden. Auf 2 oder 3 für OSS-Fuzz-Gen-artigen Wettbewerb setzen. Die Qualifizierungsphase führt jeden für `QUALIFIER_SECONDS` aus und behält den besten nach Zeilen-%. |
| `QUALIFIER_SECONDS` | `60` | Wall-Clock-Budget pro Kandidat in der Qualifizierungsphase. |
| `FUZZ_PLATEAU_THRESHOLD_PCT` | `1.0` | Zeilen-Coverage-Zuwachs (in absoluten pp), unterhalb dessen zwei aufeinanderfolgende Iterationen als Plateau gelten und die Schleife frühzeitig stoppt. |
| `FUZZ_DASHBOARD_PORT` | `8765` | Port für das Live-Dashboard. |
| `FUZZ_NO_DASHBOARD` | (nicht gesetzt) | Auf `1` setzen, um den Start des Dashboards zu überspringen. |
| `FUZZ_RUNNER_TIMEOUT` | `1200` | Subprozess-Timeout pro Tool in `fuzz_runner` (Sekunden). |
| `LOCAL_SHELL_TIMEOUT` | `180` | Timeout pro Befehl in `local_shell` (Sekunden). |
Dazu die Standard-Agent-Variablen (`COPILOT_TOKEN`, `LOG_DIR`,
`FUZZ_CONTEXT_DIR`, …). Die vollständige Liste findet sich im README des Projekt-Roots.
---
## Erweitern der Pipeline
### Hinzufügen eines neuen Formats (Mutator + Wörterbuch)
1. `dictionaries/<name>.dict` (AFL `-x`-Format) und/oder
`dictionaries/<name>_mutator.c` (libFuzzer Custom Mutator) ablegen.
2. In `_FORMAT_ASSETS` am Ende von `fuzz_runner.py` registrieren: ```python
"<name>": {
"dictionary": "<name>.dict",
"mutator": "<name>_mutator.c",
"description": "Short one-liner about the format",
},
list_format_assets().@mcp.tool() dekorierte Funktion in fuzz_context.py (für
Persistenz) oder fuzz_runner.py (für Subprozess-Arbeit) hinzu.Annotated[type, Field(description=...)] für jedes Argument — die
Beschreibung ist das, was das LLM sieht.tests/test_fuzz_context.py /
tests/test_fuzz_runner.py hinzu. Rufe das Tool über sein .fn-Attribut
auf (FastMCP-Konvention).user_prompt der relevanten Taskflow-YAML.src/seclab_taskflows/taskflows/fuzzing/. Verwende
eine der vorhandenen Dateien (z. B. triage_crashes.yaml) als Vorlage.scripts/fuzzing/run_fuzzing.sh zwischen den richtigen beiden
vorhandenen Stufen ein.scripts/fuzzing/dashboard.py hinzu.Beim Hinzufügen einer neuen SQL-Tabelle:
fuzz_context_models.py hinzu.Base.metadata.create_all() wird bei der
Engine-Initialisierung aufgerufen und erstellt neue Tabellen automatisch.Beim Hinzufügen einer neuen SPALTE zu einer vorhandenen Tabelle:
PRAGMA table_info + ALTER TABLE ADD COLUMN-Block in
_migrate() in fuzz_context.py hinzu, damit alte DBs transparent
aktualisiert werden._migrate_if_writable() in scripts/fuzzing/dashboard.py.benchmark/projects.yaml listet die Referenzprojekte auf. Sie sind so gewählt,
dass die vollständige v4+-Pipeline auf einem Codespace-Dev-Image ohne
menschliches Eingreifen von Anfang bis Ende laufen kann.
| # | Repo | Warum es interessant ist | Hinweise |
|---|---|---|---|
| 1 | tukaani-project/xz | Parser-lastige Bibliothek aus der Praxis (liblzma); reichhaltige Filterkette + Integer/VLI-Parsing-Oberfläche | Baseline |
| 2 | DaveGamble/cJSON | Kleiner ein-Datei-C-JSON-Parser; triviales CMake | Schneller Smoke-Test für die Pipeline |
| 3 | akheron/jansson | Kompakte C-JSON-Bibliothek mit dokumentiertem json_loadb()-Byte-Buffer-Einstiegspunkt | CMake; sehr schnelle exec/sec |
| 4 | libexpat/libexpat | Ausgereifter Streaming-XML-Parser; viele historische CVEs | CMake oder autotools |
| 5 | kkos/oniguruma | Regex-Engine; nimmt Angreifer-Muster + Subjekt entgegen | Autotools; Musterkompilierung ist der Hot Path |
Referenzzahlen aus einem vollständigen v4-Pipeline-Lauf auf dem Codespace-Dev-Image (≈32 min/Ziel):
| Repo | Ziele | Harnesses | AFL-Läufe | Crashes | Urteile |
|---|---|---|---|---|---|
tukaani-project/xz | 8 | 8 | 48 | 0 | — |
DaveGamble/cJSON | 6 | 6 | 36 | 0 | — |
akheron/jansson | 7 | 7 | 35 | 10 | harness_bug, library_hardening, duplicate, needs_investigation |
libexpat/libexpat | 3 | 3 | 18 | 0 | — |
kkos/oniguruma | 10 | 10 | 60 | 13 | vulnerability (×2 OOB-Read in regerror.c), library_hardening, harness_bug, non_reproducible |
Die Null-Crash-Ergebnisse von xz / cJSON / libexpat sind zu erwarten: Diese
Projekte werden upstream stark gefuzzt. Die beiden als vulnerability
klassifizierten Befunde in oniguruma sind echte Out-of-Bounds-Reads im
Warnungsformatierungs-Codepfad von onig_snprintf_with_pattern (Ein-Byte-Read
über pat_end hinaus, wenn das Muster mit einem Backslash endet); die
Markdown-Berichte pro Crash enthalten vorgeschlagene Patches.
Um ein neues Benchmark-Projekt hinzuzufügen, füge einen Eintrag in
benchmark/projects.yaml hinzu und dokumentiere (optional) in
benchmark/README.md, warum. Alles, was die vorhandene
analyze_build_system-Stufe mit clang + AFL++-Flags bauen kann, ist ein
vernünftiger Kandidat. Reine C-Parser, Decoder und Serialisierer
funktionieren in der Regel am besten.
BUILD_FAILED: und überspringt sie.kernel.core_pattern=core und
eine Anpassung des CPU-Governors. In einem Codespace sind diese nicht
verfügbar, daher exportiert der Taskflow standardmäßig AFL_SKIP_CPUFREQ=1
und AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1. AFL gibt Warnungen aus, findet
aber dennoch Crashes über libFuzzer-artige Abort-Behandlung.<dirent.h>. In Ordnung für Linux/macOS; würde unter Windows nicht
kompilieren.compile_harness gebaute AFL-Binaries
verwenden libAFLDriver im argv-Modus. replay_under_asan und tmin
verwenden daher standardmäßig stdin_input=False, weil libAFLDriver
endlos schleift, wenn er über stdin angesteuert wird.generate_smart_mutator + generate_smart_mutators verwenden Python
.format() — jedes literale { / } im C-Template muss verdoppelt werden
({{ / }}). Wenn du das Template bearbeitest und plötzlich KeyError
siehst, liegt es daran.Dieser Taskflow führt afl-fuzz, clang, llvm-cov und beliebige vom LLM
gewählte Build-Befehle direkt auf dem Host aus (kein Container). Ein
prompt-injected Agent könnte prinzipiell alles tun, was dein Benutzer kann.
Führe ihn nur aus:
git, apt und das
Build-System benötigen.Die local_shell-Toolbox ist NICHT hinter einem Bestätigungs-Prompt — der
Taskflow ist autonom und läuft ohne Mensch in der Schleife, sodass eine
interaktive Bestätigung einfach endlos blockieren würde. Jeder Shell-Befehl
wird in $LOG_DIR/mcp_local_shell.log für die nachträgliche Überprüfung
protokolliert.
hatch test
hatch fmt --linter --check
hatch fmt --linter
hatch fmt --linter --check -- src/seclab_taskflows/mcp_servers/fuzz_runner.py
Codebase-Konventionen (siehe auch `benchmark/improvements.md` für die
Kampagnenverlaufs-Version dieser):
- Verwende `os.environ.get(NAME) or "default"` statt
`os.environ.get(NAME, "default")`. Leere Strings aus der YAML-Template-
Substitution würden andernfalls zurückgegeben.
- Verwende `X | None` (PEP 604) in neuen Annotationen, nicht `Optional[X]`.
- Tests rufen MCP-Tools über `.fn(...)` auf, nicht über den dekorierten Namen direkt.
- Vermeide `/tmp/...`-Literale in Tests — verwende die `tmp_path`-pytest-Fixture
(Lint-Regel `S108`).
- Alle Inline-Imports innerhalb von Testmethoden benötigen `# noqa: PLC0415`, wenn du
sie nicht an den Anfang der Datei verschieben kannst (z. B. bei bedingtem Import
nach einem `pytest.skip`).
- Eine Assertion pro Zeile für zusammengesetzte Wahrheitstests (Lint-Regel `PT018`).
Der Verbesserungs-Tracker (`benchmark/improvements.md`) ist das persistente
Protokoll dessen, was über die Versionen hinweg zur Pipeline hinzugefügt wurde. Wenn du ein
substanzielles Feature hinzufügst, füge dort einen Abschnitt hinzu, der beschreibt, was sich geändert hat, wo es
sich befindet und welche Tests es absichern.
---
## Glossar
- **AFL++** — Coverage-gesteuerter Greybox-Fuzzer; die hier verwendete Ausführungs-Engine.
- **libAFLDriver** — Statische Bibliothek, die es AFL++-Harnesses ermöglicht, die
libFuzzer-Einstiegspunkt-Konvention (`LLVMFuzzerTestOneInput`) zu verwenden.
- **LCOV** — Industriestandard-Coverage-Tracefile-Format. Wir exportieren dorthin
über `llvm-cov export -format=lcov` und parsen es selbst.
- **`stack_top_hash`** — Ein 16-Zeichen-Hash der obersten N normalisierten Frames
eines ASan/UBSan-Stack-Trace. Wird zur Crash-Deduplizierung verwendet.
- **Persistent corpus** — Verzeichnis pro Harness unter
`<workspace>/corpus/harness_<id>/`, das AFLs interessante Inputs
über Iterationen und erneute Ausführungen derselben Kampagne hinweg trägt.
- **Smart mutator** — Ein `LLVMFuzzerCustomMutator`, dessen Splice-Tokens aus
dem Quellcode des Ziels extrahiert werden (`generate_smart_mutator`).
- **Custom mutator (libFuzzer)** — Eine vom Benutzer bereitgestellte C-Funktion, die von
der Engine mit voller Freiheit darüber aufgerufen wird, wie ein Puffer mutiert wird; AFL++ unterstützt
dieselbe ABI.
- **MCP tool** — Eine FastMCP-dekorierte Funktion, die der LLM-Agent aufrufen kann.
- **OSS-Fuzz / Fuzz-Introspector** — Googles Open-Source-Fuzzing-
Infrastruktur und das zugehörige Call-Graph-/Coverage-Analyse-Tool.
Mehrere Funktionen dieses Taskflows (Format-spezifische Mutatoren, Dedup-by-Stack,
Call-Graph- + Untouched-API-Bericht, Multi-Candidate-Harnesses) sind
von ihnen inspiriert.
---
## Lizenz
Dieses Projekt ist unter den Bedingungen der MIT-Open-Source-Lizenz lizenziert. Bitte beachte die [LICENSE](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/LICENSE.txt)-Datei für die vollständigen Bedingungen.
## Maintainer
Siehe [CODEOWNERS](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/CODEOWNERS) oder wende dich an das GitHub Security Lab Team.
## Support
Siehe [SUPPORT.md](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/SUPPORT.md) für Details dazu, wie du Hilfe zu diesem Projekt erhältst.
## Danksagung
Dieses Projekt baut auf den Konzepten und Techniken von [AFL++](https://github.com/AFLplusplus/AFLplusplus), [OSS-Fuzz](https://github.com/google/oss-fuzz) und [Fuzz-Introspector](https://github.com/ossf/fuzz-introspector) auf.