
Um benchmark DAST de aplicativos intencionalmente vulneráveis com chaves de resposta de referência para pontuar scanners
Um benchmark de DAST com aplicações intencionalmente vulneráveis e chaves de resposta com ground truth para pontuar scanners.
⚠️ Este repositório contém aplicações DELIBERADAMENTE INSEGURAS. Elas existem apenas para fazer benchmark de ferramentas de segurança - scanners DAST, engines SAST e agentes de segurança LLM. Cada aplicação vincula-se a
127.0.0.1, exibe um banner BEM VISÍVEL e não contém dados reais. Nunca implante nada disso em uma rede pública.
Um conjunto de 19 aplicações intencionalmente vulneráveis, uma por stack, cada uma com um ground truth documentado e verificável por máquina. O objetivo é medir o quão bem um scanner ou agente (a) encontra os bugs plantados, (b) ignora os quase-acertos seguros no código logo ao lado deles e (c) não alucina achados no gêmeo corrigido.
19 aplicações · 549 vulnerabilidades plantadas · 146 quase-acertos · 546 PoCs executáveis ·
594 endpoints catalogados. Cada aplicação inicia em 127.0.0.1:13311, inclui um
par gêmeo vuln/+safe/ e um build --solo de imagem única.
imprime esta tabela ao vivo; imprime seu catálogo de endpoints.
dynast-bench listdynast-bench surface <app>| App | Stack | Datastore | Vulns | Quase-acerto | Docs |
|---|---|---|---|---|---|
| aspnet | C# / ASP.NET Core Razor Pages | SQL Server | 28 | 12 | plan |
| fastapi | Python / FastAPI + Jinja2 | Postgres | 26 | 5 | plan |
| gin | Go / Gin | Postgres | 12 | 7 | readme |
| golang | Go / chi | Postgres | 26 | 4 | plan |
| graphql | Node / GraphQL 16 API-only | Postgres | 31 | 6 | plan |
| jsp | Java / JSP + Servlets (Tomcat) | Postgres | 28 | 6 | plan |
| laravel | PHP 8.3 / Laravel 11 + Blade | MySQL | 25 | 7 | plan |
Sidecars extras por stack (Mailpit, MinIO, Redis, Jenkins, Prometheus, Ollama, …) estão listados em As aplicações abaixo.
Os documentos de design por stack estão em benchmark-plans/ -
comece por lá para o catálogo completo de vulnerabilidades de cada aplicação. Este README é o
guia operacional: como o repositório está organizado e como executar e pontuar uma aplicação.
Cada bug plantado carrega um CWE e uma categoria OWASP. Agrupados por classe - cada bug contado uma vez, sob seu CWE primário - os bugs plantados se dividem aproximadamente como (rollup regenerado pela última vez em 480 bugs; as contagens por aplicação acima estão atualizadas):
| Classe | CWEs | Bugs | Apps |
|---|---|---|---|
| Exposição de dados sensíveis (erros, logs, endpoints de debug, backups, código-fonte) | 200, 209, 489, 524, 532, 538, 540, 548 | 39 | 16 |
| Credenciais padrão / hardcoded / vazadas | 321, 522, 798, 1104, 1392 | 38 | 18 |
| Autorização ausente ou quebrada (BFLA, vertical + horizontal) | 269, 284, 285, 668, 862, 863 | 37 | 18 |
| Cross-site scripting (refletido · armazenado · DOM) | 79 | 28 | 16 |
| Bypass de autenticação · sessão fraca · verificação JWT | 287, 288, 290, 306, 347, 384, 613, 614, 1385 | 28 | 13 |
| Injeção de SQL (incl. de segunda ordem, ORDER BY, NoSQL) | 89, 943 | 27 | 17 |
| Conflitos de interpretação proxy/parser (confusão de caminho, confiança em headers) | 345, 348, 349, 436, 441, 693, 697, 706, 807 | 27 | 10 |
| SSRF (incl. cego, cadeias de redirecionamento, sinks somente internos) | 918 | 20 | 17 |
| IDOR / BOLA (chave de objeto controlada pelo usuário) | 639 | 19 | 17 |
| Path traversal · LFI/RFI · zip slip | 22, 98 | 19 | 16 |
| Mass assignment / over-posting · poluição de protótipo | 915, 1321 | 18 | 16 |
| Força bruta · ausência de rate limiting · exaustão de recursos | 307, 400, 406, 674, 770 | 17 | 11 |
| Abuso de lógica de negócio, preços e cotas | 625, 840 | 15 | 14 |
| Injeção de comando / argumento no SO | 78 | 14 | 12 |
| Desserialização insegura (pickle · PHP · Java · YAML) | 470, 502 | 14 | 11 |
| Configuração incorreta de CORS |
Por categoria OWASP (Top 10 2021 para as aplicações web, API Top 10 2023 onde a aplicação é somente API):
| OWASP | Bugs | OWASP API | Bugs | |
|---|---|---|---|---|
| A01 Broken Access Control | 118 | API8 Security Misconfiguration | 21 | |
| A03 Injection | 89 | API5 Broken Function Level Authorization | 6 | |
| A05 Security Misconfiguration | 72 | API1 Broken Object Level/Property Authorization | 4 | |
| A07 Identification & Authentication Failures | 65 | API2 Broken Authentication | 4 | |
| A04 Insecure Design | 34 | API7 Server Side Request Forgery | 4 | |
| A08 Software & Data Integrity Failures | 17 | API9 Improper Inventory Management | 3 | |
| A10 SSRF | 15 | API3 Broken Object Property Level Authorization | 2 | |
| A02 Cryptographic Failures | 15 | API4 Unrestricted Resource Consumption | 2 | |
| A09 Logging & Monitoring Failures | 4 | API6 Unrestricted Access to Sensitive Business Flows | 1 | |
| A06 Vulnerable & Outdated Components | 3 | API10 Unsafe Consumption of APIs | 1 |
Duas trilhas não-web ficam ao lado destas: a aplicação network planta 32 achados
de host/porta e de nível de serviço para scanners de rede, e as duas aplicações LLM
(llmchat, llmagent) plantam bugs de prompt-injection, abuso de ferramentas e envenenamento de RAG
pontuados em uma trilha separada de canal de injeção.
Cada bug também é marcado com uma dificuldade de detecção (118 E, 68 E-M, 202
M, 61 M-H, 100 H), uma distância de taint (351 in-file, 83 cross-file, 87
cross-service, 28 config) e uma alcançabilidade (368 pre-auth, 181
user), para que o recall possa ser detalhado ao longo de cada um desses eixos em vez de
relatado como um único número. Os catálogos por aplicação estão em
benchmark-plans/.
dynast-bench/
├── README.md # you are here - overview, safety, run/score guide
├── examples/ # ready-to-score findings/v1 + endpoints/v1 files
├── Makefile # top-level runner: list / run / verify / validate / solo any app
├── benchmark-plans/ # per-stack design docs (the vulnerability catalogs)
├── dynast-bench/ # the dynast-bench CLI + scorer (Bun/TS)
└── vulnerable-apps/ # the 19 apps - each a separated, self-contained folder
├── _template/ # skeleton; copy it to start a new app
├── fastapi/ golang/ nextjs/ nestjs/ springboot/
└── rails/ wordpress/ php/ jsp/ aspnet/ ...
## Executar aplicações (CLI `dynast-bench`)
A CLI é a forma mais fácil de conduzir a suíte — ela faz health-gates nos boots, arbitra
as portas partilhadas e fala `--json` para que um harness de scanner possa consumi-la.
Requer [Bun](https://bun.sh) 1.2+ e Docker.```bash
make install # compile the CLI + link it into ~/.bun/bin
# (BIN_DIR=/somewhere/else to pick the dir)
dynast-bench list # every app: vulns, PoCs, near-misses, what's up
dynast-bench vulns nextjs # the planted bugs as a checklist, one title each
# (--full · --near · --ids for a coverage diff)
dynast-bench start nextjs # build + boot, wait for health, print the URL
dynast-bench verify nextjs # run the ground-truth PoCs (expect all exploitable)
dynast-bench validate nextjs # twin loop: vuln all-exploitable → safe all-fixed
dynast-bench status # variant, mode, target, health
dynast-bench stop --all # stop everything
dynast-bench clean --all --images --yes # reclaim containers, volumes, networks, images
dynast-bench start nextjs --variant safe # the patched twin (false-positive run)
dynast-bench start --count 5 --parallel # 5 apps at once, one port each + a summary table
dynast-bench start --all --solo --parallel # whole fleet, one image + port each
dynast-bench run nextjs -- my-scanner --url '$TARGET' # start → scan → stop
Referência completa: dynast-bench/README.md.
Tudo vive numa fatia tranquila do intervalo efémero, para que a suíte não entre em conflito com a multidão habitual de 3000/8000/8080/5432 - e cada app possui uma porta fixa, para que um URL signifique sempre a mesma app, sozinha ou num lote de cinco:
| intervalo | o quê |
|---|---|
13311–13339 | a app em teste - o URL para onde apontas o scanner, uma porta por app na ordem de list (aspnet 13311, fastapi 13312, … nextjs 13322) |
13340–13484 | os sidecars dessa app (mailpit, phpMyAdmin, Jenkins, Prometheus, …), 5 por app |
13500–13599 | pool de realocação |
dynast-bench list é o mapa. Se algo já estiver a escutar numa porta que uma app
possui, dynast-bench start deixa-a em paz e publica esse serviço a partir do
pool de realocação em vez disso, e depois imprime (e reporta via --json) o URL real.
Nada se liga alguma vez para além de 127.0.0.1. dynast-bench doctor mostra quais as portas
das apps estão livres; os alvos make não realocam e publicam os predefinidos do compose
(13311+) uma app de cada vez, respeitando DYNAST_PORT=<n>; --port N fixa-a.
Os Makefiles continuam a ser o contrato de baixo nível e funcionam de forma autónoma:```bash make list # show all apps (a [solo] tag = has a single-image build) make run APP=nextjs # start via compose (app + datastores) make verify APP=nextjs # run its ground-truth PoCs (expect all exploitable) make validate APP=nextjs # full twin loop: vuln all-pass -> safe all-fixed make down APP=nextjs # stop it make solo APP=nextjs # run as ONE self-contained image - no compose needed make solo-down APP=nextjs # stop the standalone image
Duas formas de executar cada aplicação:
- **Compose** (`make run`) - a topologia multi-serviço canônica que o ground
truth tem como alvo (app + Postgres/Redis/etc. como contêineres separados).
- **Standalone** (`make solo`) - uma imagem autocontida por aplicação
(`vuln/Dockerfile.standalone`) com os datastores + um sink SSRF interno
embutido, de modo que `docker build` + `docker run` funcionem sem compose. O
comportamento e os PoCs são idênticos (os nomes dos serviços do compose são
aliased para `127.0.0.1`).
A raiz permanece deliberadamente pequena: este README, o guia de design, as
ferramentas compartilhadas e as aplicações. Tudo o que é operacional para uma
determinada aplicação fica dentro da própria pasta dessa aplicação.
## Anatomia por aplicação
Cada aplicação em `vulnerable-apps/` tem a mesma estrutura:```
vulnerable-apps/<stack>/
├── README.md # LOUD banner + run notes
├── Makefile # up · reset · safe · verify · score · diff (uniform interface)
├── vuln/ # the vulnerable variant - this is what you scan by default
│ ├── docker-compose.yml # independent; binds 127.0.0.1 only
│ ├── app/ # application source; the planted bugs live here
│ └── db/seed.sql # seed incl. a cross-tenant user + a weak default cred
├── safe/ # the patched twin - same app, every planted bug fixed
│ ├── docker-compose.yml
│ ├── app/
│ └── db/seed.sql
└── ground-truth/ # the answer key - see "Ground truth" below
├── VULNERABILITIES.yaml # every planted bug
├── SURFACE.yaml # every endpoint the app exposes
├── verify/ # one runnable PoC per bug
└── expected/ # optional golden normalized findings
Cada aplicação inclui duas pastas de variantes separadas, em vez de ramos git ou ficheiros de patch:
vuln/ - a aplicação com todos os bugs introduzidos. O alvo predefinido; aquilo para onde
um scanner aponta.safe/ - a mesma aplicação com todos os bugs introduzidos corrigidos e nada mais
alterado (consultas parametrizadas, saída escapada, authz adicionado, desserializadores
seguros, …).diff -ru vulnerable-apps/<stack>/vuln vulnerable-apps/<stack>/safe é a verdade de base. Deve
tocar exatamente nas linhas nomeadas em ground-truth/VULNERABILITIES.yaml e em nada
mais. Analisar a variante safe/ mede a taxa de falsos positivos de uma ferramenta:
cada descoberta ali é um alarme falso, porque o par está limpo por construção.
Como o contexto de build Docker de cada variante é a sua própria pasta (vuln/ ou
safe/), o ground-truth/ da aplicação fica fora de cada contexto de build e
não pode ser incorporado numa imagem - a chave de respostas não pode vazar para a
aplicação em execução, por construção.
ground-truth/)Duas chaves de respostas, porque há duas perguntas. VULNERABILITIES.yaml diz
o que está errado na aplicação; SURFACE.yaml diz o que está presente de todo.
VULNERABILITIES.yaml regista uma entrada por bug introduzido:```yaml
`SURFACE.yaml` regista uma entrada por **operação que a aplicação expõe**, vulnerável
e benigna igualmente - é o denominador para [cobertura de
endpoints](#endpoint-coverage):```yaml
operations:
- id: posts.search
kind: http # http | graphql | ws | llm | net
method: GET
path: /api/posts/search
params: [q]
discovery: js-runtime # same crawl tiers as the answer key
reachability: user
vulns: [SQLI-001] # omit when the operation is benign
- id: graphql.mutation.update-post
kind: graphql # the op BEHIND POST /graphql, which is its own entry
op: updatePost
graphql_kind: mutation
via: graphql.transport
discovery: static-html
Operações benignas estão lá de propósito: um catálogo apenas com as rotas vulneráveis mediria a cobertura do gabarito, e não a do aplicativo.
verify/ contém um PoC executável por bug — ele sai com 0 contra vuln/ e
diferente de zero contra safe/. Essa é a definição executável de "o bug é
real (e de fato corrigido no gêmeo)."
O runner compartilhado (dynast-bench/tools/poc-runner.sh) adiciona um terceiro
resultado que o código de saída não consegue carregar sozinho: o harness não
conseguiu executar. Aponte a suíte para uma porta onde nada está escutando e
uma boa metade dos PoCs de qualquer aplicativo sai com 1 — indistinguível de
uma correção genuína. Então o runner faz uma verificação de saúde do alvo antes
de acreditar em uma rejeição, aplica um prazo por PoC e falha em ambas as pernas
em caso de timeout, ferramenta ausente ou alvo que parou de responder. "A suíte
não conseguiu executar" nunca é registrado como "a vulnerabilidade foi corrigida".
dynast-bench/, Bun/TypeScript)Um único toolchain, usado por todos os aplicativos, para que resultados entre stacks sejam comparáveis:
dynast-bench.ts — a CLI: iniciar/parar/redefinir/limpar, gate de saúde,
arbitragem de porta, verificação de PoC, pontuação, --json para harnesses.src/schema/ — tipos + validadores para os dois formatos de relatório
(findings/v1, endpoints/v1) e as duas chaves de resposta
(VULNERABILITIES.yaml, SURFACE.yaml), a tabela de famílias CWE usada para
crédito parcial e os normalizadores de caminho/rota/operação pelos quais ambos
os lados de uma comparação passam.src/normalize/ — adaptadores que convertem saída bruta de scanners (OWASP
ZAP, SARIF de Semgrep/CodeQL/Snyk, nuclei, Burp XML, nmap XML) nesse formato. O
formato é auto-detectado, então score aceita saída nativa diretamente.src/scorer/ — compara achados com a chave de resposta e emite
precisão / recall / F1, recall por dificuldade / severidade / alcançabilidade /
taint / CWE, uma pontuação de discriminação sobre os quase-acertos e uma
proporção de duplicatas (ruído). Ao lado disso, uma trilha de
cobertura de endpoints avalia quanto do aplicativo uma
execução realmente alcançou e divide cada erro em "nunca encontrou o endpoint"
vs "encontrou, mas errou o bug".```
dynast-bench verify # run the app's ground-truth PoCs
dynast-bench score findings.json # findings → P/R/F1 + per-dimension recall
dynast-bench coverage endpoints.json # endpoint discovery → how much was reached
dynast-bench surface # the operation checklist a crawl is graded on
dynast-bench diff # the vuln↔safe delta vs the answer key
dynast-bench check --all # CI gate: schema · anchors · diff scope · binds[`examples/`](https://github.com/j3ssie/dynast-bench/blob/main/examples) contém ficheiros que pode pontuar imediatamente - uma execução de descobertas, uma
execução de falsos positivos, três rastreios de endpoints e dois modelos em branco, cada um documentado
com os números que produz:```bash
dynast-bench score nextjs examples/findings.json --safe examples/findings-safe.json
dynast-bench coverage nextjs examples/endpoints.json --findings examples/findings.json
Referência completa - o esquema de descobertas, os níveis de correspondência, cada métrica:
dynast-bench/README.md.
make up # docker compose up the vuln/ variant (127.0.0.1 only), wait for health make reset # down -v && up → fresh, byte-identical state make safe # bring up the safe/ variant instead (for false-positive runs) make verify # run every ground-truth PoC; expect all PASS against vuln/ make score FINDINGS=f.json # grade a scanner's findings → P/R/F1 make diff # the vuln↔safe delta, cross-checked against the answer key make check # CI gate: schema · anchors · diff scope · PoCs · 127.0.0.1 binds
## Os aplicativos
| App | Stack | DB | Serviços extras | Documento de design |
|------------|--------------------------------|------------|----------------------|------------|
| fastapi | Python / FastAPI + Jinja2 | Postgres | MinIO, Mailpit | [fastapi.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/fastapi.md) |
| golang | Go / chi | Postgres | Prometheus, Grafana | [golang.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/golang.md) |
| gin | Go / Gin | Postgres | chromium, ImageMagick (na imagem) | [README](https://github.com/j3ssie/dynast-bench/blob/main/vulnerable-apps/gin/README.md) |
| nextjs | Node / Next.js 15 | Postgres | Redis, Mailpit | [nextjs.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/nextjs.md) |
| nestjs | Node / NestJS + Handlebars | Postgres | Redis, nginx | [nestjs.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/nestjs.md) |
| springboot | Java / Spring Boot + Thymeleaf | Postgres | Jenkins, Prometheus | [springboot.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/springboot.md) |
| rails | Ruby / Rails 7.2 | Postgres | MinIO, nginx | [rails.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/rails.md) |
| wordpress | PHP / WordPress + plugin | MySQL | nginx, Mailpit | [wordpress.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/wordpress.md) |
| php | PHP / LAMP procedural | MySQL | phpMyAdmin, Mailpit | [php.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/php.md) |
| jsp | Java / JSP + Servlets (Tomcat) | Postgres | Mailpit | [jsp.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/jsp.md) |
| aspnet | C# / ASP.NET Core Razor Pages | SQL Server | Mailpit | [aspnet.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/aspnet.md) |
Mais três aplicativos **somente API** (GraphQL, WebSocket, Swagger/OpenAPI), uma
frota de **intervalo de rede** para scanners de host/porta e dois aplicativos **LLM**:
| App | Stack | DB | Serviços extras | Documento de design |
|------------|---------------------------------------------|-------------------|--------------------------------------|------------|
| llmchat | Python / FastAPI + LangChain (chatbot RAG) | Postgres+pgvector | Redis, Ollama, svc interno | [llmchat.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/llmchat.md) |
| llmagent | Node / Fastify + Vercel AI SDK + MCP (agente)| Postgres | Redis, Ollama, partner-MCP, svc interno | [llmagent.md](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/llmagent.md) |
Ambos os aplicativos LLM executam um **modelo local** por meio de um contêiner Ollama
somente interno (`gemma3:1b` para chat, `qwen2.5:1.5b` para chamada de ferramentas) — sem chave de API, sem egresso,
sem custo por execução — e incluem um backend com script `LLM_BACKEND=stub` para que os
PoCs de verdade fundamental permaneçam determinísticos contra um modelo estocástico.
Consulte [`benchmark-plans/README.md`](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/README.md) para o
modelo de domínio compartilhado, a matriz de cobertura do OWASP Top 10 e os
princípios de design do benchmark (quase-acertos, distância de taint, bugs somente de lógica).
## Primeiros passos```bash
make install # once: puts `dynast-bench` on your PATH
dynast-bench doctor # docker reachable? which ports are taken?
dynast-bench start fastapi # boots the vuln/ variant, waits for health
dynast-bench verify fastapi # sanity-check: every planted bug's PoC PASSes
# ...point your scanner/agent at $(dynast-bench target fastapi), collect findings.json...
dynast-bench start fastapi --variant safe # patched twin → measures false positives
dynast-bench reset fastapi # restore fresh, re-seeded state
dynast-bench clean --all --yes # give the disk back
Ou execute uma aplicação diretamente com o seu Makefile:```bash cd vulnerable-apps/fastapi make up # vuln/ variant on 127.0.0.1 make verify # every planted bug's PoC PASSes make safe # the patched twin make reset # fresh state
## Status
- **19 aplicações trazem um gabarito completo**: 549 vulnerabilidades plantadas, 146
quase-acertos, 546 PoCs e um `Dockerfile.standalone` cada (`--solo`).
`dynast-bench list` imprime a tabela ao vivo.
- **`nextjs` é a implementação de referência** - construída e validada de ponta a ponta
(35 vulns + 15 quase-acertos). `make validate APP=nextjs` prova que cada PoC é
explorável em `vuln/` e corrigida em `safe/`; `make solo APP=nextjs` a executa a partir de
uma única imagem. Copie seus padrões.
- **CLI `dynast-bench` - construída**: executa, verifica, pontua e limpa qualquer aplicação, em
modo compose ou imagem única, com `--json` para harnesses.
- **O avaliador - construído** (`dynast-bench/src/`): saída do scanner → achados normalizados
→ precisão/recall/F1, recall por dificuldade, uma pontuação de discriminação sobre os
quase-acertos, e trilhas separadas de descoberta (rede) e canal de injeção (LLM).
- **Cobertura de endpoints - construída**: um `SURFACE.yaml` por aplicação (~600 operações em
toda a frota) avaliando quanto da aplicação uma execução realmente alcançou, e dividindo
cada falha em "nunca encontrou o endpoint" vs "encontrou, mas errou o bug".
- Invariantes por aplicação sobre todos os 19 gabaritos e catálogos de superfície rodam em
`make test`; `dynast-bench check --all` é o gate de CI.
## Pontuando uma ferramenta```bash
dynast-bench start nextjs --json | jq -r .target # boot, get the URL
zap-baseline.py -t http://127.0.0.1:13311 -J zap.json # scan
dynast-bench score nextjs zap.json --full # grade it
# measure false positives properly: scan the patched twin too
dynast-bench start nextjs --variant safe
my-scanner --url http://127.0.0.1:13311 --out safe.json
dynast-bench score nextjs zap.json --safe safe.json
score lê um ficheiro findings/v1 ou saída nativa de ZAP / SARIF / nuclei / Burp / nmap
— o formato é detetado automaticamente. Comece por examples/ se estiver a
integrar uma ferramenta: examples/template-findings.json é um esqueleto em branco com todos os
campos, e examples/findings.json é um ficheiro funcional que pode pontuar agora mesmo.
A descoberta de endpoints é avaliada separadamente, contra o SURFACE.yaml de cada aplicação:```bash
dynast-bench coverage nextjs endpoints.json --findings findings.json
É isso que separa uma **falha de descoberta** (nunca alcançou o endpoint — corrija o
crawler) de uma **falha de análise** (alcançou-o, não reportou — corrija o scanner).
Consulte [`dynast-bench/README.md`](https://github.com/j3ssie/dynast-bench/blob/main/dynast-bench/README.md#scoring) para o esquema, os
níveis de correspondência e cada métrica.
### Como ler o relatório (`Leg │ Precision │ Recall │ F1`)
Uma **leg** é uma execução de scan contra um estado-alvo:
| Leg | O que é |
|---|---|
| `blackbox` | sem credenciais — a visão do atacante não autenticado |
| `credentialed` | o mesmo alvo com os logins semeados injetados, para que a superfície autenticada (IDOR, escalonamento de privilégios) seja alcançável |
| `safe-twin` | o gémeo corrigido (`--safe`), uma linha de base de falsos positivos — idealmente não encontra nada |
Todas as três correm de `0.0`–`1.0`, e para todas as três **quanto mais alto, melhor** (`1.0` é perfeito):
| Métrica | Fórmula | Melhor | Leia-se como |
|---|---|---|---|
| **Precision** | `TP / (TP + FP)` | ↑ mais alto | de tudo o que foi reportado, quanto era real. `0.38` = ~38% dos achados eram genuínos, o resto ruído. Alto = poucos falsos alarmes. |
| **Recall** | `TP / (TP + FN)` | ↑ mais alto | dos bugs efetivamente plantados, quantos foram encontrados. `0.73` = 8 de 11. Alto = poucas falhas. |
| **F1** | `2 × P × R / (P + R)` | ↑ mais alto | média harmónica dos dois — o número principal de "qualidade geral". Só é alto quando ambos o são, por isso penaliza ser ruidoso *e* falhar bugs. |
A única inversão: na **leg `safe-twin` não há nada real para encontrar**, por isso
cada achado aí é um falso alarme — menos é melhor, e um relatório vazio é
a pontuação perfeita.
## Cobertura de endpoints
O Recall diz-lhe quantos bugs uma ferramenta encontrou. Não lhe pode dizer **porquê** falhou
o resto — e as duas razões exigem correções opostas:
| Falha | Significado | O que corrigir |
|---|---|---|
| **falha de descoberta** | nunca alcançou o endpoint que contém o bug | o crawler |
| **falha de análise** | alcançou o endpoint, não reportou o bug | a análise |
Distingui-las exige uma segunda entrada: os endpoints que a sua ferramenta diz ter encontrado.
Isso é `endpoints/v1`, pontuado contra o `SURFACE.yaml` de cada app.```bash
dynast-bench surface nextjs # the checklist a crawl is graded on
dynast-bench coverage nextjs endpoints.json # how much did it reach?
dynast-bench coverage nextjs endpoints.json --findings findings.json # ...and why not the rest
dynast-bench score nextjs findings.json --endpoints endpoints.json # both in one report
Um crawler que lê HTML e executa JS, mas nunca conclui um fluxo de várias etapas:``` operations 62.5% 25 of 40 detection 25.0% of the bugs on operations it reached misses: 11 never reached the operation · 18 reached it and did not report
static-html 6/6 100.0% js-static 5/5 100.0% js-runtime 11/19 57.9% interaction 3/5 60.0% flow 0/5 0.0%
A repartição por camadas é a parte útil: 100% em `static-html` e 0% em `flow` é
um problema de descoberta, não um problema do scanner, e essas leituras são idênticas em
um único número de recall.
Duas regras mantêm o número honesto:
- **Transporte não é operação.** Um `POST /graphql` não exercita as 25
operações GraphQL por trás dele; um handshake WebSocket não exercita os seus
eventos; um `POST /api/runs` não exercita as ferramentas de um agente. Alcançar um URL
e exercitar o que vive lá são pontuados separadamente.
- **Telemetria ausente não produz nenhum rasto**, nunca `0%`. "Não medimos
isto" e "não alcançou nada" são afirmações opostas sobre uma ferramenta.
Endpoints reportados que não correspondem a nada custam precisão, mas nunca reduzem a cobertura,
portanto, pulverizar uma wordlist não é uma forma de pontuar mais alto. Modelo completo:
[`dynast-bench/README.md#endpoint-coverage`](https://github.com/j3ssie/dynast-bench/blob/main/dynast-bench/README.md#endpoint-coverage).
## Licença
`dynast-bench` é feito com ♥ por [@j3ssie](https://github.com/j3ssie) para avaliar
**Vigolium** e **Gimora** (um agente autónomo de segurança ofensiva), e é
disponibilizado sob a [licença MIT](https://github.com/j3ssie/dynast-bench/blob/main/LICENSE).
| llmagent | Node / Fastify + AI SDK + MCP | Postgres | 29 | 8 | plan |
| llmchat | Python / FastAPI + LangChain RAG | Postgres+pgvector | 30 | 9 | plan |
| nestjs | Node / NestJS + Handlebars | Postgres | 23 | 6 | plan |
| network | Faixa de rede multi-host simulada | frota mista | 32 | 5 | plan |
| nextjs | Node / Next.js 15 (impl. de referência) | Postgres | 35 | 15 | plan |
| php | PHP / LAMP procedural | MySQL | 21 | 5 | plan |
| rails | Ruby / Rails 7.2 | Postgres | 26 | 6 | plan |
| springboot | Java / Spring Boot + Thymeleaf | Postgres | 30 | 4 | plan |
| swagger | OpenAPI / Swagger UI + carregamento de spec | Postgres | 19 | 5 | plan |
| websocket | Node 22 / ws + Socket.IO em tempo real | Postgres | 28 | 6 | plan |
| weirdproxy | nginx + Apache + Traefik sobre uma origem | nenhum | 16 | 4 | plan |
| wordpress | PHP / WordPress + plugin personalizado | MySQL | 28 | 6 | plan |
| 942 |
| 14 |
| 14 |
| Condições de corrida / TOCTOU | 362 | 14 | 14 |
| Open redirect | 601 | 14 | 14 |
| Enumeração de usuários e recursos (discrepância observável na resposta) | 204, 598 | 13 | 12 |
| Injeção de código · SSTI · linguagem de expressão | 94, 917, 1059, 1336 | 11 | 10 |
| Criptografia e aleatoriedade fracas · transporte em texto claro | 295, 319, 327, 330, 338 | 11 | 6 |
| Falhas de redefinição de senha e recuperação de conta | 184, 640 | 9 | 9 |
| Upload de arquivo irrestrito / inseguro | 434 | 9 | 9 |
| CSRF (incl. sequestro de WebSocket entre sites) | 352 | 8 | 8 |
| Prompt injection e abuso de ferramentas LLM (direto · indireto · RAG) | 1427 | 7 | 2 |
| XXE / entidade externa XML | 611 | 5 | 5 |
| Supply chain e integridade (atualizações não assinadas, dependências vulneráveis) | 494, 1035 | 2 | 2 |
| Exposição de rede insegura (binding, configuração incorreta de serviço) | 1327 | 2 | 1 |
| Logging insuficiente / injeção em logs | 117 | 1 | 1 |