Um pipeline de fuzzing orientado por LLM alimentado pelo GitHub Security Lab Taskflow Agent
Um pipeline de fuzzing no estilo OSS-Fuzz, orientado por LLM, para projetos nativos em C/C++. AFL++ para execução, clang+lcov para cobertura, um agente LLM para escrita de harness, decisões baseadas em feedback de cobertura, triagem e relatórios.
Este repositório contém o taskflow de fuzzing para o
GitHub Security Lab Taskflow Agent.
Ele depende do repositório complementar
seclab-taskflows
para alguns blocos de construção compartilhados
(taskflow fetch_source_code, toolboxes local_file_viewer / gh_file_viewer
e o model_config padrão) — esses são instalados
automaticamente como uma dependência Python.
Contribuições são bem-vindas! Consulte CONTRIBUTING.md para diretrizes.
aptgh)pip install git+https://github.com/GitHubSecurityLab/seclab-taskflows-fuzzing
Isso traz `seclab-taskflow-agent` e `seclab-taskflows` (pai)
transitivamente, então toda referência pontuada da forma
`seclab_taskflows.taskflows.audit.*`,
`seclab_taskflows.toolboxes.local_file_viewer`,
`seclab_taskflows.toolboxes.gh_file_viewer`, e
`seclab_taskflows.configs.model_config` é resolvida a partir da distribuição
pai em tempo de execução.
---
## Índice
1. [O que é isto](#what-this-is)
2. [Início rápido](#quick-start)
3. [Arquitetura](#architecture)
4. [O pipeline, estágio por estágio](#the-pipeline-stage-by-stage)
5. [O ciclo de feedback de cobertura](#the-coverage-feedback-loop)
6. [Fuzzing ciente de estrutura](#structure-aware-fuzzing)
7. [Corpus persistente entre iterações e campanhas](#persistent-corpus-across-iterations-and-campaigns)
8. [Triagem e relatórios de vulnerabilidade](#triage-and-vulnerability-reports)
9. [Dashboard ao vivo](#live-dashboard)
10. [Arquivos de saída](#output-files)
11. [Esquema do banco de dados](#database-schema)
12. [Ferramentas MCP (o vocabulário do agente)](#mcp-tools-the-agents-vocabulary)
13. [Ajustes configuráveis (variáveis de ambiente)](#tunable-knobs-environment-variables)
14. [Estendendo o pipeline](#extending-the-pipeline)
15. [Projetos de benchmark e resultados](#benchmark-projects-and-results)
16. [Limitações e armadilhas](#limitations-and-gotchas)
17. [Aviso de segurança](#security-warning)
18. [Desenvolvimento: testes, linting, contribuição](#development-testing-linting-contributing)
19. [Glossário](#glossary)
---
## O que é isto
Este taskflow é um pipeline de fuzzing totalmente autônomo. Dado um repositório
GitHub de um projeto nativo em C/C++, ele irá:
1. instalar AFL++ + clang/llvm/lcov + ctags/cscope/graphviz se estiverem ausentes,
2. buscar o código-fonte,
3. identificar alvos de fuzzing candidatos (parsers, decodificadores, validadores, …),
4. analisar o sistema de build,
5. escrever um ou mais candidatos a harness por alvo, compilar cada um tanto como
um binário `.afl` instrumentado com AFL quanto como um binário `.cov`
instrumentado para cobertura,
6. (opcionalmente) qualificar candidatos por cobertura de 60 segundos e manter o melhor,
7. executar um ciclo de fuzz/cobertura/melhoria com orçamentos de tempo que dobram,
8. fazer triagem de cada crash, confirmar que crashes previamente conhecidos ainda
se reproduzem, e escrever relatórios markdown de vulnerabilidade por crash com
veredictos, explorabilidade, patches sugeridos e esboços de testes de regressão,
9. construir um grafo de chamadas no estilo Fuzz-Introspector + relatório de APIs
não tocadas para a próxima campanha,
10. publicar tudo em um dashboard HTML ao vivo.
O pipeline é **no estilo OSS-Fuzz**: usa muitas das mesmas
técnicas (mutadores e dicionários por formato, splicing de tokens ciente de
estrutura, melhorias de harness guiadas por cobertura, relatórios legíveis por
máquina, crashes deduplicados por hash de pilha), mas é muito menor e
autocontido.
---
## Início rápido```bash
# Inside the codespace (or a host with python + git available):
./scripts/fuzzing/run_fuzzing.sh tukaani-project/xz
Essa é toda a interface. O script é autônomo; ele instalará o AFL++
na primeira execução e, em seguida, conduzirá o restante do fluxo de tarefas. Os arquivos de saída são gravados
em ~/.local/share/seclab-taskflow-agent/seclab-taskflows/.
O dashboard é iniciado automaticamente em segundo plano; em um Codespace, a porta 8765 é
redirecionada automaticamente — abra-a em qualquer navegador para acompanhar o progresso ao vivo.
Para um teste rápido de fumaça, use um alvo pequeno:```bash ./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON
---
## Arquitetura
Três camadas, de cima para baixo:```
┌────────────────────────────────────────────────────────────────────┐
│ 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 │
└────────────────────────────────────────────────────────────────────┘
Regras de design fundamentais:
fuzz_context.db.run_afl_for, compile_harness, store_crash, etc.afl-clang-lto -fsanitize=address,undefined (o binário .afl) e outra
com clang -fprofile-instr-generate -fcoverage-mapping (o binário .cov).
O binário .afl faz fuzzing; o binário .cov reproduz a fila do AFL
para produzir cobertura real de linhas de código/funções/ramos.| # | Etapa | Taskflow YAML |
|---|---|---|
| 1 | Instalar AFL++ + ferramentas | scripts/fuzzing/install_afl.sh |
| 2 | Obter código-fonte | seclab_taskflows.taskflows.audit.fetch_source_code |
| 3 | Identificar alvos de fuzzing | seclab_taskflows_fuzzing.taskflows.fuzzing.identify_fuzz_targets |
| 4 | Analisar sistema de build | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_build_system |
| 5a | Escrever harnesses iniciais (×N candidatos se solicitado) | seclab_taskflows_fuzzing.taskflows.fuzzing.write_initial_harnesses |
| 5b | Compilar harnesses (AFL + cobertura) | seclab_taskflows_fuzzing.taskflows.fuzzing.build_harnesses |
| 5c | Qualificar candidatos (quando HARNESS_CANDIDATES > 1) | seclab_taskflows_fuzzing.taskflows.fuzzing.qualify_harnesses |
| 6 | Ciclo de fuzzing/cobertura/melhoria (×N iterações) | seclab_taskflows_fuzzing.taskflows.fuzzing.fuzz_iteration |
| 7 | Triagem de crashes | seclab_taskflows_fuzzing.taskflows.fuzzing.triage_crashes |
| 8 | Confirmar que crashes previamente conhecidos ainda se reproduzem | seclab_taskflows_fuzzing.taskflows.fuzzing.confirm_fixed_crashes |
| 9 | Construir grafo de chamadas + relatório de APIs não tocadas | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_call_graph |
| 10 | Escrever relatórios de vulnerabilidade por crash | seclab_taskflows_fuzzing.taskflows.fuzzing.write_vuln_reports |
| 11 | Escrever relatório da campanha | seclab_taskflows_fuzzing.taskflows.fuzzing.write_report |
Cada etapa é um taskflow YAML autocontido que o agente executa
de ponta a ponta. As etapas comunicam exclusivamente através da base de dados SQLite
em fuzz_context.db — não há transferência em memória.
Este é o coração do pipeline. Os orçamentos de tempo duplicam a cada iteração:``` 30s → 60s → 120s → 240s → 480s → 960s (≈ 32 min/target)
A cada iteração, a cada harness, o agente:
1. Solicita `get_persistent_corpus_dir(harness_id)` para obter o diretório
de corpus estável deste harness.
2. Chama `run_afl_for(afl_binary_path, seed_dir=<persistent corpus>,
output_dir=<run dir>, seconds=<budget>, dictionary=<auto.dict>)`.
3. Chama `run_coverage(cov_binary_path, inputs_dir=<run>/default/queue,
output_dir=<run>/coverage)` para produzir um tracefile LCOV e um relatório HTML.
4. Chama `store_coverage_from_lcov(run_id, lcov_path, html_path)` para persistir
uma linha `coverage_report` + linhas `coverage_gap` por item não coberto.
5. Chama `fold_queue_into_persistent_corpus(...)` para fundir a fila de iteração
do AFL no corpus persistente e executar `cmin` para manter o tamanho limitado.
6. Lê `get_coverage_summary` + `get_coverage_gaps`, e então ou:
- adiciona uma nova seed (marcada com `coverage_feedback`) para alcançar um
branch não coberto,
- edita o código-fonte do harness para chamar uma API adicional,
- chama `enrich_dictionary_from_uncovered(...)` para adicionar automaticamente
entradas de dicionário para as constantes mágicas que o AFL precisa para
satisfazer um guard, ou
- ignora a lacuna (caminho de erro frio / código do fornecedor).
7. Chama `store_iteration_note(repo, iteration_number, harness_id, note=<resumo
de uma linha>)` para que a linha do tempo de iterações do dashboard acompanhe o que
mudou.
**Detecção de platô.** O loop termina antecipadamente assim que duas iterações consecutivas
tiverem ambas ganhado < `FUZZ_PLATEAU_THRESHOLD_PCT` (padrão `1.0`) pontos percentuais
absolutos de cobertura de linhas.
---
## Fuzzing ciente da estrutura
Três mecanismos complementares produzem entradas mais fortes do que a mutação bruta de bytes.
### 1. Dicionários por formato + mutadores personalizados
Para alvos cujo `input_kind` corresponde a um formato conhecido, o taskflow inclui
dicionários pré-construídos e arquivos-fonte C `LLVMFuzzerCustomMutator`:
| Formato | Dicionário | Mutador | Notas |
|--------|------------|---------|-------|
| `json` | `json.dict` | `json_mutator.c` | Emenda de tokens, dup/drop de colchetes balanceados, inversão de tipo |
| `xml` | `xml.dict` | `xml_mutator.c` | Tags, entidades, DTDs, tokens billion-laughs |
| `regex` | `regex.dict` | `regex_mutator.c` | Âncoras, classes, quantificadores, padrões ReDoS reais |
| `binary_tlv` | _(nenhum)_ | `binary_tlv_mutator.c` | Registros com prefixo de comprimento: estouro de comprimento / dup / drop |
| `png` | `png.dict` | _(reutiliza binary_tlv)_ | Dicionário PNG + mutador binary_tlv |
Estes são capturados automaticamente por `write_initial_harnesses` (dicionário
copiado ao lado das seeds) e `build_harnesses` (mutador vinculado ao binário
AFL). Cada mutador delega 50% das mutações ao mutador de bytes padrão do AFL
para não perdermos a randomização do motor.
Para adicionar um novo formato: coloque um `<name>.dict` e/ou um `<name>_mutator.c` em
`src/seclab_taskflows/dictionaries/`, depois registre-o no
mapa `_FORMAT_ASSETS` na parte inferior de `fuzz_runner.py`.
### 2. Mutador inteligente ciente do código-fonte (específico do projeto)
Para formatos desconhecidos, ou sempre que você quiser tokens mais fortes e específicos do projeto,
`generate_smart_mutator` escaneia os próprios arquivos `.c`/`.h` do repositório alvo
e emite um arquivo C `LLVMFuzzerCustomMutator` cujos dicionários de emenda
são extraídos de:
- literais de string com ≥3 caracteres alfabéticos (após filtrar ruído de compilador/licença,
caminhos, cabeçalhos, restrições asm, especificadores de formato),
- constantes numéricas de 32 bits de `#define`, `case` e `enum` (após
filtrar ruído genérico de inteiros pequenos como 0, 1, 256, 0xff…).
Três focos estão disponíveis:
| Foco | O que emenda | Quando usar |
|-------|-----------------|-------------|
| `strings` | Apenas literais de string do projeto | Formatos de texto (JSON, XML, YAML, CSV) |
| `constants` | Apenas valores mágicos numéricos de 32 bits | Protocolos binários, cabeçalhos com números mágicos |
| `combined` | Ambos | Padrão; geralmente o melhor |
Combine `generate_smart_mutators(...)` (plural) com `HARNESS_CANDIDATES >= 3`
para que cada foco se torne um harness candidato na rodada de qualificação.
### 3. Dicionário AFL ciente do projeto + enriquecimento orientado por cobertura
Duas ferramentas complementares constroem e expandem um dicionário AFL `-x` à medida que
a campanha avança:
- **`generate_project_dictionary(source_root, output_path)`** — executa uma vez
antes da iteração 1, extrai estaticamente o mesmo conjunto de tokens de código-fonte usado pelo
mutador inteligente e o escreve como um dicionário AFL. Constantes numéricas
são emitidas em AMBAS as endiannesses para que o fuzzer possa satisfazer
`memcmp(x, &magic, 4)` independentemente da ordem de bytes do host.
- **`enrich_dictionary_from_uncovered(source_root, dictionary_path,
uncovered_locations)`** — executa após a etapa de cobertura de cada iteração,
escaneia o código-fonte ao redor em busca de guards condicionais
(`strncmp/memcmp/strstr`, `case 0xN:`, `== 0xN`, `== 'X'`) próximos às
linhas não cobertas, e ANEXA quaisquer novos tokens ao dicionário. Idempotente:
nunca readiciona uma entrada que já está presente.
### 4. Operação de emenda de corpus
Quando `corpus_dir` é passado para `generate_smart_mutator`, o C gerado
também recebe um operador de emenda de corpus: na primeira chamada ele carrega até 64 arquivos
desse diretório (limitados a 4 KiB cada), e a partir daí pode emendar
sub-regiões aleatórias desses arquivos na entrada mutada. Isso dá ao
mutador um operador no estilo de recombinação que o havoc padrão do AFL não faz
bem. Combine com `get_persistent_corpus_dir(...)` para que a biblioteca de emenda
seja "remixar o que o AFL já descobriu".
---
## Corpus persistente entre iterações e campanhas
Cada harness tem um diretório de corpus estável em:```
<workspace>/corpus/harness_<id>/
É isto que fuzz_iteration usa como seed_dir para run_afl_for (em vez
de <harness>/seeds). No final de cada iteração,
fold_queue_into_persistent_corpus(...) funde a fila de iteração do AFL neste
diretório e executa afl-cmin para o manter limitado.
O resultado: a fila de ontem transita para a execução de hoje E entre re-execuções do mesmo projeto. Uma paragem e reinício da campanha não perde progresso.
Depois de o ciclo fuzz/coverage/improve terminar, três fases são executadas automaticamente:
triage_crashesPara cada ficheiro de crash em <run>/default/crashes/:
afl-tmin para minimizar a entrada,replay_under_asan para capturar um stack trace e stack_top_hash
(top-N frames normalizados; templates, namespaces inline do libcxx,
namespaces anónimos e sufixos numéricos de LTO são removidos para que
crashes semanticamente idênticos tenham o mesmo hash),crash com classificação de
bug-class + nota de confiança (high / medium / low).confirm_fixed_crashesReexecuta cada crash previamente classificado (cujo veredicto ainda não seja
fixed/duplicate/non_reproducible) através do binário AFL+ASan atual.
Se já não provocar crash, marca verdict="fixed". Útil ao reexecutar uma
campanha contra um projeto que teve correções upstream aplicadas desde a
última campanha.
write_vuln_reportsPara cada crash único, o agente lê o código-fonte do harness + o código-fonte da função que provoca o crash, percorre a cadeia de chamadas a partir da API pública e depois atribui um de dez veredictos no estilo OSS-Fuzz e escreve um relatório de vulnerabilidade em markdown:
| Veredicto | Significado |
|---|---|
vulnerability | Real, explorável através de uma API pública |
library_hardening | Bug real mas sem caminho realista pela API pública; a biblioteca deve ainda assim defender-se |
harness_bug | O bug está no nosso harness, não na biblioteca |
non_reproducible | A reexecução não reproduz o crash na entrada minimizada |
oom | Out-of-memory; vulnerabilidade apenas se o tamanho controlável pelo atacante for ilimitado |
timeout | DoS por explosão algorítmica |
assertion_failure | assert() acionado; relevância de segurança varia |
fixed | Definido por confirm_fixed_crashes: a entrada já não reproduz |
duplicate | Mesma causa raiz que outro crash com um stack hash diferente |
needs_investigation | Não foi possível determinar; sinalizado para revisão humana |
Cada relatório de vulnerabilidade inclui:
O dashboard é iniciado automaticamente em segundo plano por
run_fuzzing.sh. Desative com FUZZ_NO_DASHBOARD=1; substitua a porta
com FUZZ_DASHBOARD_PORT (predefinição 8765).
Num Codespace, a porta 8765 é reencaminhada automaticamente — abra o URL
reencaminhado em qualquer navegador. A página atualiza-se automaticamente a
cada 5 s e mostra:
fuzz_run em cursovulnerability primeiro),
com ligações para cada relatório de vulnerabilidade e entrada minimizadaO dashboard também expõe uma pequena API JSON apenas de leitura para scripts:```bash
curl 'http://127.0.0.1:8765/api/json?repo=kkos/oniguruma' | jq .
---
## Arquivos de saída
Todos em `~/.local/share/seclab-taskflow-agent/seclab-taskflows/`.
| Caminho | Conteúdo |
|------|----------|
| `fuzz_context/fuzz_context.db` | SQLite — alvos, harnesses, execuções, cobertura, crashes, veredictos, grafos de chamadas, sugestões de harness, notas de iteração |
| `fuzz_runner/builds/` | Binários `.afl` e `.cov` compilados |
| `fuzz_runner/runs/` | Diretórios de saída do AFL + arquivos LCOV + relatórios de cobertura HTML |
| `fuzz_runner/corpus/harness_<id>/` | Corpus persistente por harness (mantém-se entre iterações e campanhas) |
| `fuzz_runner/repo/<owner>__<repo>/REPORT.md` | Resumo da campanha em Markdown, crashes agrupados por veredicto |
| `fuzz_runner/repo/<owner>__<repo>/vuln_<crash_id>.md` | Relatório de vulnerabilidade em Markdown por crash |
| `fuzz_runner/repo/<owner>__<repo>/call_graph.{dot,svg,md}` | Grafo de chamadas estático + sobreposição de alcançadas/não alcançadas |
---
## Esquema da base de dados
Tabelas em `fuzz_context.db` (SQLite via SQLAlchemy):
| Tabela | Colunas de 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` |
As migrações de esquema residem em `_migrate()` em `fuzz_context.py`. Novas TABELAS são
criadas automaticamente por `Base.metadata.create_all()`; apenas novas COLUNAS precisam
de `ALTER TABLE` baseado em PRAGMA.
---
## Ferramentas MCP (o vocabulário do agente)
O agente nunca chama AFL ou clang diretamente — compõe o pipeline
chamando ferramentas MCP. O conjunto completo, agrupado por finalidade:
### Persistência (`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 / cobertura (`fuzz_runner.py`)
- `check_tooling`, `workspace_paths`
- `compile_harness` — compila os binários `.afl` e `.cov`
- `run_afl_for`, `cmin`, `tmin`, `replay_under_asan`, `reproduce_crash`
- `run_coverage` — reproduz a fila do AFL contra o binário `.cov`, exporta LCOV
- `extract_dictionary` — extrai strings imprimíveis de um binário
- `package_reproducer` — empacota um `.tgz` de crash único
### 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 + dicionário ciente do projeto
- `generate_smart_mutator`, `generate_smart_mutators`
- `generate_project_dictionary`, `enrich_dictionary_from_uncovered`
As funções de ferramenta são decoradas com `@mcp.tool()` (FastMCP). Dentro dos testes,
invocam-se através do atributo `.fn`, por exemplo
`fr.run_afl_for.fn(afl_binary_path=..., ...)`.
---
## Parâmetros ajustáveis (variáveis de ambiente)
| Variável | Padrão | Finalidade |
|----------|---------|---------|
| `HARNESS_CANDIDATES` | `1` | Número de harnesses candidatos escritos por alvo. Defina como 2 ou 3 para competição ao estilo OSS-Fuzz-Gen. A fase de qualificação executa cada um durante `QUALIFIER_SECONDS` e mantém o melhor por % de linhas. |
| `QUALIFIER_SECONDS` | `60` | Orçamento de tempo real por candidato na fase de qualificação. |
| `FUZZ_PLATEAU_THRESHOLD_PCT` | `1.0` | Ganho de cobertura de linhas (em pp absolutos) abaixo do qual duas iterações consecutivas são consideradas um plateau e o ciclo termina antecipadamente. |
| `FUZZ_DASHBOARD_PORT` | `8765` | Porta para o dashboard em tempo real. |
| `FUZZ_NO_DASHBOARD` | (não definido) | Defina como `1` para ignorar o arranque do dashboard. |
| `FUZZ_RUNNER_TIMEOUT` | `1200` | Timeout do subprocesso por ferramenta em `fuzz_runner` (segundos). |
| `LOCAL_SHELL_TIMEOUT` | `180` | Timeout por comando em `local_shell` (segundos). |
Mais as variáveis padrão do agente (`COPILOT_TOKEN`, `LOG_DIR`,
`FUZZ_CONTEXT_DIR`, …). Consulte o README na raiz do projeto para a lista completa.
---
## Estender o pipeline
### Adicionar um novo formato (mutador + dicionário)
1. Coloque `dictionaries/<name>.dict` (formato `-x` do AFL) e/ou
`dictionaries/<name>_mutator.c` (mutador personalizado libFuzzer).
2. Registe em `_FORMAT_ASSETS` no 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() em fuzz_context.py (para
persistência) ou fuzz_runner.py (para trabalho em subprocesso).Annotated[type, Field(description=...)] para cada argumento — a
descrição é o que o LLM vê.tests/test_fuzz_context.py /
tests/test_fuzz_runner.py. Invoque a ferramenta através do seu atributo .fn
(convenção do FastMCP).user_prompt do YAML do taskflow relevante.src/seclab_taskflows/taskflows/fuzzing/. Use
um dos arquivos existentes (por exemplo, triage_crashes.yaml) como modelo.scripts/fuzzing/run_fuzzing.sh entre os dois estágios
existentes corretos.scripts/fuzzing/dashboard.py.Ao adicionar uma nova tabela SQL:
fuzz_context_models.py.Base.metadata.create_all() é chamado na inicialização
do engine e cria novas tabelas automaticamente.Ao adicionar uma nova COLUNA a uma tabela existente:
PRAGMA table_info + ALTER TABLE ADD COLUMN em
_migrate() em fuzz_context.py para que bancos de dados antigos sejam atualizados de forma transparente._migrate_if_writable() em scripts/fuzzing/dashboard.py.benchmark/projects.yaml lista os projetos de referência. Eles são escolhidos para
que o pipeline completo v4+ possa rodar de ponta a ponta em uma imagem de desenvolvimento do codespace sem
intervenção humana.
| # | Repo | Por que é interessante | Notas |
|---|---|---|---|
| 1 | tukaani-project/xz | Biblioteca real com muito parsing (liblzma); cadeia de filtros rica + superfície de parsing de inteiros/VLI | Baseline |
| 2 | DaveGamble/cJSON | Parser JSON em C de arquivo único pequeno; CMake trivial | Smoke rápido para o pipeline |
| 3 | akheron/jansson | Biblioteca JSON em C compacta com ponto de entrada documentado json_loadb() para buffer de bytes | CMake; exec/sec muito rápido |
| 4 | libexpat/libexpat | Parser XML de streaming maduro; muitos CVEs históricos | CMake ou autotools |
| 5 | kkos/oniguruma | Motor de regex; recebe padrão do atacante + sujeito | Autotools; a compilação do padrão é o caminho crítico |
Números de referência de uma execução completa do pipeline v4 na imagem de desenvolvimento do codespace (≈32 min/alvo):
| Repo | Alvos | Harnesses | Execuções 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 leitura fora dos limites em regerror.c), library_hardening, harness_bug, non_reproducible |
Os resultados de zero crashes do xz / cJSON / libexpat são esperados: esses projetos
são intensamente fuzzed upstream. Os dois achados classificados como vulnerability
no oniguruma são leituras reais fora dos limites no caminho de código de formatação de avisos
de onig_snprintf_with_pattern (leitura de um byte além de pat_end quando
o padrão termina com uma barra invertida); os relatórios markdown por crash
incluem patches sugeridos.
Para adicionar um novo projeto de benchmark, adicione uma entrada em benchmark/projects.yaml
e (opcionalmente) documente o motivo em benchmark/README.md. Qualquer coisa que o
estágio existente analyze_build_system consiga compilar com clang + flags do AFL++
é um candidato razoável. Parsers, decodificadores e serializadores em C puro
tendem a funcionar melhor.
BUILD_FAILED:
e os ignora.kernel.core_pattern=core e um
ajuste no governor da CPU. Em um Codespace isso não está disponível, então o
taskflow exporta AFL_SKIP_CPUFREQ=1 e
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 por padrão. O AFL imprime
avisos, mas ainda encontra crashes através do tratamento de abort no estilo libFuzzer.<dirent.h>. Ok para Linux/macOS; não compilaria no Windows.compile_harness usam
libAFLDriver em modo argv. replay_under_asan e tmin portanto
usam stdin_input=False por padrão porque o libAFLDriver entra em loop infinito quando
acionado via stdin.generate_smart_mutator + generate_smart_mutators usam .format() do Python
— cada { / } literal no template C deve ser duplicado
({{ / }}). Se você editar o template e começar a ver KeyError,
é por isso.Este taskflow executa afl-fuzz, clang, llvm-cov, e comandos de build arbitrários
escolhidos pelo LLM, diretamente no host (sem container). Um
agente com prompt injetado poderia, em princípio, fazer qualquer coisa que seu usuário pode. Execute apenas:
git, apt e o sistema de build
precisam.A toolbox local_shell NÃO está atrás de um prompt de confirmação — o
taskflow é autônomo e roda sem um humano no loop, então uma
confirmação interativa simplesmente bloquearia para sempre. Todo comando de shell é
registrado em $LOG_DIR/mcp_local_shell.log para revisão posterior.
hatch test
hatch fmt --linter --check
hatch fmt --linter
hatch fmt --linter --check -- src/seclab_taskflows/mcp_servers/fuzz_runner.py
Convenções do código (veja também `benchmark/improvements.md` para a
versão de histórico de campanha destas):
- Use `os.environ.get(NAME) or "default"` em vez de
`os.environ.get(NAME, "default")`. Strings vazias provenientes da
substituição de template YAML seriam retornadas de outra forma.
- Use `X | None` (PEP 604) em novas anotações, não `Optional[X]`.
- Os testes invocam ferramentas MCP via `.fn(...)`, não o nome decorado
diretamente.
- Evite literais `/tmp/...` nos testes — use a fixture `tmp_path` do pytest
(regra de lint `S108`).
- Todos os imports inline dentro de métodos de teste precisam de
`# noqa: PLC0415` se você não puder movê-los para o topo do arquivo
(por exemplo, quando importados condicionalmente após um `pytest.skip`).
- Uma asserção por linha para testes de verdade compostos (regra de lint
`PT018`).
O rastreador de melhorias (`benchmark/improvements.md`) é o log
persistente do que foi adicionado ao pipeline ao longo das versões. Quando
você adicionar uma funcionalidade substancial, adicione uma seção lá
descrevendo o que mudou, onde ela vive e quais testes a protegem.
---
## Glossário
- **AFL++** — Fuzzer greybox guiado por cobertura; o motor de execução aqui.
- **libAFLDriver** — Biblioteca estática que permite que harnesses do
AFL++ usem a convenção de ponto de entrada do libFuzzer
(`LLVMFuzzerTestOneInput`).
- **LCOV** — Formato padrão da indústria para arquivos de rastreamento de
cobertura. Exportamos para ele via `llvm-cov export -format=lcov` e o
analisamos nós mesmos.
- **`stack_top_hash`** — Um hash de 16 caracteres dos N frames superiores
normalizados de um stack trace do ASan/UBSan. Usado para deduplicação de
crashes.
- **Corpus persistente** — Diretório por harness em
`<workspace>/corpus/harness_<id>/` que carrega as entradas interessantes
do AFL entre iterações e reexecuções da mesma campanha.
- **Mutador inteligente** — Um `LLVMFuzzerCustomMutator` cujos tokens de
splice são extraídos do próprio código-fonte do alvo
(`generate_smart_mutator`).
- **Mutador customizado (libFuzzer)** — Uma função C fornecida pelo
usuário, chamada pelo motor com total liberdade sobre como mutar um
buffer; o AFL++ suporta a mesma ABI.
- **Ferramenta MCP** — Uma função decorada com FastMCP que o agente LLM
pode chamar.
- **OSS-Fuzz / Fuzz-Introspector** — A infraestrutura de fuzzing de
código aberto do Google e sua ferramenta companheira de análise de
call-graph/cobertura. Várias funcionalidades deste taskflow (mutadores
por formato, dedup por stack, relatório de call-graph + APIs não
tocadas, harnesses multi-candidatos) são inspiradas nelas.
---
## Licença
Este projeto está licenciado sob os termos da licença de código aberto
MIT. Consulte o arquivo [LICENSE](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/LICENSE.txt) para os termos completos.
## Mantenedores
Veja [CODEOWNERS](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/CODEOWNERS) ou entre em contato com a equipe do GitHub
Security Lab.
## Suporte
Veja [SUPPORT.md](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/SUPPORT.md) para detalhes sobre como obter ajuda com
este projeto.
## Agradecimentos
Este projeto é construído sobre os conceitos e técnicas do
[AFL++](https://github.com/AFLplusplus/AFLplusplus),
[OSS-Fuzz](https://github.com/google/oss-fuzz) e
[Fuzz-Introspector](https://github.com/ossf/fuzz-introspector).