Una pipeline de fuzzing impulsada por LLM y potenciada por el GitHub Security Lab Taskflow Agent
Una pipeline de fuzzing estilo OSS-Fuzz impulsada por LLM para proyectos nativos en C/C++. AFL++ para la ejecución, clang+lcov para la cobertura, un agente LLM para la escritura de harnesses, decisiones basadas en retroalimentación de cobertura, triaje y reportes.
Este repositorio contiene el taskflow de fuzzing para el
GitHub Security Lab Taskflow Agent.
Depende del repositorio complementario
seclab-taskflows
para algunos bloques de construcción compartidos
(taskflow fetch_source_code, toolboxes local_file_viewer / gh_file_viewer,
y el model_config predeterminado) — estos se instalan
automáticamente como una dependencia de Python.
¡Las contribuciones son bienvenidas! Consulta CONTRIBUTING.md para obtener pautas.
aptgh)pip install git+https://github.com/GitHubSecurityLab/seclab-taskflows-fuzzing
Esto incorpora `seclab-taskflow-agent` y `seclab-taskflows` (padre)
de forma transitiva, por lo que cada referencia con puntos de la forma
`seclab_taskflows.taskflows.audit.*`,
`seclab_taskflows.toolboxes.local_file_viewer`,
`seclab_taskflows.toolboxes.gh_file_viewer`, y
`seclab_taskflows.configs.model_config` se resuelve desde la distribución
padre en tiempo de ejecución.
---
## Tabla de contenidos
1. [Qué es esto](#what-this-is)
2. [Inicio rápido](#quick-start)
3. [Arquitectura](#architecture)
4. [El pipeline, etapa por etapa](#the-pipeline-stage-by-stage)
5. [El bucle de retroalimentación de cobertura](#the-coverage-feedback-loop)
6. [Fuzzing consciente de la estructura](#structure-aware-fuzzing)
7. [Corpus persistente entre iteraciones y campañas](#persistent-corpus-across-iterations-and-campaigns)
8. [Triaje e informes de vulnerabilidades](#triage-and-vulnerability-reports)
9. [Panel en vivo](#live-dashboard)
10. [Archivos de salida](#output-files)
11. [Esquema de la base de datos](#database-schema)
12. [Herramientas MCP (el vocabulario del agente)](#mcp-tools-the-agents-vocabulary)
13. [Parámetros ajustables (variables de entorno)](#tunable-knobs-environment-variables)
14. [Extender el pipeline](#extending-the-pipeline)
15. [Proyectos de referencia y resultados](#benchmark-projects-and-results)
16. [Limitaciones y trampas](#limitations-and-gotchas)
17. [Advertencia de seguridad](#security-warning)
18. [Desarrollo: pruebas, linting, contribución](#development-testing-linting-contributing)
19. [Glosario](#glossary)
---
## Qué es esto
Este taskflow es un pipeline de fuzzing totalmente autónomo. Dado un repositorio de GitHub
de un proyecto nativo en C/C++, hará lo siguiente:
1. instalar AFL++ + clang/llvm/lcov + ctags/cscope/graphviz si faltan,
2. obtener el código fuente,
3. identificar objetivos de fuzzing candidatos (parsers, decodificadores, validadores, …),
4. analizar el sistema de compilación,
5. escribir uno o más candidatos de harness por objetivo, compilar cada uno tanto como un
binario `.afl` instrumentado con AFL como un binario `.cov` instrumentado para cobertura,
6. (opcionalmente) calificar candidatos mediante cobertura de 60 segundos y conservar el mejor,
7. ejecutar un bucle de fuzzing/cobertura/mejora con presupuestos de tiempo que se duplican,
8. triar cada fallo, confirmar que los fallos previamente conocidos aún se reproducen, y
escribir informes de vulnerabilidad en markdown por cada fallo con veredictos, explotabilidad,
parches sugeridos y bocetos de pruebas de regresión,
9. construir un grafo de llamadas al estilo Fuzz-Introspector + informe de API no tocada para la
siguiente campaña,
10. publicar todo en un panel HTML en vivo.
El pipeline es **al estilo OSS-Fuzz** en esencia: utiliza muchas de las mismas
técnicas (mutadores y diccionarios por formato, empalme de tokens consciente de la estructura, mejoras de harness guiadas por cobertura, informes legibles por máquina,
fallos deduplicados con hash de pila) pero es mucho más pequeño y autocontenido.
---
## Inicio rápido```bash
# Inside the codespace (or a host with python + git available):
./scripts/fuzzing/run_fuzzing.sh tukaani-project/xz
Esa es toda la interfaz. El script es autónomo; instalará AFL++
en la primera ejecución y luego dirigirá el resto del flujo de tareas. Los archivos de salida se escriben
en ~/.local/share/seclab-taskflow-agent/seclab-taskflows/.
El panel de control se inicia automáticamente en segundo plano; en un Codespace, el puerto 8765 se
redirige automáticamente — ábrelo en cualquier navegador para ver el progreso en vivo.
Para una prueba rápida, usa un objetivo pequeño:```bash ./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON
---
## Arquitectura
Tres capas, de arriba a abajo:```
┌────────────────────────────────────────────────────────────────────┐
│ 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 │
└────────────────────────────────────────────────────────────────────┘
Reglas de diseño clave:
fuzz_context.db.run_afl_for, compile_harness, store_crash, etc.afl-clang-lto -fsanitize=address,undefined (el binario .afl) y otra
con clang -fprofile-instr-generate -fcoverage-mapping (el binario .cov).
El binario .afl hace fuzzing; el binario .cov reproduce la cola de AFL
para producir cobertura real de líneas de código/funciones/ramas.| # | Etapa | Taskflow YAML |
|---|---|---|
| 1 | Instalar AFL++ + herramientas | scripts/fuzzing/install_afl.sh |
| 2 | Obtener código fuente | seclab_taskflows.taskflows.audit.fetch_source_code |
| 3 | Identificar objetivos de fuzzing | seclab_taskflows_fuzzing.taskflows.fuzzing.identify_fuzz_targets |
| 4 | Analizar el sistema de compilación | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_build_system |
| 5a | Escribir harnesses iniciales (×N candidatos si se solicita) | seclab_taskflows_fuzzing.taskflows.fuzzing.write_initial_harnesses |
| 5b | Compilar harnesses (AFL + cobertura) | seclab_taskflows_fuzzing.taskflows.fuzzing.build_harnesses |
| 5c | Calificar candidatos (cuando HARNESS_CANDIDATES > 1) | seclab_taskflows_fuzzing.taskflows.fuzzing.qualify_harnesses |
| 6 | Bucle de fuzzing/cobertura/mejora (×N iteraciones) | seclab_taskflows_fuzzing.taskflows.fuzzing.fuzz_iteration |
| 7 | Triaje de crashes | seclab_taskflows_fuzzing.taskflows.fuzzing.triage_crashes |
| 8 | Confirmar que crashes previamente conocidos aún se reproducen | seclab_taskflows_fuzzing.taskflows.fuzzing.confirm_fixed_crashes |
| 9 | Construir grafo de llamadas + informe de API no tocada | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_call_graph |
| 10 | Escribir informes de vulnerabilidades por crash | seclab_taskflows_fuzzing.taskflows.fuzzing.write_vuln_reports |
| 11 | Escribir informe de campaña | seclab_taskflows_fuzzing.taskflows.fuzzing.write_report |
Cada etapa es un taskflow YAML autocontenido que el agente ejecuta
de principio a fin. Las etapas se comunican exclusivamente a través de la base de datos SQLite
en fuzz_context.db — no hay transferencia en memoria.
Este es el corazón del pipeline. Los presupuestos de tiempo se duplican en cada iteración:``` 30s → 60s → 120s → 240s → 480s → 960s (≈ 32 min/target)
En cada iteración, para cada harness, el agente:
1. Solicita `get_persistent_corpus_dir(harness_id)` para obtener el
directorio de corpus estable de este harness.
2. Llama a `run_afl_for(afl_binary_path, seed_dir=<persistent corpus>,
output_dir=<run dir>, seconds=<budget>, dictionary=<auto.dict>)`.
3. Llama a `run_coverage(cov_binary_path, inputs_dir=<run>/default/queue,
output_dir=<run>/coverage)` para producir un tracefile LCOV y un informe HTML.
4. Llama a `store_coverage_from_lcov(run_id, lcov_path, html_path)` para persistir
una fila `coverage_report` + filas `coverage_gap` por cada elemento no cubierto.
5. Llama a `fold_queue_into_persistent_corpus(...)` para fusionar la cola de iteración
de AFL en el corpus persistente y ejecutar `cmin` para mantener el tamaño acotado.
6. Lee `get_coverage_summary` + `get_coverage_gaps`, y luego:
- añade una nueva semilla (etiquetada `coverage_feedback`) para alcanzar una
rama no cubierta,
- edita el código fuente del harness para llamar a una API adicional,
- llama a `enrich_dictionary_from_uncovered(...)` para añadir automáticamente
entradas al diccionario para las constantes mágicas que AFL necesita para
satisfacer un guard, o
- omite la brecha (ruta de error en frío / código de proveedor).
7. Llama a `store_iteration_note(repo, iteration_number, harness_id, note=<resumen
de una línea>)` para que la línea temporal de iteraciones del panel de control
registre lo que cambió.
**Detección de estancamiento.** El bucle termina anticipadamente una vez que dos iteraciones consecutivas
han ganado ambas < `FUZZ_PLATEAU_THRESHOLD_PCT` (por defecto `1.0`) puntos porcentuales absolutos
de cobertura de líneas.
---
## Fuzzing consciente de la estructura
Tres mecanismos complementarios producen entradas más sólidas que la mutación de bytes en bruto.
### 1. Diccionarios por formato + mutadores personalizados
Para objetivos cuyo `input_kind` coincide con un formato conocido, el taskflow incluye
diccionarios preconstruidos y archivos fuente C de `LLVMFuzzerCustomMutator`:
| Formato | Diccionario | Mutador | Notas |
|--------|------------|---------|-------|
| `json` | `json.dict` | `json_mutator.c` | Empalme de tokens, dup/drop de corchetes balanceados, cambio de tipo |
| `xml` | `xml.dict` | `xml_mutator.c` | Etiquetas, entidades, DTDs, tokens billion-laughs |
| `regex` | `regex.dict` | `regex_mutator.c` | Anclas, clases, cuantificadores, patrones ReDoS reales |
| `binary_tlv` | _(ninguno)_ | `binary_tlv_mutator.c` | Registros con prefijo de longitud: desbordamiento de longitud / dup / drop |
| `png` | `png.dict` | _(reutiliza binary_tlv)_ | Diccionario PNG + mutador binary_tlv |
Estos son recogidos automáticamente por `write_initial_harnesses` (diccionario
copiado junto a las semillas) y `build_harnesses` (mutador enlazado en el binario
de AFL). Cada mutador delega el 50% de las mutaciones al mutador de bytes por defecto
de AFL para no perder la aleatorización del motor.
Para añadir un nuevo formato: coloca un `<name>.dict` y/o un `<name>_mutator.c` en
`src/seclab_taskflows/dictionaries/`, luego regístralo en el
mapa `_FORMAT_ASSETS` al final de `fuzz_runner.py`.
### 2. Mutador inteligente consciente del código fuente (específico del proyecto)
Para formatos desconocidos, o cuando quieras tokens más sólidos específicos del proyecto,
`generate_smart_mutator` escanea los propios archivos `.c`/`.h` del repositorio objetivo
y emite un archivo C `LLVMFuzzerCustomMutator` cuyos diccionarios de empalme
se extraen de:
- literales de cadena con ≥3 caracteres alfabéticos (tras filtrar ruido de
compilador/licencia, rutas, cabeceras, restricciones asm, especificadores de formato),
- constantes numéricas de 32 bits de `#define`, `case` y `enum` (tras
filtrar ruido genérico de enteros pequeños como 0, 1, 256, 0xff…).
Hay tres enfoques disponibles:
| Enfoque | Qué empalma | Cuándo usarlo |
|-------|-----------------|-------------|
| `strings` | Solo literales de cadena del proyecto | Formatos de texto (JSON, XML, YAML, CSV) |
| `constants` | Solo valores mágicos numéricos de 32 bits | Protocolos binarios, cabeceras con números mágicos |
| `combined` | Ambos | Por defecto; normalmente el mejor |
Combina `generate_smart_mutators(...)` (plural) con `HARNESS_CANDIDATES >= 3`
para que cada enfoque se convierta en un harness candidato en la ronda de clasificación.
### 3. Diccionario AFL consciente del proyecto + enriquecimiento guiado por cobertura
Dos herramientas complementarias construyen y hacen crecer un diccionario `-x` de AFL
a medida que avanza la campaña:
- **`generate_project_dictionary(source_root, output_path)`** — se ejecuta una vez
antes de la iteración 1, extrae estáticamente el mismo conjunto de tokens de código fuente
usado por el mutador inteligente y lo escribe como un diccionario AFL. Las constantes
numéricas se emiten en AMBAS endiannesses para que el fuzzer pueda satisfacer
`memcmp(x, &magic, 4)` independientemente del orden de bytes del host.
- **`enrich_dictionary_from_uncovered(source_root, dictionary_path,
uncovered_locations)`** — se ejecuta después del paso de cobertura de cada iteración,
escanea el código fuente circundante en busca de guards condicionales
(`strncmp/memcmp/strstr`, `case 0xN:`, `== 0xN`, `== 'X'`) cerca de las
líneas no cubiertas, y AÑADE cualquier token nuevo al diccionario. Idempotente:
nunca vuelve a añadir una entrada que ya está presente.
### 4. Operación de empalme de corpus
Cuando se pasa `corpus_dir` a `generate_smart_mutator`, el C generado
también obtiene un operador de empalme de corpus: en la primera llamada carga hasta 64 archivos
de ese directorio (limitados a 4 KiB cada uno), y a partir de entonces puede empalmar
subregiones aleatorias de esos archivos en la entrada mutada. Esto le da
al mutador un operador de estilo recombinación que el havoc estándar de AFL no hace
bien. Combínalo con `get_persistent_corpus_dir(...)` para que la biblioteca de empalme
sea "remezclar lo que AFL ya ha descubierto".
---
## Corpus persistente entre iteraciones y campañas
Cada harness tiene un directorio de corpus estable en:```
<workspace>/corpus/harness_<id>/
Así es como fuzz_iteration usa seed_dir para run_afl_for (en lugar
de <harness>/seeds). Al final de cada iteración,
fold_queue_into_persistent_corpus(...) fusiona la cola de iteración de AFL en
este directorio y ejecuta afl-cmin para mantenerlo acotado.
El resultado: la cola de ayer se traslada a la ejecución de hoy Y a través de re-ejecuciones del mismo proyecto. Un parar-y-reiniciar de la campaña no pierde ningún progreso.
Después de que el bucle de fuzz/cobertura/mejora finaliza, tres etapas se ejecutan automáticamente:
triage_crashesPara cada archivo de crash en <run>/default/crashes/:
afl-tmin para minimizar la entrada,replay_under_asan para capturar un stack trace y stack_top_hash
(top-N frames normalizados; plantillas, espacios de nombres inline de libcxx,
espacios de nombres anónimos y sufijos numéricos de LTO se eliminan para que
crashes semánticamente idénticos tengan el mismo hash),crash con clasificación de
bug-class + nota de confianza (alta / media / baja).confirm_fixed_crashesReejecuta cada crash previamente clasificado (cuyo veredicto no sea ya
fixed/duplicate/non_reproducible) a través del binario AFL+ASan actual.
Si ya no provoca crash, marca verdict="fixed". Útil al re-ejecutar una
campaña contra un proyecto que ha recibido correcciones upstream desde la
última campaña.
write_vuln_reportsPara cada crash único, el agente lee el código fuente del harness + el código fuente de la función que provoca el crash, recorre la cadena de llamadas desde la API pública, luego asigna uno de diez veredictos al estilo OSS-Fuzz y escribe un informe de vulnerabilidad en markdown:
| Veredicto | Significado |
|---|---|
vulnerability | Real, explotable a través de una API pública |
library_hardening | Bug real pero sin ruta realista por API pública; la biblioteca aún debería defenderse |
harness_bug | El bug está en nuestro harness, no en la biblioteca |
non_reproducible | La reproducción no reproduce el crash con la entrada minimizada |
oom | Out-of-memory; vulnerabilidad solo si el tamaño controlable por el atacante es ilimitado |
timeout | DoS mediante explosión algorítmica |
assertion_failure | Se alcanzó assert(); la relevancia de seguridad varía |
fixed | Establecido por confirm_fixed_crashes: la entrada ya no reproduce |
duplicate | Misma causa raíz que otro crash con un hash de pila diferente |
needs_investigation | No se pudo determinar; marcado para revisión humana |
Cada informe de vulnerabilidad incluye:
El panel se inicia automáticamente en segundo plano mediante
run_fuzzing.sh. Desactívalo con FUZZ_NO_DASHBOARD=1; sobrescribe el puerto
con FUZZ_DASHBOARD_PORT (por defecto 8765).
En un Codespace, el puerto 8765 se reenvía automáticamente — abre la URL
reenviada en cualquier navegador. La página se actualiza automáticamente cada
5 s y muestra:
fuzz_run en cursovulnerability primero),
enlazando a cada informe de vulnerabilidad y entrada minimizadaEl panel también expone una pequeña API JSON de solo lectura para scripts:```bash
curl 'http://127.0.0.1:8765/api/json?repo=kkos/oniguruma' | jq .
---
## Archivos de salida
Todos bajo `~/.local/share/seclab-taskflow-agent/seclab-taskflows/`.
| Ruta | Contenido |
|------|----------|
| `fuzz_context/fuzz_context.db` | SQLite — objetivos, harnesses, ejecuciones, cobertura, crashes, veredictos, grafos de llamadas, sugerencias de harness, notas de iteración |
| `fuzz_runner/builds/` | Binarios `.afl` y `.cov` compilados |
| `fuzz_runner/runs/` | Directorios de salida de AFL + archivos LCOV + informes de cobertura HTML |
| `fuzz_runner/corpus/harness_<id>/` | Corpus persistente por harness (se mantiene entre iteraciones y campañas) |
| `fuzz_runner/repo/<owner>__<repo>/REPORT.md` | Resumen de campaña en Markdown, crashes agrupados por veredicto |
| `fuzz_runner/repo/<owner>__<repo>/vuln_<crash_id>.md` | Informe de vulnerabilidad en Markdown por crash |
| `fuzz_runner/repo/<owner>__<repo>/call_graph.{dot,svg,md}` | Grafo de llamadas estático + superposición de alcanzadas/no alcanzadas |
---
## Esquema de la base de datos
Tablas en `fuzz_context.db` (SQLite vía SQLAlchemy):
| Tabla | Columnas de interés |
|-------|--------------------|
| `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` |
Las migraciones del esquema residen en `_migrate()` en `fuzz_context.py`. Las nuevas TABLAS se crean automáticamente mediante `Base.metadata.create_all()`; solo las nuevas COLUMNAS necesitan `ALTER TABLE` basado en PRAGMA.
---
## Herramientas MCP (el vocabulario del agente)
El agente nunca llama directamente a AFL o clang — compone el pipeline llamando a herramientas MCP. El conjunto completo, agrupado por propósito:
### Persistencia (`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`
### Compilación / fuzzing / cobertura (`fuzz_runner.py`)
- `check_tooling`, `workspace_paths`
- `compile_harness` — compila los binarios `.afl` y `.cov`
- `run_afl_for`, `cmin`, `tmin`, `replay_under_asan`, `reproduce_crash`
- `run_coverage` — reproduce la cola de AFL contra el binario `.cov`, exporta LCOV
- `extract_dictionary` — extrae cadenas imprimibles de un binario
- `package_reproducer` — empaqueta un `.tgz` de un solo crash
### Corpus persistente (v8)
- `get_persistent_corpus_dir`, `fold_queue_into_persistent_corpus`
### Recursos de formato (C5)
- `list_format_assets`, `get_format_dictionary`, `write_format_mutator`
### Mutador inteligente + diccionario consciente del proyecto
- `generate_smart_mutator`, `generate_smart_mutators`
- `generate_project_dictionary`, `enrich_dictionary_from_uncovered`
Las funciones de las herramientas están decoradas con `@mcp.tool()` (FastMCP). Dentro de las pruebas, invócalas mediante el atributo `.fn`, p. ej.
`fr.run_afl_for.fn(afl_binary_path=..., ...)`.
---
## Parámetros ajustables (variables de entorno)
| Variable | Valor predeterminado | Propósito |
|----------|---------|---------|
| `HARNESS_CANDIDATES` | `1` | Número de harnesses candidatos escritos por objetivo. Establécelo en 2 o 3 para una competencia al estilo OSS-Fuzz-Gen. La etapa de calificación ejecuta cada uno durante `QUALIFIER_SECONDS` y conserva el mejor por % de líneas. |
| `QUALIFIER_SECONDS` | `60` | Presupuesto de tiempo real por candidato en la etapa de calificación. |
| `FUZZ_PLATEAU_THRESHOLD_PCT` | `1.0` | Ganancia de cobertura de líneas (en pp absolutos) por debajo de la cual dos iteraciones consecutivas se consideran una meseta y el bucle se detiene anticipadamente. |
| `FUZZ_DASHBOARD_PORT` | `8765` | Puerto para el panel en vivo. |
| `FUZZ_NO_DASHBOARD` | (sin establecer) | Establécelo en `1` para omitir el inicio del panel. |
| `FUZZ_RUNNER_TIMEOUT` | `1200` | Tiempo de espera del subproceso por herramienta en `fuzz_runner` (segundos). |
| `LOCAL_SHELL_TIMEOUT` | `180` | Tiempo de espera por comando en `local_shell` (segundos). |
Además de las variables estándar del agente (`COPILOT_TOKEN`, `LOG_DIR`,
`FUZZ_CONTEXT_DIR`, …). Consulta el README raíz del proyecto para la lista completa.
---
## Extender el pipeline
### Añadir un nuevo formato (mutador + diccionario)
1. Coloca `dictionaries/<name>.dict` (formato `-x` de AFL) y/o
`dictionaries/<name>_mutator.c` (mutador personalizado de libFuzzer).
2. Regístralo en `_FORMAT_ASSETS` al final 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() en fuzz_context.py (para
persistencia) o fuzz_runner.py (para trabajo en subprocesos).Annotated[type, Field(description=...)] para cada argumento — la
descripción es lo que ve el LLM.tests/test_fuzz_context.py /
tests/test_fuzz_runner.py. Invoca la herramienta a través de su atributo .fn
(convención de FastMCP).user_prompt del YAML del taskflow correspondiente.src/seclab_taskflows/taskflows/fuzzing/. Usa
uno de los archivos existentes (p. ej. triage_crashes.yaml) como plantilla.scripts/fuzzing/run_fuzzing.sh entre las dos etapas
existentes correctas.scripts/fuzzing/dashboard.py.Al añadir una nueva tabla SQL:
fuzz_context_models.py.Base.metadata.create_all() se llama en la
inicialización del motor y crea las nuevas tablas automáticamente.Al añadir una nueva COLUMNA a una tabla existente:
PRAGMA table_info + ALTER TABLE ADD COLUMN en
_migrate() en fuzz_context.py para que las bases de datos antiguas se actualicen de forma transparente._migrate_if_writable() en scripts/fuzzing/dashboard.py.benchmark/projects.yaml enumera los proyectos de referencia. Se eligen para que
el pipeline completo v4+ pueda ejecutarse de principio a fin en una imagen de desarrollo de codespace sin
intervención humana.
| # | Repo | Por qué es interesante | Notas |
|---|---|---|---|
| 1 | tukaani-project/xz | Biblioteca del mundo real con mucho análisis sintáctico (liblzma); rica cadena de filtros + superficie de análisis de enteros/VLI | Línea base |
| 2 | DaveGamble/cJSON | Pequeño analizador JSON en C de un solo archivo; CMake trivial | Prueba rápida para el pipeline |
| 3 | akheron/jansson | Biblioteca JSON en C compacta con punto de entrada documentado json_loadb() para búfer de bytes | CMake; ejecuciones/seg muy rápidas |
| 4 | libexpat/libexpat | Analizador XML en streaming maduro; muchos CVE históricos | CMake o autotools |
| 5 | kkos/oniguruma | Motor de regex; recibe patrón del atacante + sujeto | Autotools; la compilación del patrón es la ruta crítica |
Números de referencia de una ejecución completa del pipeline v4 en la imagen de desarrollo de codespace (≈32 min/objetivo):
| Repo | Objetivos | Arneses | Ejecuciones de AFL | Crashes | Veredictos |
|---|---|---|---|---|---|
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 lectura fuera de límites en regerror.c), library_hardening, harness_bug, non_reproducible |
Los resultados de cero crashes de xz / cJSON / libexpat son esperados: esos proyectos
reciben mucho fuzzing upstream. Los dos hallazgos clasificados como vulnerability
en oniguruma son lecturas fuera de límites reales en la ruta de código de formateo de advertencias
de onig_snprintf_with_pattern (lectura de un byte más allá de pat_end cuando
el patrón termina con una barra invertida); los informes markdown por crash
incluyen parches sugeridos.
Para añadir un nuevo proyecto de referencia, añade una entrada en benchmark/projects.yaml
y (opcionalmente) documenta el motivo en benchmark/README.md. Cualquier cosa que la
etapa existente analyze_build_system pueda compilar con clang + flags de AFL++
es un candidato razonable. Los analizadores, decodificadores y serializadores en C puro
suelen funcionar mejor.
BUILD_FAILED:
y los omite.kernel.core_pattern=core y un
ajuste del gobernador de CPU. En un Codespace no están disponibles, por lo que el
taskflow exporta AFL_SKIP_CPUFREQ=1 y
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 por defecto. AFL imprime
advertencias pero sigue encontrando crashes mediante el manejo de abortos al estilo libFuzzer.<dirent.h>. Bien para Linux/macOS; no compilaría en Windows.compile_harness usan
libAFLDriver en modo argv. Por lo tanto, replay_under_asan y tmin
usan stdin_input=False por defecto porque libAFLDriver entra en bucle infinito cuando
se controla mediante stdin.generate_smart_mutator + generate_smart_mutators usan .format() de Python
— cada { / } literal en la plantilla de C debe duplicarse
({{ / }}). Si editas la plantilla y empiezas a ver KeyError,
ese es el motivo.Este taskflow ejecuta afl-fuzz, clang, llvm-cov, y comandos de compilación arbitrarios
elegidos por el LLM, directamente en el host (sin contenedor). Un
agente con inyección de prompt podría en principio hacer cualquier cosa que tu usuario pueda. Ejecuta solo:
git, apt y el sistema de compilación.La caja de herramientas local_shell NO está detrás de un prompt de confirmación — el
taskflow es autónomo y se ejecuta sin un humano en el bucle, por lo que una
confirmación interactiva simplemente se bloquearía para siempre. Cada comando de shell se
registra en $LOG_DIR/mcp_local_shell.log para su revisión a posteriori.
hatch test
hatch fmt --linter --check
hatch fmt --linter
hatch fmt --linter --check -- src/seclab_taskflows/mcp_servers/fuzz_runner.py
Convenciones del código base (véase también `benchmark/improvements.md` para la versión de estas según el historial de la campaña):
- Use `os.environ.get(NAME) or "default"` en lugar de
`os.environ.get(NAME, "default")`. De lo contrario, se devolverían cadenas vacías
procedentes de la sustitución de plantillas YAML.
- Use `X | None` (PEP 604) en las nuevas anotaciones, no `Optional[X]`.
- Las pruebas invocan las herramientas MCP mediante `.fn(...)`, no directamente el nombre decorado.
- Evite los literales `/tmp/...` en las pruebas — use el fixture `tmp_path` de pytest
(regla de lint `S108`).
- Todas las importaciones en línea dentro de los métodos de prueba necesitan `# noqa: PLC0415` si
no puede moverlas al principio del archivo (por ejemplo, cuando se importan condicionalmente
después de un `pytest.skip`).
- Una sola aserción por línea para pruebas de verdad compuestas (regla de lint `PT018`).
El registro de mejoras (`benchmark/improvements.md`) es el log persistente
de lo que se ha añadido al pipeline a lo largo de las versiones. Cuando añada una
característica sustancial, agregue una sección allí describiendo qué cambió, dónde
reside y qué pruebas la protegen.
---
## Glosario
- **AFL++** — Fuzzer greybox guiado por cobertura; el motor de ejecución aquí.
- **libAFLDriver** — Biblioteca estática que permite a los arneses de AFL++ usar
la convención de punto de entrada de libFuzzer (`LLVMFuzzerTestOneInput`).
- **LCOV** — Formato estándar de la industria para archivos de traza de cobertura. Exportamos a él
mediante `llvm-cov export -format=lcov` y lo analizamos nosotros mismos.
- **`stack_top_hash`** — Un hash de 16 caracteres de los N marcos superiores normalizados de
una traza de pila de ASan/UBSan. Se usa para la deduplicación de fallos.
- **Corpus persistente** — Directorio por arnés en
`<workspace>/corpus/harness_<id>/` que conserva las entradas interesantes de AFL
a través de iteraciones y re-ejecuciones de la misma campaña.
- **Mutador inteligente** — Un `LLVMFuzzerCustomMutator` cuyos tokens de empalme se
extraen del propio código fuente del objetivo (`generate_smart_mutator`).
- **Mutador personalizado (libFuzzer)** — Una función C proporcionada por el usuario invocada por
el motor con total libertad sobre cómo mutar un búfer; AFL++ admite
la misma ABI.
- **Herramienta MCP** — Una función decorada con FastMCP que el agente LLM puede invocar.
- **OSS-Fuzz / Fuzz-Introspector** — La infraestructura de fuzzing de código abierto de Google
y su herramienta complementaria de análisis de grafo de llamadas/cobertura.
Varias de las características de este taskflow (mutadores por formato, deduplicación por pila,
informe de grafo de llamadas + API no tocada, arneses multi-candidato) están
inspiradas en ellas.
---
## Licencia
Este proyecto está licenciado bajo los términos de la licencia de código abierto MIT. Consulte el archivo [LICENSE](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/LICENSE.txt) para conocer los términos completos.
## Mantenedores
Consulte [CODEOWNERS](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/CODEOWNERS) o póngase en contacto con el equipo de GitHub Security Lab.
## Soporte
Consulte [SUPPORT.md](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/SUPPORT.md) para obtener detalles sobre cómo obtener ayuda con este proyecto.
## Agradecimientos
Este proyecto se basa en los conceptos y técnicas de [AFL++](https://github.com/AFLplusplus/AFLplusplus), [OSS-Fuzz](https://github.com/google/oss-fuzz) y [Fuzz-Introspector](https://github.com/ossf/fuzz-introspector).