Una pipeline di fuzzing basata su LLM alimentata dal GitHub Security Lab Taskflow Agent
Una pipeline di fuzzing in stile OSS-Fuzz, guidata da LLM, per progetti nativi C/C++. AFL++ per l'esecuzione, clang+lcov per la copertura, un agente LLM per la scrittura degli harness, le decisioni basate sul feedback di copertura, il triage e il reporting.
Questo repository contiene il taskflow di fuzzing per il
GitHub Security Lab Taskflow Agent.
Dipende dal repository complementare
seclab-taskflows
per alcuni blocchi di costruzione condivisi
(taskflow fetch_source_code, toolbox local_file_viewer / gh_file_viewer
e il model_config predefinito) — questi vengono installati
automaticamente come dipendenza Python.
I contributi sono benvenuti! Consulta CONTRIBUTING.md per le linee guida.
aptgh)pip install git+https://github.com/GitHubSecurityLab/seclab-taskflows-fuzzing
Questo include transitivamente `seclab-taskflow-agent` e `seclab-taskflows` (parent),
quindi ogni riferimento puntato nella forma
`seclab_taskflows.taskflows.audit.*`,
`seclab_taskflows.toolboxes.local_file_viewer`,
`seclab_taskflows.toolboxes.gh_file_viewer`, e
`seclab_taskflows.configs.model_config` viene risolto dalla distribuzione
parent a runtime.
---
## Indice
1. [Cos'è questo](#what-this-is)
2. [Avvio rapido](#quick-start)
3. [Architettura](#architecture)
4. [La pipeline, fase per fase](#the-pipeline-stage-by-stage)
5. [Il ciclo di feedback sulla copertura](#the-coverage-feedback-loop)
6. [Fuzzing structure-aware](#structure-aware-fuzzing)
7. [Corpus persistente tra iterazioni e campagne](#persistent-corpus-across-iterations-and-campaigns)
8. [Triage e report di vulnerabilità](#triage-and-vulnerability-reports)
9. [Dashboard live](#live-dashboard)
10. [File di output](#output-files)
11. [Schema del database](#database-schema)
12. [Strumenti MCP (il vocabolario dell'agente)](#mcp-tools-the-agents-vocabulary)
13. [Parametri regolabili (variabili d'ambiente)](#tunable-knobs-environment-variables)
14. [Estendere la pipeline](#extending-the-pipeline)
15. [Progetti di benchmark e risultati](#benchmark-projects-and-results)
16. [Limitazioni e insidie](#limitations-and-gotchas)
17. [Avviso di sicurezza](#security-warning)
18. [Sviluppo: testing, linting, contribuire](#development-testing-linting-contributing)
19. [Glossario](#glossary)
---
## Cos'è questo
Questo taskflow è una pipeline di fuzzing completamente autonoma. Dato un repository GitHub
di un progetto nativo C/C++, esso:
1. installa AFL++ + clang/llvm/lcov + ctags/cscope/graphviz se mancanti,
2. recupera il codice sorgente,
3. identifica i candidati target di fuzzing (parser, decoder, validatori, …),
4. analizza il sistema di build,
5. scrive uno o più candidati harness per target, compilando ciascuno sia come
binario `.afl` instrumentato con AFL sia come binario `.cov` instrumentato per la copertura,
6. (opzionalmente) qualifica i candidati tramite copertura di 60 secondi e mantiene il migliore,
7. esegue un ciclo fuzz/copertura/miglioramento con budget temporali raddoppianti,
8. effettua il triage di ogni crash, conferma che i crash precedentemente noti si riproducono ancora, e
scrive report markdown di vulnerabilità per ogni crash con verdetti, sfruttabilità,
patch suggerite e bozze di test di regressione,
9. costruisce un call graph in stile Fuzz-Introspector + report delle API non toccate per la
campagna successiva,
10. pubblica tutto su una dashboard HTML live.
La pipeline è **in stile OSS-Fuzz**: utilizza molte delle stesse
tecniche (mutatori e dizionari per formato, token splicing structure-aware, miglioramenti degli harness guidati dalla copertura, report machine-readable,
crash deduplicati con hash dello stack) ma è molto più piccola e autosufficiente.
---
## Avvio rapido```bash
# Inside the codespace (or a host with python + git available):
./scripts/fuzzing/run_fuzzing.sh tukaani-project/xz
È tutta l'interfaccia. Lo script è autonomo; installerà AFL++
alla prima esecuzione, poi guiderà il resto del taskflow. I file di output vengono scritti
in ~/.local/share/seclab-taskflow-agent/seclab-taskflows/.
La dashboard si avvia automaticamente in background; in un Codespace, la porta 8765 viene
inoltrata automaticamente — aprila in qualsiasi browser per seguire i progressi in tempo reale.
Per un rapido smoke-test, usa un target di piccole dimensioni:```bash ./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON
## Architettura
Tre livelli, dall'alto verso il basso:```
┌────────────────────────────────────────────────────────────────────┐
│ 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 │
└────────────────────────────────────────────────────────────────────┘
Regole di progettazione chiave:
fuzz_context.db.run_afl_for, compile_harness, store_crash, ecc.afl-clang-lto -fsanitize=address,undefined (il binario .afl) e una
con clang -fprofile-instr-generate -fcoverage-mapping (il binario .cov).
Il binario .afl esegue il fuzzing; il binario .cov riproduce la coda AFL
per produrre una copertura reale di righe sorgente/funzioni/branch.| # | Fase | Taskflow YAML |
|---|---|---|
| 1 | Installare AFL++ + tooling | scripts/fuzzing/install_afl.sh |
| 2 | Recuperare il sorgente | seclab_taskflows.taskflows.audit.fetch_source_code |
| 3 | Identificare i target di fuzzing | seclab_taskflows_fuzzing.taskflows.fuzzing.identify_fuzz_targets |
| 4 | Analizzare il sistema di build | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_build_system |
| 5a | Scrivere gli harness iniziali (×N candidati se richiesto) | seclab_taskflows_fuzzing.taskflows.fuzzing.write_initial_harnesses |
| 5b | Compilare gli harness (AFL + copertura) | seclab_taskflows_fuzzing.taskflows.fuzzing.build_harnesses |
| 5c | Qualificare i candidati (quando HARNESS_CANDIDATES > 1) | seclab_taskflows_fuzzing.taskflows.fuzzing.qualify_harnesses |
| 6 | Ciclo fuzz/copertura/miglioramento (×N iterazioni) | seclab_taskflows_fuzzing.taskflows.fuzzing.fuzz_iteration |
| 7 | Triage dei crash | seclab_taskflows_fuzzing.taskflows.fuzzing.triage_crashes |
| 8 | Confermare che i crash precedentemente noti si riproducono ancora | seclab_taskflows_fuzzing.taskflows.fuzzing.confirm_fixed_crashes |
| 9 | Costruire il call graph + report delle API non toccate | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_call_graph |
| 10 | Scrivere report di vulnerabilità per ogni crash | seclab_taskflows_fuzzing.taskflows.fuzzing.write_vuln_reports |
| 11 | Scrivere il report della campagna | seclab_taskflows_fuzzing.taskflows.fuzzing.write_report |
Ogni fase è un taskflow YAML autonomo che l'agente esegue
end-to-end. Le fasi comunicano esclusivamente attraverso il database SQLite
in fuzz_context.db — non c'è alcun passaggio di consegne in memoria.
Questo è il cuore della pipeline. I budget di tempo raddoppiano a ogni iterazione:``` 30s → 60s → 120s → 240s → 480s → 960s (≈ 32 min/target)
Ad ogni iterazione, per ogni harness, l'agente:
1. Chiama `get_persistent_corpus_dir(harness_id)` per ottenere la directory
del corpus stabile di questo harness.
2. Chiama `run_afl_for(afl_binary_path, seed_dir=<persistent corpus>,
output_dir=<run dir>, seconds=<budget>, dictionary=<auto.dict>)`.
3. Chiama `run_coverage(cov_binary_path, inputs_dir=<run>/default/queue,
output_dir=<run>/coverage)` per produrre un tracefile LCOV e un report HTML.
4. Chiama `store_coverage_from_lcov(run_id, lcov_path, html_path)` per persistere
una riga `coverage_report` + righe `coverage_gap` per ogni elemento non coperto.
5. Chiama `fold_queue_into_persistent_corpus(...)` per unire la coda di iterazione
di AFL nel corpus persistente ed eseguire `cmin` per mantenere la dimensione limitata.
6. Legge `get_coverage_summary` + `get_coverage_gaps`, poi alternativamente:
- aggiunge un nuovo seed (etichettato `coverage_feedback`) per raggiungere un
branch non coperto,
- modifica il sorgente dell'harness per chiamare un'API aggiuntiva,
- chiama `enrich_dictionary_from_uncovered(...)` per aggiungere automaticamente
voci al dizionario per le costanti magiche di cui AFL ha bisogno per soddisfare
un guard, oppure
- salta il gap (percorso di errore freddo / codice del vendor).
7. Chiama `store_iteration_note(repo, iteration_number, harness_id, note=<riepilogo
su una riga>)` in modo che la timeline delle iterazioni della dashboard tracci cosa
è cambiato.
**Rilevamento del plateau.** Il loop termina anticipatamente quando due iterazioni
consecutive hanno entrambe guadagnato < `FUZZ_PLATEAU_THRESHOLD_PCT` (default `1.0`)
punti percentuali assoluti di copertura delle righe.
---
## Fuzzing structure-aware
Tre meccanismi complementari producono input più forti della semplice mutazione di byte.
### 1. Dizionari per formato + mutatori personalizzati
Per i target il cui `input_kind` corrisponde a un formato noto, il taskflow include
dizionari pre-costruiti e file sorgente C `LLVMFuzzerCustomMutator`:
| Formato | Dizionario | Mutatore | Note |
|--------|------------|---------|-------|
| `json` | `json.dict` | `json_mutator.c` | Splice di token, dup/drop di parentesi bilanciate, type flip |
| `xml` | `xml.dict` | `xml_mutator.c` | Tag, entità, DTD, token billion-laughs |
| `regex` | `regex.dict` | `regex_mutator.c` | Ancore, classi, quantificatori, pattern ReDoS reali |
| `binary_tlv` | _(nessuno)_ | `binary_tlv_mutator.c` | Record con prefisso di lunghezza: length-overflow / dup / drop |
| `png` | `png.dict` | _(riusa binary_tlv)_ | Dizionario PNG + mutatore binary_tlv |
Questi vengono raccolti automaticamente da `write_initial_harnesses` (dizionario
copiato accanto ai seed) e `build_harnesses` (mutatore linkato nel binario AFL).
Ogni mutatore delega il 50% delle mutazioni al mutatore di byte predefinito di AFL
per non perdere la randomizzazione del motore.
Per aggiungere un nuovo formato: inserisci un `<name>.dict` e/o un `<name>_mutator.c`
in `src/seclab_taskflows/dictionaries/`, poi registralo nella mappa `_FORMAT_ASSETS`
in fondo a `fuzz_runner.py`.
### 2. Mutatore smart source-aware (specifico del progetto)
Per formati sconosciuti, o ogni volta che vuoi token più forti specifici del progetto,
`generate_smart_mutator` analizza i file `.c`/`.h` del repository target ed emette un
file C `LLVMFuzzerCustomMutator` i cui dizionari di splice sono estratti da:
- stringhe letterali con ≥3 caratteri alfabetici (dopo aver filtrato rumore di
compilatore/licenza, percorsi, header, vincoli asm, specificatori di formato),
- costanti numeriche a 32 bit da `#define`, `case` ed `enum` (dopo aver filtrato
rumore generico di piccoli interi come 0, 1, 256, 0xff…).
Sono disponibili tre focus:
| Focus | Cosa fa il splice | Quando usarlo |
|-------|-----------------|-------------|
| `strings` | Solo stringhe letterali del progetto | Formati testuali (JSON, XML, YAML, CSV) |
| `constants` | Solo valori magici numerici a 32 bit | Protocolli binari, header con numeri magici |
| `combined` | Entrambi | Default; di solito il migliore |
Abbina `generate_smart_mutators(...)` (plurale) con `HARNESS_CANDIDATES >= 3`
così ogni focus diventa un harness candidato nel round di qualificazione.
### 3. Dizionario AFL project-aware + arricchimento guidato dalla copertura
Due strumenti complementari costruiscono e fanno crescere un dizionario AFL `-x`
man mano che la campagna progredisce:
- **`generate_project_dictionary(source_root, output_path)`** — eseguito una volta
prima dell'iterazione 1, estrae staticamente lo stesso insieme di token sorgente
usato dal mutatore smart e lo scrive come dizionario AFL. Le costanti numeriche
sono emesse in ENTRAMBI gli endianness così il fuzzer può soddisfare
`memcmp(x, &magic, 4)` indipendentemente dall'ordine dei byte dell'host.
- **`enrich_dictionary_from_uncovered(source_root, dictionary_path,
uncovered_locations)`** — eseguito dopo la fase di copertura di ogni iterazione,
analizza il sorgente circostante per guard condizionali
(`strncmp/memcmp/strstr`, `case 0xN:`, `== 0xN`, `== 'X'`) vicino alle righe
non coperte, e APPENDE qualsiasi nuovo token al dizionario. Idempotente:
non riaggiunge mai una voce già presente.
### 4. Operazione di corpus-splice
Quando `corpus_dir` viene passato a `generate_smart_mutator`, il C generato
ottiene anche un operatore di corpus-splice: alla prima chiamata carica fino a 64 file
da quella directory (limitati a 4 KiB ciascuno), e da quel momento può fare splice
di sotto-regioni casuali di quei file nell'input mutato. Questo dà al mutatore un
operatore in stile ricombinazione che l'havoc standard di AFL non esegue bene.
Abbinalo con `get_persistent_corpus_dir(...)` così la libreria di splice
è "remix di ciò che AFL ha già scoperto".
---
## Corpus persistente tra iterazioni e campagne
Ogni harness ha una directory del corpus stabile in:```
<workspace>/corpus/harness_<id>/
Questo è ciò che fuzz_iteration usa come seed_dir per run_afl_for (invece
di <harness>/seeds). Alla fine di ogni iterazione,
fold_queue_into_persistent_corpus(...) unisce la coda di iterazione di AFL in
questa directory ed esegue afl-cmin per mantenerla limitata.
Il risultato: la coda di ieri si trasferisce nell'esecuzione di oggi E attraverso le riesecuzioni dello stesso progetto. Un arresto e riavvio della campagna non perde alcun progresso.
Dopo che il ciclo fuzz/coverage/improve termina, tre fasi vengono eseguite automaticamente:
triage_crashesPer ogni file di crash in <run>/default/crashes/:
afl-tmin per minimizzare l'input,replay_under_asan per catturare uno stack trace e stack_top_hash
(top-N frame normalizzati; template, namespace inline di libcxx, namespace
anonimi e suffissi numerici LTO vengono rimossi così che crash semanticamente
identici abbiano lo stesso hash),crash con classificazione della
bug-class + nota di confidenza (alta / media / bassa).confirm_fixed_crashesRiproduce ogni crash precedentemente classificato (il cui verdetto non sia già
fixed/duplicate/non_reproducible) attraverso il binario AFL+ASan corrente.
Se non causa più crash, imposta verdict="fixed". Utile quando si riesegue una
campagna contro un progetto che ha ricevuto correzioni upstream dall'ultima
campagna.
write_vuln_reportsPer ogni crash unico, l'agente legge il sorgente dell'harness + il sorgente della funzione che causa il crash, percorre la catena di chiamate dall'API pubblica, quindi assegna uno tra dieci verdetti in stile OSS-Fuzz e scrive un report di vulnerabilità in markdown:
| Verdetto | Significato |
|---|---|
vulnerability | Reale, sfruttabile attraverso un'API pubblica |
library_hardening | Bug reale ma nessun percorso realistico tramite API pubblica; la libreria dovrebbe comunque difendersi |
harness_bug | Il bug è nel nostro harness, non nella libreria |
non_reproducible | Il replay non riproduce il crash sull'input minimizzato |
oom | Out-of-memory; vulnerabilità solo se la dimensione controllabile dall'attaccante è illimitata |
timeout | DoS tramite esplosione algoritmica |
assertion_failure | assert() attivato; la rilevanza per la sicurezza varia |
fixed | Impostato da confirm_fixed_crashes: l'input non riproduce più |
duplicate | Stessa causa radice di un altro crash con un hash dello stack diverso |
needs_investigation | Impossibile determinare; segnalato per revisione umana |
Ogni report di vulnerabilità include:
La dashboard viene avviata automaticamente in background da
run_fuzzing.sh. Disabilitare con FUZZ_NO_DASHBOARD=1; sovrascrivere la porta
con FUZZ_DASHBOARD_PORT (predefinita 8765).
In un Codespace, la porta 8765 viene inoltrata automaticamente — aprire l'URL
inoltrato in qualsiasi browser. La pagina si aggiorna automaticamente ogni 5 s e
mostra:
fuzz_run in corsovulnerability per primo), con link
a ciascun report di vulnerabilità e all'input minimizzatoLa dashboard espone anche una piccola API JSON di sola lettura per gli script:```bash
curl 'http://127.0.0.1:8765/api/json?repo=kkos/oniguruma' | jq .
---
## File di output
Tutti sotto `~/.local/share/seclab-taskflow-agent/seclab-taskflows/`.
| Percorso | Contenuto |
|------|----------|
| `fuzz_context/fuzz_context.db` | SQLite — target, harness, esecuzioni, copertura, crash, verdetti, call graph, suggerimenti di harness, note di iterazione |
| `fuzz_runner/builds/` | Binari `.afl` e `.cov` compilati |
| `fuzz_runner/runs/` | Directory di output AFL + file LCOV + report di copertura HTML |
| `fuzz_runner/corpus/harness_<id>/` | Corpus persistente per harness (persiste tra iterazioni e campagne) |
| `fuzz_runner/repo/<owner>__<repo>/REPORT.md` | Riepilogo della campagna in Markdown, crash raggruppati per verdetto |
| `fuzz_runner/repo/<owner>__<repo>/vuln_<crash_id>.md` | Report di vulnerabilità in Markdown per singolo crash |
| `fuzz_runner/repo/<owner>__<repo>/call_graph.{dot,svg,md}` | Call graph statico + overlay raggiunto/non raggiunto |
---
## Schema del database
Tabelle in `fuzz_context.db` (SQLite tramite SQLAlchemy):
| Tabella | Colonne di interesse |
|-------|--------------------|
| `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` |
Le migrazioni dello schema risiedono in `_migrate()` in `fuzz_context.py`. Le nuove TABELLE vengono
create automaticamente da `Base.metadata.create_all()`; solo le nuove COLONNE necessitano
di `ALTER TABLE` basato su PRAGMA.
---
## Strumenti MCP (il vocabolario dell'agente)
L'agente non chiama mai AFL o clang direttamente — compone la pipeline
chiamando gli strumenti MCP. L'insieme completo, raggruppato per scopo:
### Persistenza (`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 / copertura (`fuzz_runner.py`)
- `check_tooling`, `workspace_paths`
- `compile_harness` — compila i binari `.afl` e `.cov`
- `run_afl_for`, `cmin`, `tmin`, `replay_under_asan`, `reproduce_crash`
- `run_coverage` — riproduce la coda AFL contro il binario `.cov`, esporta LCOV
- `extract_dictionary` — estrae stringhe stampabili da un binario
- `package_reproducer` — raggruppa un singolo crash in un `.tgz`
### Corpus persistente (v8)
- `get_persistent_corpus_dir`, `fold_queue_into_persistent_corpus`
### Asset di formato (C5)
- `list_format_assets`, `get_format_dictionary`, `write_format_mutator`
### Mutator intelligente + dizionario consapevole del progetto
- `generate_smart_mutator`, `generate_smart_mutators`
- `generate_project_dictionary`, `enrich_dictionary_from_uncovered`
Le funzioni degli strumenti sono decorate con `@mcp.tool()` (FastMCP). Nei test,
invocale tramite l'attributo `.fn`, ad es.
`fr.run_afl_for.fn(afl_binary_path=..., ...)`.
---
## Parametri configurabili (variabili d'ambiente)
| Variabile | Predefinito | Scopo |
|----------|---------|---------|
| `HARNESS_CANDIDATES` | `1` | Numero di harness candidati scritti per target. Imposta a 2 o 3 per una competizione in stile OSS-Fuzz-Gen. La fase di qualificazione esegue ciascuno per `QUALIFIER_SECONDS` e mantiene il migliore per % di righe. |
| `QUALIFIER_SECONDS` | `60` | Budget di tempo reale per candidato nella fase di qualificazione. |
| `FUZZ_PLATEAU_THRESHOLD_PCT` | `1.0` | Guadagno di copertura delle righe (in pp assoluti) al di sotto del quale due iterazioni consecutive sono considerate un plateau e il ciclo si interrompe anticipatamente. |
| `FUZZ_DASHBOARD_PORT` | `8765` | Porta per la dashboard live. |
| `FUZZ_NO_DASHBOARD` | (non impostata) | Imposta a `1` per saltare l'avvio della dashboard. |
| `FUZZ_RUNNER_TIMEOUT` | `1200` | Timeout del sottoprocesso per strumento in `fuzz_runner` (secondi). |
| `LOCAL_SHELL_TIMEOUT` | `180` | Timeout per comando in `local_shell` (secondi). |
Più le variabili standard dell'agente (`COPILOT_TOKEN`, `LOG_DIR`,
`FUZZ_CONTEXT_DIR`, …). Vedi il README nella radice del progetto per l'elenco completo.
---
## Estendere la pipeline
### Aggiungere un nuovo formato (mutator + dizionario)
1. Inserisci `dictionaries/<name>.dict` (formato AFL `-x`) e/o
`dictionaries/<name>_mutator.c` (mutator personalizzato libFuzzer).
2. Registra in `_FORMAT_ASSETS` in fondo a `fuzz_runner.py`: ```python
"<name>": {
"dictionary": "<name>.dict",
"mutator": "<name>_mutator.c",
"description": "Short one-liner about the format",
},
list_format_assets().@mcp.tool() in fuzz_context.py (per
la persistenza) o fuzz_runner.py (per il lavoro in subprocess).Annotated[type, Field(description=...)] per ogni argomento — la
descrizione è ciò che vede l'LLM.tests/test_fuzz_context.py /
tests/test_fuzz_runner.py. Invoca lo strumento tramite il suo attributo .fn
(convenzione FastMCP).user_prompt del taskflow YAML
pertinente.src/seclab_taskflows/taskflows/fuzzing/. Usa
uno dei file esistenti (ad es. triage_crashes.yaml) come modello.scripts/fuzzing/run_fuzzing.sh tra le due fasi
esistenti appropriate.scripts/fuzzing/dashboard.py.Quando aggiungi una nuova tabella SQL:
fuzz_context_models.py.Base.metadata.create_all() viene chiamato all'inizializzazione
dell'engine e crea automaticamente le nuove tabelle.Quando aggiungi una nuova COLONNA a una tabella esistente:
PRAGMA table_info + ALTER TABLE ADD COLUMN in
_migrate() in fuzz_context.py in modo che i vecchi DB vengano aggiornati in modo trasparente._migrate_if_writable() in scripts/fuzzing/dashboard.py.benchmark/projects.yaml elenca i progetti di riferimento. Sono scelti in modo che
l'intera pipeline v4+ possa essere eseguita end-to-end su un'immagine dev di codespace senza
intervento umano.
| # | Repo | Perché è interessante | Note |
|---|---|---|---|
| 1 | tukaani-project/xz | Libreria reale ad alto uso di parser (liblzma); ricca catena di filtri + superficie di parsing integer/VLI | Baseline |
| 2 | DaveGamble/cJSON | Piccolo parser JSON C in singolo file; CMake banale | Smoke test rapido per la pipeline |
| 3 | akheron/jansson | Libreria JSON C compatta con punto di ingresso documentato json_loadb() per byte-buffer | CMake; exec/sec molto veloce |
| 4 | libexpat/libexpat | Parser XML streaming maturo; molte CVE storiche | CMake o autotools |
| 5 | kkos/oniguruma | Motore regex; accetta pattern + subject dell'attaccante | Autotools; la compilazione del pattern è il percorso critico |
Numeri di riferimento da un'esecuzione completa della pipeline v4 sull'immagine dev di codespace (≈32 min/target):
| Repo | Target | Harness | Esecuzioni AFL | Crash | Verdetti |
|---|---|---|---|---|---|
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 |
I risultati a zero crash di xz / cJSON / libexpat sono attesi: quei progetti
sono pesantemente fuzzati upstream. I due findings classificati come vulnerability
in oniguruma sono reali letture out-of-bounds nel percorso di codice di formattazione degli avvisi
di onig_snprintf_with_pattern (lettura di un byte oltre pat_end quando
il pattern termina con un backslash); i report markdown per-crash
includono patch suggerite.
Per aggiungere un nuovo progetto di benchmark, aggiungi una voce in benchmark/projects.yaml
e (opzionalmente) documenta il motivo in benchmark/README.md. Qualsiasi cosa che la
fase esistente analyze_build_system possa compilare con clang + flag AFL++
è un candidato ragionevole. I parser, decoder e serializzatori in puro C tendono
a funzionare meglio.
BUILD_FAILED:
e li salta.kernel.core_pattern=core e una
modifica al CPU governor. In un Codespace questi non sono disponibili, quindi il
taskflow esporta AFL_SKIP_CPUFREQ=1 e
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 per impostazione predefinita. AFL stampa
avvisi ma trova comunque crash tramite la gestione degli abort in stile libFuzzer.<dirent.h>. Va bene per Linux/macOS; non compilerebbe su Windows.compile_harness usano
libAFLDriver in modalità argv. replay_under_asan e tmin quindi
usano stdin_input=False per impostazione predefinita perché libAFLDriver entra in loop infinito quando
pilotato via stdin.generate_smart_mutator + generate_smart_mutators usano .format() di Python
— ogni { / } letterale nel template C deve essere raddoppiato
({{ / }}). Se modifichi il template e inizi a vedere KeyError,
ecco perché.Questo taskflow esegue afl-fuzz, clang, llvm-cov, e comandi di build arbitrari
scelti dall'LLM, direttamente sull'host (nessun container). Un
agente soggetto a prompt injection potrebbe in linea di principio fare qualsiasi cosa possa fare il tuo utente. Esegui
solo:
git, apt e il build system
hanno bisogno.Il toolbox local_shell NON è dietro un prompt di conferma — il
taskflow è autonomo e viene eseguito senza un umano nel loop, quindi una
conferma interattiva si bloccherebbe semplicemente per sempre. Ogni comando shell viene
registrato in $LOG_DIR/mcp_local_shell.log per una revisione a posteriori.
hatch test
hatch fmt --linter --check
hatch fmt --linter
hatch fmt --linter --check -- src/seclab_taskflows/mcp_servers/fuzz_runner.py
Convenzioni del codebase (vedi anche `benchmark/improvements.md` per la
versione di queste relative alla cronologia delle campagne):
- Usa `os.environ.get(NAME) or "default"` invece di
`os.environ.get(NAME, "default")`. Le stringhe vuote derivanti dalla
sostituzione dei template YAML verrebbero altrimenti restituite.
- Usa `X | None` (PEP 604) nelle nuove annotazioni, non `Optional[X]`.
- I test invocano gli strumenti MCP tramite `.fn(...)`, non direttamente
il nome decorato.
- Evita i letterali `/tmp/...` nei test — usa la fixture pytest `tmp_path`
(regola di lint `S108`).
- Tutti gli import inline all'interno dei metodi di test necessitano di
`# noqa: PLC0415` se non puoi spostarli in cima al file (ad esempio
quando vengono importati condizionatamente dopo un `pytest.skip`).
- Una singola asserzione per riga per i test di verità composti (regola
di lint `PT018`).
Il tracker dei miglioramenti (`benchmark/improvements.md`) è il registro
persistente di ciò che è stato aggiunto alla pipeline attraverso le
versioni. Quando aggiungi una funzionalità sostanziale, aggiungi una
sezione lì che descriva cosa è cambiato, dove risiede e quali test la
proteggono.
---
## Glossario
- **AFL++** — Fuzzer greybox guidato dalla copertura; il motore di
esecuzione qui.
- **libAFLDriver** — Libreria statica che consente agli harness di AFL++
di usare la convenzione dell'entry-point di libFuzzer
(`LLVMFuzzerTestOneInput`).
- **LCOV** — Formato standard di file di traccia della copertura del
settore. Esportiamo verso di esso tramite
`llvm-cov export -format=lcov` e lo analizziamo noi stessi.
- **`stack_top_hash`** — Un hash di 16 caratteri dei primi N frame
normalizzati di uno stack trace ASan/UBSan. Usato per la
deduplicazione dei crash.
- **Corpus persistente** — Directory per-harness in
`<workspace>/corpus/harness_<id>/` che conserva gli input interessanti
di AFL attraverso le iterazioni e le riesecuzioni della stessa
campagna.
- **Smart mutator** — Un `LLVMFuzzerCustomMutator` i cui token di splice
sono estratti dal codice sorgente del target stesso
(`generate_smart_mutator`).
- **Custom mutator (libFuzzer)** — Una funzione C fornita dall'utente
chiamata dal motore con piena libertà su come mutare un buffer; AFL++
supporta la stessa ABI.
- **Strumento MCP** — Una funzione decorata con FastMCP che l'agente LLM
può chiamare.
- **OSS-Fuzz / Fuzz-Introspector** — L'infrastruttura di fuzzing
open-source di Google e il suo strumento complementare di analisi del
call-graph/copertura. Diverse funzionalità di questo taskflow
(mutatori per-formato, dedup-by-stack, report call-graph + API non
toccate, harness multi-candidato) sono ispirate ad essi.
---
## Licenza
Questo progetto è concesso in licenza secondo i termini della licenza
open source MIT. Fare riferimento al file [LICENSE](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/LICENSE.txt) per i
termini completi.
## Maintainer
Vedi [CODEOWNERS](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/CODEOWNERS) o contatta il team GitHub Security Lab.
## Supporto
Vedi [SUPPORT.md](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/SUPPORT.md) per dettagli su come ottenere aiuto con
questo progetto.
## Riconoscimenti
Questo progetto si basa sui concetti e sulle tecniche di
[AFL++](https://github.com/AFLplusplus/AFLplusplus),
[OSS-Fuzz](https://github.com/google/oss-fuzz) e
[Fuzz-Introspector](https://github.com/ossf/fuzz-introspector).