Un pipeline de fuzzing piloté par LLM propulsé par le GitHub Security Lab Taskflow Agent
Un pipeline de fuzzing de style OSS-Fuzz, piloté par LLM, pour les projets natifs C/C++. AFL++ pour l'exécution, clang+lcov pour la couverture, un agent LLM pour l'écriture de harness, les décisions basées sur les retours de couverture, le triage et le reporting.
Ce dépôt contient le taskflow de fuzzing pour le
GitHub Security Lab Taskflow Agent.
Il dépend du dépôt compagnon
seclab-taskflows
pour quelques blocs de construction partagés
(taskflow fetch_source_code, toolboxes local_file_viewer / gh_file_viewer,
et le model_config par défaut) — ceux-ci sont installés
automatiquement en tant que dépendance Python.
Les contributions sont les bienvenues ! Veuillez consulter CONTRIBUTING.md pour les directives.
aptgh)pip install git+https://github.com/GitHubSecurityLab/seclab-taskflows-fuzzing
Cela intègre `seclab-taskflow-agent` et `seclab-taskflows` (parent)
de manière transitive, de sorte que chaque référence pointée de la forme
`seclab_taskflows.taskflows.audit.*`,
`seclab_taskflows.toolboxes.local_file_viewer`,
`seclab_taskflows.toolboxes.gh_file_viewer`, et
`seclab_taskflows.configs.model_config` se résout à partir de la distribution
parente à l'exécution.
---
## Table des matières
1. [Ce que c'est](#what-this-is)
2. [Démarrage rapide](#quick-start)
3. [Architecture](#architecture)
4. [Le pipeline, étape par étape](#the-pipeline-stage-by-stage)
5. [La boucle de rétroaction de couverture](#the-coverage-feedback-loop)
6. [Fuzzing sensible à la structure](#structure-aware-fuzzing)
7. [Corpus persistant à travers les itérations et les campagnes](#persistent-corpus-across-iterations-and-campaigns)
8. [Triage et rapports de vulnérabilité](#triage-and-vulnerability-reports)
9. [Tableau de bord en direct](#live-dashboard)
10. [Fichiers de sortie](#output-files)
11. [Schéma de base de données](#database-schema)
12. [Outils MCP (le vocabulaire de l'agent)](#mcp-tools-the-agents-vocabulary)
13. [Paramètres ajustables (variables d'environnement)](#tunable-knobs-environment-variables)
14. [Étendre le pipeline](#extending-the-pipeline)
15. [Projets de référence et résultats](#benchmark-projects-and-results)
16. [Limitations et pièges](#limitations-and-gotchas)
17. [Avertissement de sécurité](#security-warning)
18. [Développement : tests, linting, contribution](#development-testing-linting-contributing)
19. [Glossaire](#glossary)
---
## Ce que c'est
Ce taskflow est un pipeline de fuzzing entièrement autonome. Étant donné un dépôt GitHub
d'un projet natif C/C++, il va :
1. installer AFL++ + clang/llvm/lcov + ctags/cscope/graphviz s'ils sont absents,
2. récupérer le code source,
3. identifier les cibles de fuzzing candidates (parseurs, décodeurs, validateurs, …),
4. analyser le système de build,
5. écrire un ou plusieurs candidats de harnais par cible, compiler chacun à la fois comme un
binaire `.afl` instrumenté par AFL et un binaire `.cov` instrumenté pour la couverture,
6. (optionnellement) qualifier les candidats par une couverture de 60 secondes et garder le meilleur,
7. exécuter une boucle fuzz/couverture/amélioration avec des budgets de temps doublés,
8. trier chaque crash, confirmer que les crashes précédemment connus se reproduisent toujours, et
écrire des rapports de vulnérabilité markdown par crash avec des verdicts, l'exploitabilité,
des correctifs suggérés, et des esquisses de tests de régression,
9. construire un graphe d'appels de style Fuzz-Introspector + un rapport d'API non touchée pour la
campagne suivante,
10. publier le tout sur un tableau de bord HTML en direct.
Le pipeline est **de style OSS-Fuzz** dans l'esprit : il utilise un grand nombre des mêmes
techniques (mutateurs et dictionnaires par format, épissage de tokens sensible à la structure,
améliorations de harnais guidées par la couverture, rapports lisibles par machine,
crashes dédupliqués par hachage de pile) mais il est beaucoup plus petit et autonome.
---
## Démarrage rapide```bash
# Inside the codespace (or a host with python + git available):
./scripts/fuzzing/run_fuzzing.sh tukaani-project/xz
C'est toute l'interface. Le script est autonome ; il installera AFL++
lors de la première exécution, puis pilotera le reste du flux de tâches. Les fichiers de sortie sont écrits
dans ~/.local/share/seclab-taskflow-agent/seclab-taskflows/.
Le tableau de bord démarre automatiquement en arrière-plan ; dans un Codespace, le port 8765 est
automatiquement redirigé — ouvrez-le dans n'importe quel navigateur pour suivre la progression en direct.
Pour un test rapide, utilisez une petite cible :```bash ./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON
## Architecture
Trois couches, de haut en bas :```
┌────────────────────────────────────────────────────────────────────┐
│ 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 │
└────────────────────────────────────────────────────────────────────┘
Règles de conception clés :
fuzz_context.db.run_afl_for, compile_harness, store_crash, etc.afl-clang-lto -fsanitize=address,undefined (le binaire .afl) et une fois
avec clang -fprofile-instr-generate -fcoverage-mapping (le binaire .cov). Le binaire .afl fuzze ; le binaire .cov rejoue la file d'attente AFL
pour produire une couverture réelle des lignes source/fonctions/branches.| # | Étape | Taskflow YAML |
|---|---|---|
| 1 | Installer AFL++ + outillage | scripts/fuzzing/install_afl.sh |
| 2 | Récupérer les sources | seclab_taskflows.taskflows.audit.fetch_source_code |
| 3 | Identifier les cibles de fuzzing | seclab_taskflows_fuzzing.taskflows.fuzzing.identify_fuzz_targets |
| 4 | Analyser le système de build | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_build_system |
| 5a | Écrire les harnesses initiaux (×N candidats si demandé) | seclab_taskflows_fuzzing.taskflows.fuzzing.write_initial_harnesses |
| 5b | Compiler les harnesses (AFL + couverture) | seclab_taskflows_fuzzing.taskflows.fuzzing.build_harnesses |
| 5c | Qualifier les candidats (quand HARNESS_CANDIDATES > 1) | seclab_taskflows_fuzzing.taskflows.fuzzing.qualify_harnesses |
| 6 | Boucle fuzzing/couverture/amélioration (×N itérations) | seclab_taskflows_fuzzing.taskflows.fuzzing.fuzz_iteration |
| 7 | Trier les crashes | seclab_taskflows_fuzzing.taskflows.fuzzing.triage_crashes |
| 8 | Confirmer que les crashes précédemment connus se reproduisent toujours | seclab_taskflows_fuzzing.taskflows.fuzzing.confirm_fixed_crashes |
| 9 | Construire le graphe d'appels + rapport des API non touchées | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_call_graph |
| 10 | Écrire les rapports de vulnérabilité par crash | seclab_taskflows_fuzzing.taskflows.fuzzing.write_vuln_reports |
| 11 | Écrire le rapport de campagne | seclab_taskflows_fuzzing.taskflows.fuzzing.write_report |
Chaque étape est un taskflow YAML autonome que l'agent exécute
de bout en bout. Les étapes communiquent exclusivement via la base de données SQLite
dans fuzz_context.db — il n'y a aucun transfert en mémoire.
C'est le cœur du pipeline. Les budgets de temps doublent à chaque itération :``` 30s → 60s → 120s → 240s → 480s → 960s (≈ 32 min/target)
À chaque itération, pour chaque harness, l'agent :
1. Demande `get_persistent_corpus_dir(harness_id)` pour obtenir le répertoire
de corpus stable de ce harness.
2. Appelle `run_afl_for(afl_binary_path, seed_dir=<persistent corpus>,
output_dir=<run dir>, seconds=<budget>, dictionary=<auto.dict>)`.
3. Appelle `run_coverage(cov_binary_path, inputs_dir=<run>/default/queue,
output_dir=<run>/coverage)` pour produire un fichier de trace LCOV et un rapport HTML.
4. Appelle `store_coverage_from_lcov(run_id, lcov_path, html_path)` pour persister
une ligne `coverage_report` + des lignes `coverage_gap` par élément non couvert.
5. Appelle `fold_queue_into_persistent_corpus(...)` pour fusionner la file d'attente
d'itération d'AFL dans le corpus persistant et exécuter `cmin` pour maintenir la taille bornée.
6. Lit `get_coverage_summary` + `get_coverage_gaps`, puis soit :
- ajoute une nouvelle graine (étiquetée `coverage_feedback`) pour atteindre une
branche non couverte,
- modifie la source du harness pour appeler une API supplémentaire,
- appelle `enrich_dictionary_from_uncovered(...)` pour ajouter automatiquement des entrées
de dictionnaire pour les constantes magiques dont AFL a besoin pour satisfaire une garde, ou
- ignore la lacune (chemin d'erreur froid / code fournisseur).
7. Appelle `store_iteration_note(repo, iteration_number, harness_id, note=<résumé
en une ligne>)` afin que la chronologie des itérations du tableau de bord suive ce qui
a changé.
**Détection de plateau.** La boucle se termine prématurément dès que deux itérations consécutives
ont toutes deux gagné < `FUZZ_PLATEAU_THRESHOLD_PCT` (par défaut `1.0`) points de pourcentage
absolus de couverture de lignes.
---
## Fuzzing conscient de la structure
Trois mécanismes complémentaires produisent des entrées plus fortes que la mutation brute d'octets.
### 1. Dictionnaires par format + mutateurs personnalisés
Pour les cibles dont `input_kind` correspond à un format connu, le taskflow fournit
des dictionnaires préconstruits et des fichiers source C `LLVMFuzzerCustomMutator` :
| Format | Dictionnaire | Mutateur | Notes |
|--------|------------|---------|-------|
| `json` | `json.dict` | `json_mutator.c` | Épissage de tokens, duplication/suppression de crochets équilibrés, inversion de type |
| `xml` | `xml.dict` | `xml_mutator.c` | Balises, entités, DTD, tokens billion-laughs |
| `regex` | `regex.dict` | `regex_mutator.c` | Ancres, classes, quantificateurs, motifs ReDoS réels |
| `binary_tlv` | _(aucun)_ | `binary_tlv_mutator.c` | Enregistrements préfixés par longueur : dépassement de longueur / duplication / suppression |
| `png` | `png.dict` | _(réutilise binary_tlv)_ | Dictionnaire PNG + mutateur binary_tlv |
Ceux-ci sont récupérés automatiquement par `write_initial_harnesses` (dictionnaire
copié à côté des graines) et `build_harnesses` (mutateur lié dans le binaire
AFL). Chaque mutateur délègue 50 % des mutations au mutateur d'octets par défaut d'AFL
afin de ne pas perdre la randomisation du moteur.
Pour ajouter un nouveau format : déposez un `<name>.dict` et/ou un `<name>_mutator.c` dans
`src/seclab_taskflows/dictionaries/`, puis enregistrez-le dans la
map `_FORMAT_ASSETS` en bas de `fuzz_runner.py`.
### 2. Mutateur intelligent conscient de la source (spécifique au projet)
Pour les formats inconnus, ou chaque fois que vous voulez des tokens plus forts
spécifiques au projet, `generate_smart_mutator` analyse les propres fichiers `.c`/`.h`
du dépôt cible et émet un fichier C `LLVMFuzzerCustomMutator` dont les dictionnaires
d'épissage sont extraits de :
- littéraux de chaîne avec ≥3 caractères alphabétiques (après filtrage du bruit
du compilateur/licence, des chemins, des en-têtes, des contraintes asm, des spécificateurs de format),
- constantes numériques 32 bits issues de `#define`, `case` et `enum` (après
filtrage du bruit générique de petits entiers comme 0, 1, 256, 0xff…).
Trois focus sont disponibles :
| Focus | Ce qu'il épisse | Quand l'utiliser |
|-------|-----------------|-------------|
| `strings` | Littéraux de chaîne du projet uniquement | Formats texte (JSON, XML, YAML, CSV) |
| `constants` | Valeurs magiques numériques 32 bits uniquement | Protocoles binaires, en-têtes avec nombres magiques |
| `combined` | Les deux | Par défaut ; généralement le meilleur |
Associez `generate_smart_mutators(...)` (au pluriel) avec `HARNESS_CANDIDATES >= 3`
afin que chaque focus devienne un harness candidat lors du tour de qualification.
### 3. Dictionnaire AFL conscient du projet + enrichissement piloté par la couverture
Deux outils complémentaires construisent et développent un dictionnaire AFL `-x` au
fur et à mesure de la campagne :
- **`generate_project_dictionary(source_root, output_path)`** — s'exécute une fois
avant l'itération 1, extrait statiquement le même ensemble de tokens source que
celui utilisé par le mutateur intelligent et l'écrit sous forme de dictionnaire AFL. Les constantes
numériques sont émises dans LES DEUX endianness afin que le fuzzer puisse satisfaire
`memcmp(x, &magic, 4)` quel que soit l'ordre des octets de l'hôte.
- **`enrich_dictionary_from_uncovered(source_root, dictionary_path,
uncovered_locations)`** — s'exécute après l'étape de couverture de chaque itération,
analyse la source environnante pour les gardes conditionnelles
(`strncmp/memcmp/strstr`, `case 0xN:`, `== 0xN`, `== 'X'`) à proximité des
lignes non couvertes, et AJOUTE tous les nouveaux tokens au dictionnaire. Idempotent :
ne rajoute jamais une entrée déjà présente.
### 4. Opération d'épissage de corpus
Lorsque `corpus_dir` est passé à `generate_smart_mutator`, le C généré
reçoit également un opérateur d'épissage de corpus : au premier appel, il charge jusqu'à 64 fichiers
depuis ce répertoire (plafonnés à 4 Kio chacun), et à partir de là peut épisser
des sous-régions aléatoires de ces fichiers dans l'entrée mutée. Cela donne au
mutateur un opérateur de style recombinaison que le havoc standard d'AFL ne fait pas
bien. Associez-le à `get_persistent_corpus_dir(...)` afin que la bibliothèque d'épissage
soit « remixez ce qu'AFL a déjà découvert ».
---
## Corpus persistant à travers les itérations et les campagnes
Chaque harness dispose d'un répertoire de corpus stable à l'emplacement :```
<workspace>/corpus/harness_<id>/
Voici ce que fuzz_iteration utilise comme seed_dir pour run_afl_for (plutôt
que <harness>/seeds). À la fin de chaque itération,
fold_queue_into_persistent_corpus(...) fusionne la file d'attente d'itération d'AFL dans
ce répertoire et exécute afl-cmin pour la maintenir bornée.
Le résultat : la file d'attente d'hier est reportée dans l'exécution d'aujourd'hui ET à travers les ré-exécutions du même projet. Un arrêt-redémarrage de la campagne ne perd aucun progrès.
Une fois la boucle fuzz/couverture/amélioration terminée, trois étapes s'exécutent automatiquement :
triage_crashesPour chaque fichier de crash dans <run>/default/crashes/ :
afl-tmin pour minimiser l'entrée,replay_under_asan pour capturer une trace de pile et stack_top_hash
(top-N frames normalisées ; les templates, les espaces de noms inline de libcxx, les espaces de noms anonymes
et les suffixes numériques LTO sont supprimés afin que des crashes sémantiquement
identiques produisent le même hash),crash avec classification de classe de bug +
note de confiance (élevée / moyenne / faible).confirm_fixed_crashesRejoue chaque crash précédemment classifié (dont le verdict n'est pas déjà
fixed/duplicate/non_reproducible) à travers le binaire AFL+ASan actuel.
S'il ne plante plus, marque verdict="fixed". Utile lors de la ré-exécution d'une
campagne contre un projet ayant reçu des correctifs en amont depuis la
dernière campagne.
write_vuln_reportsPour chaque crash unique, l'agent lit la source du harness + la source de la fonction qui plante, parcourt la chaîne d'appels depuis l'API publique, puis attribue l'un des dix verdicts de style OSS-Fuzz et écrit un rapport de vulnérabilité markdown :
| Verdict | Signification |
|---|---|
vulnerability | Réel, exploitable via une API publique |
library_hardening | Bug réel mais aucun chemin réaliste via l'API publique ; la bibliothèque devrait tout de même se défendre |
harness_bug | Le bug est dans notre harness, pas dans la bibliothèque |
non_reproducible | Le replay ne reproduit pas le crash sur l'entrée minimisée |
oom | Out-of-memory ; vulnérabilité uniquement si la taille contrôlable par l'attaquant est non bornée |
timeout | DoS via explosion algorithmique |
assertion_failure | assert() déclenché ; pertinence de sécurité variable |
fixed | Défini par confirm_fixed_crashes : l'entrée ne reproduit plus le crash |
duplicate | Même cause racine qu'un autre crash avec un hash de pile différent |
needs_investigation | Impossible à déterminer ; signalé pour revue humaine |
Chaque rapport de vulnérabilité inclut :
Le tableau de bord est démarré automatiquement en arrière-plan par
run_fuzzing.sh. Désactivez-le avec FUZZ_NO_DASHBOARD=1 ; remplacez le port
avec FUZZ_DASHBOARD_PORT (par défaut 8765).
Dans un Codespace, le port 8765 est automatiquement redirigé — ouvrez l'URL redirigée dans
n'importe quel navigateur. La page s'actualise automatiquement toutes les 5 s et affiche :
fuzz_run en coursvulnerability en premier), avec liens
vers chaque rapport de vulnérabilité et entrée minimiséeLe tableau de bord expose également une petite API JSON en lecture seule pour les scripts :```bash
curl 'http://127.0.0.1:8765/api/json?repo=kkos/oniguruma' | jq .
---
## Fichiers de sortie
Tous sous `~/.local/share/seclab-taskflow-agent/seclab-taskflows/`.
| Chemin | Contenu |
|------|----------|
| `fuzz_context/fuzz_context.db` | SQLite — cibles, harnesses, exécutions, couverture, crashes, verdicts, graphes d'appels, suggestions de harness, notes d'itération |
| `fuzz_runner/builds/` | Binaires `.afl` et `.cov` compilés |
| `fuzz_runner/runs/` | Répertoires de sortie AFL + fichiers LCOV + rapports de couverture HTML |
| `fuzz_runner/corpus/harness_<id>/` | Corpus persistant par harness (conservé entre les itérations et les campagnes) |
| `fuzz_runner/repo/<owner>__<repo>/REPORT.md` | Résumé de campagne Markdown, crashes groupés par verdict |
| `fuzz_runner/repo/<owner>__<repo>/vuln_<crash_id>.md` | Rapport de vulnérabilité Markdown par crash |
| `fuzz_runner/repo/<owner>__<repo>/call_graph.{dot,svg,md}` | Graphe d'appels statique + superposition atteint/non atteint |
---
## Schéma de la base de données
Tables dans `fuzz_context.db` (SQLite via SQLAlchemy) :
| Table | Colonnes d'intérêt |
|-------|--------------------|
| `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` |
Les migrations de schéma se trouvent dans `_migrate()` dans `fuzz_context.py`. Les nouvelles TABLES sont
créées automatiquement par `Base.metadata.create_all()` ; seules les nouvelles COLONNES nécessitent
un `ALTER TABLE` basé sur PRAGMA.
---
## Outils MCP (le vocabulaire de l'agent)
L'agent n'appelle jamais AFL ou clang directement — il compose le pipeline en
appelant des outils MCP. L'ensemble complet, groupé par objectif :
### Persistance (`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 / couverture (`fuzz_runner.py`)
- `check_tooling`, `workspace_paths`
- `compile_harness` — compile les binaires `.afl` et `.cov`
- `run_afl_for`, `cmin`, `tmin`, `replay_under_asan`, `reproduce_crash`
- `run_coverage` — rejoue la file d'attente AFL contre le binaire `.cov`, exporte LCOV
- `extract_dictionary` — extrait les chaînes imprimables d'un binaire
- `package_reproducer` — regroupe un `.tgz` pour un crash unique
### Corpus persistant (v8)
- `get_persistent_corpus_dir`, `fold_queue_into_persistent_corpus`
### Ressources de format (C5)
- `list_format_assets`, `get_format_dictionary`, `write_format_mutator`
### Mutateur intelligent + dictionnaire conscient du projet
- `generate_smart_mutator`, `generate_smart_mutators`
- `generate_project_dictionary`, `enrich_dictionary_from_uncovered`
Les fonctions d'outil sont décorées avec `@mcp.tool()` (FastMCP). Dans les tests,
invoquez-les via l'attribut `.fn`, par ex.
`fr.run_afl_for.fn(afl_binary_path=..., ...)`.
---
## Paramètres ajustables (variables d'environnement)
| Variable | Défaut | Objectif |
|----------|---------|---------|
| `HARNESS_CANDIDATES` | `1` | Nombre de harnesses candidats écrits par cible. Définir à 2 ou 3 pour une compétition de style OSS-Fuzz-Gen. L'étape de qualification exécute chacun pendant `QUALIFIER_SECONDS` et conserve le meilleur selon le % de lignes. |
| `QUALIFIER_SECONDS` | `60` | Budget en temps réel par candidat dans l'étape de qualification. |
| `FUZZ_PLATEAU_THRESHOLD_PCT` | `1.0` | Gain de couverture de lignes (en pp absolus) en dessous duquel deux itérations consécutives sont considérées comme un plateau et la boucle s'arrête prématurément. |
| `FUZZ_DASHBOARD_PORT` | `8765` | Port pour le tableau de bord en direct. |
| `FUZZ_NO_DASHBOARD` | (non défini) | Définir à `1` pour ignorer le démarrage du tableau de bord. |
| `FUZZ_RUNNER_TIMEOUT` | `1200` | Délai d'expiration du sous-processus par outil dans `fuzz_runner` (secondes). |
| `LOCAL_SHELL_TIMEOUT` | `180` | Délai d'expiration par commande dans `local_shell` (secondes). |
Plus les variables d'agent standard (`COPILOT_TOKEN`, `LOG_DIR`,
`FUZZ_CONTEXT_DIR`, …). Voir le README à la racine du projet pour la liste complète.
---
## Étendre le pipeline
### Ajouter un nouveau format (mutateur + dictionnaire)
1. Déposez `dictionaries/<name>.dict` (format AFL `-x`) et/ou
`dictionaries/<name>_mutator.c` (mutateur personnalisé libFuzzer).
2. Enregistrez dans `_FORMAT_ASSETS` en bas de `fuzz_runner.py` : ```python
"<name>": {
"dictionary": "<name>.dict",
"mutator": "<name>_mutator.c",
"description": "Short one-liner about the format",
},
list_format_assets().@mcp.tool() dans fuzz_context.py (pour
la persistance) ou fuzz_runner.py (pour le travail en sous-processus).Annotated[type, Field(description=...)] pour chaque argument — la
description est ce que le LLM voit.tests/test_fuzz_context.py /
tests/test_fuzz_runner.py. Invoquez l'outil via son attribut .fn
(convention FastMCP).user_prompt du YAML de taskflow
concerné.src/seclab_taskflows/taskflows/fuzzing/. Utilisez
l'un des fichiers existants (par ex. triage_crashes.yaml) comme modèle.scripts/fuzzing/run_fuzzing.sh entre les deux bonnes
étapes existantes.scripts/fuzzing/dashboard.py.Lors de l'ajout d'une nouvelle table SQL :
fuzz_context_models.py.Base.metadata.create_all() est appelé à
l'initialisation du moteur et crée automatiquement les nouvelles tables.Lors de l'ajout d'une nouvelle COLONNE à une table existante :
PRAGMA table_info + ALTER TABLE ADD COLUMN dans
_migrate() dans fuzz_context.py afin que les anciennes bases de données
soient mises à niveau de manière transparente._migrate_if_writable() dans scripts/fuzzing/dashboard.py.benchmark/projects.yaml liste les projets de référence. Ils sont choisis pour
que le pipeline complet v4+ puisse s'exécuter de bout en bout sur une image de
développement codespace sans intervention humaine.
| # | Dépôt | Pourquoi il est intéressant | Notes |
|---|---|---|---|
| 1 | tukaani-project/xz | Bibliothèque réelle à forte composante d'analyse syntaxique (liblzma) ; chaîne de filtres riche + surface d'analyse d'entiers/VLI | Référence |
| 2 | DaveGamble/cJSON | Petit analyseur JSON C en un seul fichier ; CMake trivial | Test rapide pour le pipeline |
| 3 | akheron/jansson | Bibliothèque JSON C compacte avec un point d'entrée documenté json_loadb() sur tampon d'octets | CMake ; exec/sec très rapide |
| 4 | libexpat/libexpat | Analyseur XML en flux mature ; nombreux CVE historiques | CMake ou autotools |
| 5 | kkos/oniguruma | Moteur de regex ; prend un motif et un sujet contrôlés par l'attaquant | Autotools ; la compilation du motif est le chemin critique |
Chiffres de référence d'une exécution complète du pipeline v4 sur l'image de développement codespace (≈32 min/cible) :
| Dépôt | Cibles | Harnais | Exécutions AFL | Crashs | Verdicts |
|---|---|---|---|---|---|
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 lecture hors limites dans regerror.c), library_hardening, harness_bug, non_reproducible |
Les résultats sans crash de xz / cJSON / libexpat sont attendus : ces projets
sont massivement fuzzés en amont. Les deux constats classés vulnerability
dans oniguruma sont de véritables lectures hors limites dans le chemin de code
de formatage des avertissements de onig_snprintf_with_pattern (lecture d'un
octet au-delà de pat_end lorsque le motif se termine par une barre oblique
inverse) ; les rapports markdown par crash incluent des correctifs suggérés.
Pour ajouter un nouveau projet de référence, ajoutez une entrée dans
benchmark/projects.yaml et (éventuellement) documentez la raison dans
benchmark/README.md. Tout ce que l'étape existante analyze_build_system
peut compiler avec clang + les drapeaux AFL++ est un candidat raisonnable.
Les analyseurs, décodeurs et sérialiseurs en C pur ont tendance à mieux
fonctionner.
BUILD_FAILED: et les ignore.kernel.core_pattern=core et un ajustement du governor CPU. Dans un
Codespace, ceux-ci sont indisponibles, donc le taskflow exporte
AFL_SKIP_CPUFREQ=1 et AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 par
défaut. AFL affiche des avertissements mais trouve quand même des crashs
via la gestion des abandons de type libFuzzer.<dirent.h>. Correct pour Linux/macOS ; ne se
compilerait pas sous Windows.compile_harness utilisent libAFLDriver en mode argv. replay_under_asan
et tmin utilisent donc par défaut stdin_input=False car libAFLDriver
boucle indéfiniment lorsqu'il est piloté via stdin.generate_smart_mutator + generate_smart_mutators utilisent le
.format() de Python — chaque { / } littéral dans le template C doit
être doublé ({{ / }}). Si vous modifiez le template et commencez à voir
des KeyError, c'est la raison.Ce taskflow exécute afl-fuzz, clang, llvm-cov, et des commandes de
build arbitraires choisies par le LLM, directement sur l'hôte (sans
conteneur). Un agent soumis à une injection de prompt pourrait en principe
faire tout ce que votre utilisateur peut faire. Exécutez-le uniquement :
git, apt et le système de build
ont besoin.La boîte à outils local_shell n'est PAS derrière une invite de
confirmation — le taskflow est autonome et s'exécute sans humain dans la
boucle, donc une confirmation interactive bloquerait indéfiniment. Chaque
commande shell est journalisée dans $LOG_DIR/mcp_local_shell.log pour un
examen a posteriori.
hatch test
hatch fmt --linter --check
hatch fmt --linter
hatch fmt --linter --check -- src/seclab_taskflows/mcp_servers/fuzz_runner.py
Conventions de base de code (voir aussi `benchmark/improvements.md` pour la version historique de campagne de celles-ci) :
- Utilisez `os.environ.get(NAME) or "default"` plutôt que
`os.environ.get(NAME, "default")`. Les chaînes vides issues de la substitution
de template YAML seraient sinon retournées.
- Utilisez `X | None` (PEP 604) dans les nouvelles annotations, pas `Optional[X]`.
- Les tests invoquent les outils MCP via `.fn(...)`, pas le nom décoré directement.
- Évitez les littéraux `/tmp/...` dans les tests — utilisez la fixture pytest `tmp_path`
(règle de lint `S108`).
- Tous les imports en ligne à l'intérieur des méthodes de test nécessitent `# noqa: PLC0415` si vous
ne pouvez pas les déplacer en haut du fichier (par exemple lorsqu'ils sont importés conditionnellement
après un `pytest.skip`).
- Une seule assertion par ligne pour les tests de vérité composés (règle de lint `PT018`).
Le suivi des améliorations (`benchmark/improvements.md`) est le journal persistant
de ce qui a été ajouté au pipeline au fil des versions. Lorsque vous ajoutez une
fonctionnalité substantielle, ajoutez-y une section décrivant ce qui a changé, où cela
se trouve, et quels tests le protègent.
---
## Glossaire
- **AFL++** — Fuzzer greybox guidé par la couverture ; le moteur d'exécution ici.
- **libAFLDriver** — Bibliothèque statique qui permet aux harnais AFL++ d'utiliser
la convention de point d'entrée libFuzzer (`LLVMFuzzerTestOneInput`).
- **LCOV** — Format de fichier de trace de couverture standard de l'industrie. Nous exportons vers celui-ci
via `llvm-cov export -format=lcov` et le parsons nous-mêmes.
- **`stack_top_hash`** — Un hash de 16 caractères des N premières trames normalisées d'une
trace de pile ASan/UBSan. Utilisé pour la déduplication des crashs.
- **Corpus persistant** — Répertoire par harnais à
`<workspace>/corpus/harness_<id>/` qui conserve les entrées intéressantes d'AFL
à travers les itérations et les ré-exécutions d'une même campagne.
- **Mutateur intelligent** — Un `LLVMFuzzerCustomMutator` dont les jetons de splice sont
extraits du propre code source de la cible (`generate_smart_mutator`).
- **Mutateur personnalisé (libFuzzer)** — Une fonction C fournie par l'utilisateur appelée par
le moteur avec une liberté totale sur la façon de muter un buffer ; AFL++ prend en charge
la même ABI.
- **Outil MCP** — Une fonction décorée par FastMCP que l'agent LLM peut appeler.
- **OSS-Fuzz / Fuzz-Introspector** — L'infrastructure de fuzzing open-source de Google
et son outil compagnon d'analyse de graphe d'appels/couverture.
Plusieurs fonctionnalités de ce taskflow (mutateurs par format, déduplication par pile,
rapport de graphe d'appels + API non touchées, harnais multi-candidats) sont
inspirées par ceux-ci.
---
## Licence
Ce projet est sous licence selon les termes de la licence open source MIT. Veuillez vous référer au fichier [LICENSE](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/LICENSE.txt) pour les termes complets.
## Mainteneurs
Voir [CODEOWNERS](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/CODEOWNERS) ou contactez l'équipe GitHub Security Lab.
## Support
Voir [SUPPORT.md](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/SUPPORT.md) pour plus de détails sur la façon d'obtenir de l'aide avec ce projet.
## Remerciements
Ce projet s'appuie sur les concepts et techniques d'[AFL++](https://github.com/AFLplusplus/AFLplusplus), [OSS-Fuzz](https://github.com/google/oss-fuzz), et [Fuzz-Introspector](https://github.com/ossf/fuzz-introspector).