
Un benchmark DAST de aplicaciones intencionalmente vulnerables con claves de respuesta de referencia para puntuar escáneres.
Un benchmark DAST de aplicaciones intencionalmente vulnerables con claves de respuesta de referencia (ground truth) para puntuar escáneres.
⚠️ Este repositorio contiene aplicaciones DELIBERADAMENTE INSEGURAS. Existen únicamente para evaluar herramientas de seguridad: escáneres DAST, motores SAST y agentes de seguridad LLM. Cada aplicación se vincula a
127.0.0.1, muestra un banner LLAMATIVO y no contiene datos reales. Nunca despliegue nada de esto en una red pública.
Un conjunto de 19 aplicaciones intencionalmente vulnerables, una por stack, cada una con una ground truth documentada y verificable por máquina. El objetivo es medir qué tan bien un escáner o agente (a) encuentra los bugs plantados, (b) ignora el código seguro de casi-acierto (near-miss) que está justo al lado, y (c) no alucina hallazgos en el gemelo parcheado.
19 aplicaciones · 549 vulnerabilidades plantadas · 146 casi-aciertos · 546 PoCs ejecutables ·
594 endpoints catalogados. Cada aplicación arranca en 127.0.0.1:13311, incluye un
par gemelo vuln/+safe/ y una compilación --solo de imagen única.
imprime esta tabla en vivo; imprime su catálogo de endpoints.
dynast-bench listdynast-bench surface <app>| App | Stack | Almacén de datos | Vulns | Casi-acierto | 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 adicionales por stack (Mailpit, MinIO, Redis, Jenkins, Prometheus, Ollama, …) se enumeran en Las aplicaciones más abajo.
Los documentos de diseño por stack viven en benchmark-plans/:
comience allí para ver el catálogo completo de vulnerabilidades de cada aplicación. Este README es la
guía operativa: cómo está organizado el repositorio y cómo ejecutar y puntuar una aplicación.
Cada bug plantado lleva un CWE y una categoría OWASP. Agrupados por clase: cada bug se cuenta una vez, bajo su CWE principal: los bugs plantados se desglosan aproximadamente como sigue (el rollup se regeneró por última vez con 480 bugs; los recuentos por aplicación anteriores están actualizados):
| Clase | CWEs | Bugs | Apps |
|---|---|---|---|
| Exposición de datos sensibles (errores, logs, endpoints de depuración, copias de seguridad, código fuente) | 200, 209, 489, 524, 532, 538, 540, 548 | 39 | 16 |
| Credenciales por defecto / hardcodeadas / filtradas | 321, 522, 798, 1104, 1392 | 38 | 18 |
| Autorización ausente o rota (BFLA, vertical + horizontal) | 269, 284, 285, 668, 862, 863 | 37 | 18 |
| Cross-site scripting (reflejado · almacenado · DOM) | 79 | 28 | 16 |
| Bypass de autenticación · sesión débil · verificación JWT | 287, 288, 290, 306, 347, 384, 613, 614, 1385 | 28 | 13 |
| Inyección SQL (incl. de segundo orden, ORDER BY, NoSQL) | 89, 943 | 27 | 17 |
| Conflictos de interpretación proxy/parser (confusión de rutas, confianza en cabeceras) | 345, 348, 349, 436, 441, 693, 697, 706, 807 | 27 | 10 |
| SSRF (incl. ciego, cadenas de redirección, sinks solo internos) | 918 | 20 | 17 |
| IDOR / BOLA (clave de objeto controlada por el usuario) | 639 | 19 | 17 |
| Path traversal · LFI/RFI · zip slip | 22, 98 | 19 | 16 |
| Mass assignment / sobre-publicación · contaminación de prototipos | 915, 1321 | 18 | 16 |
| Fuerza bruta · falta de rate limiting · agotamiento de recursos | 307, 400, 406, 674, 770 | 17 | 11 |
| Abuso de lógica de negocio, precios y cuotas | 625, 840 | 15 | 14 |
| Inyección de comandos / argumentos del SO | 78 | 14 | 12 |
| Deserialización insegura (pickle · PHP · Java · YAML) | 470, 502 | 14 | 11 |
Por categoría OWASP (Top 10 2021 para las aplicaciones web, API Top 10 2023 donde la aplicación es solo 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 |
Dos pistas no web se sitúan junto a estas: la aplicación network planta 32 hallazgos
de host/puerto y de nivel de servicio para escáneres de red, y las dos aplicaciones LLM
(llmchat, llmagent) plantan bugs de prompt-injection, abuso de herramientas y envenenamiento RAG
puntuados en una pista separada de canal de inyección.
Cada bug también está etiquetado con una dificultad de detección (118 E, 68 E-M, 202
M, 61 M-H, 100 H), una distancia de taint (351 in-file, 83 cross-file, 87
cross-service, 28 config) y una alcanzabilidad (368 pre-auth, 181
user), de modo que el recall puede desglosarse a lo largo de cada uno de esos ejes en lugar de
informarse como un único número. Los catálogos por aplicación viven en
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/ ...
## Ejecutar aplicaciones (CLI `dynast-bench`)
La CLI es la forma más sencilla de manejar el conjunto de pruebas: verifica el estado de los arranques, arbitra
los puertos compartidos y habla `--json` para que un arnés de escáner pueda consumirlo.
Requiere [Bun](https://bun.sh) 1.2+ y 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
Referencia completa: dynast-bench/README.md.
Todo vive en un segmento tranquilo del rango efímero, para que el conjunto no pelee con la multitud habitual de 3000/8000/8080/5432 - y cada aplicación posee un puerto fijo, de modo que una URL siempre significa la misma aplicación, sola o en un lote de cinco:
| rango | qué |
|---|---|
13311–13339 | la aplicación bajo prueba - la URL a la que apuntas con un escáner, un puerto por aplicación en orden de list (aspnet 13311, fastapi 13312, … nextjs 13322) |
13340–13484 | los sidecars de esa aplicación (mailpit, phpMyAdmin, Jenkins, Prometheus, …), 5 por cada una |
13500–13599 | grupo de reubicación |
dynast-bench list es el mapa. Si algo ya está escuchando en un puerto que una aplicación
posee, dynast-bench start lo deja en paz y publica ese servicio desde el
grupo de reubicación en su lugar, y luego imprime (y reporta con --json) la URL real.
Nada se vincula jamás más allá de 127.0.0.1. dynast-bench doctor muestra qué puertos
de aplicaciones están libres; los objetivos de make no reubican y publican los valores
predeterminados de compose (13311+) una aplicación a la vez, respetando DYNAST_PORT=<n>; --port N lo fija.
Los Makefiles siguen siendo el contrato de bajo nivel y funcionan de forma independiente:```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
Dos formas de ejecutar cada aplicación:
- **Compose** (`make run`) - la topología multi-servicio canónica que los
objetivos de ground truth (app + Postgres/Redis/etc. como contenedores separados).
- **Standalone** (`make solo`) - una imagen autocontenida por aplicación
(`vuln/Dockerfile.standalone`) con los almacenes de datos + un sumidero SSRF interno
integrado, de modo que `docker build` + `docker run` funcionan sin compose. El comportamiento
y los PoCs son idénticos (los nombres de los servicios de compose están aliasados a `127.0.0.1`).
La raíz se mantiene deliberadamente pequeña: este README, la guía de diseño, las herramientas
compartidas y las aplicaciones. Todo lo operativo para una aplicación determinada está dentro de la
carpeta propia de esa aplicación.
## Anatomía por aplicación
Cada aplicación bajo `vulnerable-apps/` tiene la misma estructura:```
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 aplicación incluye dos carpetas de variantes separadas en lugar de ramas de git o archivos de parche:
vuln/ - la aplicación con cada error plantado. El objetivo predeterminado; a lo que apunta un escáner.safe/ - la misma aplicación con cada error plantado corregido y nada más cambiado (consultas parametrizadas, salida escapada, autorización añadida, deserializadores seguros, …).diff -ru vulnerable-apps/<stack>/vuln vulnerable-apps/<stack>/safe es la verdad absoluta. Debe tocar exactamente las líneas nombradas en ground-truth/VULNERABILITIES.yaml y nada más. Escanear la variante safe/ mide la tasa de falsos positivos de una herramienta: cada hallazgo allí es una falsa alarma, porque el gemelo está limpio por construcción.
Debido a que el contexto de compilación de Docker de cada variante es su propia carpeta (vuln/ o safe/), el ground-truth/ de la aplicación queda fuera de cada contexto de compilación y no puede integrarse en una imagen - la clave de respuestas no puede filtrarse a la aplicación en ejecución, por construcción.
ground-truth/)Dos claves de respuestas, porque hay dos preguntas. VULNERABILITIES.yaml indica qué está mal en la aplicación; SURFACE.yaml indica qué existe en absoluto.
VULNERABILITIES.yaml registra una entrada por error plantado:```yaml
`SURFACE.yaml` registra una entrada por **operación que expone la aplicación**, tanto vulnerable como benigna: es el denominador para la [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
Las operaciones benignas están ahí a propósito: un catálogo solo con las rutas vulnerables mediría la cobertura de la clave de respuestas, no la de la app.
verify/ contiene un PoC ejecutable por cada bug: termina con 0 contra vuln/ y
distinto de cero contra safe/. Esa es la definición ejecutable de "el bug es
real (y de hecho está corregido en su gemelo)".
El runner compartido (dynast-bench/tools/poc-runner.sh) añade un tercer resultado
que el código de salida no puede transmitir por sí solo: el harness no pudo ejecutarse.
Si apuntas el conjunto a un puerto donde nada está escuchando, una buena mitad de los PoCs
de cualquier app termina con 1, indistinguible de una corrección genuina. Por eso el runner
hace una comprobación de salud del objetivo antes de creer en un rechazo, aplica un plazo
por PoC y falla ambas ramas ante un timeout, una herramienta ausente o un objetivo que dejó
de responder. "El conjunto no pudo ejecutarse" nunca se registra como "la vulnerabilidad
está corregida".
dynast-bench/, Bun/TypeScript)Un único toolchain, usado por todas las apps, para que los resultados entre stacks sean comparables:
dynast-bench.ts - la CLI: iniciar/detener/reiniciar/limpiar, compuerta de salud,
arbitraje de puertos, verificación de PoCs, puntuación, --json para harnesses.src/schema/ - tipos y validadores para los dos formatos de informe
(findings/v1, endpoints/v1) y las dos claves de respuestas
(VULNERABILITIES.yaml, SURFACE.yaml), la tabla de familias CWE usada para el
crédito parcial, y los normalizadores de ruta/route/operación por los que pasan
ambos lados de una comparación.src/normalize/ - adaptadores que convierten la salida cruda de escáneres (OWASP ZAP, SARIF
de Semgrep/CodeQL/Snyk, nuclei, Burp XML, nmap XML) a ese formato. El
formato se detecta automáticamente, así que score acepta salida nativa directamente.src/scorer/ - compara los hallazgos con la clave de respuestas y emite
precisión / recall / F1, recall por dificultad / severidad / alcanzabilidad /
taint / CWE, una puntuación de discriminación sobre los casi-aciertos, y una
proporción de duplicados (ruido). Junto a ello, una pista de cobertura de endpoints
califica cuánto de la app alcanzó realmente una ejecución y divide cada fallo en
"nunca encontró el endpoint" vs "lo encontró, pero falló el 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) contiene archivos que puedes puntuar de inmediato: una ejecución de hallazgos, una ejecución de falsos positivos, tres trazas de endpoints y dos plantillas en blanco, cada una documentada con los números que produce:```bash
dynast-bench score nextjs examples/findings.json --safe examples/findings-safe.json
dynast-bench coverage nextjs examples/endpoints.json --findings examples/findings.json
Referencia completa: el esquema de hallazgos, los niveles de coincidencia, 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
## Las aplicaciones
| App | Stack | DB | Servicios adicionales | Documento de diseño |
|------------|--------------------------------|------------|----------------------|------------|
| 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 (en la imagen) | [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) |
Más tres aplicaciones **solo API** (GraphQL, WebSocket, Swagger/OpenAPI), una
flota de **rango de red** para escáneres de host/puertos, y dos aplicaciones **LLM**:
| App | Stack | DB | Servicios adicionales | Documento de diseño |
|------------|---------------------------------------------|-------------------|--------------------------------------|------------|
| 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) |
Ambas aplicaciones LLM ejecutan un **modelo local** mediante un contenedor Ollama
de solo acceso interno (`gemma3:1b` para chat, `qwen2.5:1.5b` para llamadas a herramientas) - sin clave API, sin salida de red,
sin coste por ejecución - e incluyen un backend con script `LLM_BACKEND=stub` para que
los PoCs de referencia sigan siendo deterministas frente a un modelo estocástico.
Consulta [`benchmark-plans/README.md`](https://github.com/j3ssie/dynast-bench/blob/main/benchmark-plans/README.md) para el
modelo de dominio compartido, la matriz de cobertura OWASP-Top-10 y los
principios de diseño del benchmark (casi aciertos, distancia de taint, errores solo de lógica).
## Primeros pasos```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
O bien ejecuta una aplicación directamente con su 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
## Estado
- **19 aplicaciones incluyen una clave de respuestas completa**: 549 vulnerabilidades plantadas, 146
casi-aciertos, 546 PoCs y un `Dockerfile.standalone` cada una (`--solo`).
`dynast-bench list` imprime la tabla en vivo.
- **`nextjs` es la implementación de referencia**: construida y validada de extremo a extremo
(35 vulns + 15 casi-aciertos). `make validate APP=nextjs` demuestra que cada PoC
es explotable en `vuln/` y está corregido en `safe/`; `make solo APP=nextjs` lo ejecuta desde
una sola imagen. Copia sus patrones.
- **CLI `dynast-bench`: construida**: ejecuta, verifica, puntúa y limpia cualquier aplicación, en
modo compose o de imagen única, con `--json` para harnesses.
- **El puntuador: construido** (`dynast-bench/src/`): salida del escáner → hallazgos normalizados
→ precisión/recall/F1, recall por dificultad, una puntuación de discriminación sobre los
casi-aciertos, y pistas separadas de descubrimiento (red) y canal de inyección (LLM).
- **Cobertura de endpoints: construida**: un `SURFACE.yaml` por aplicación (~600 operaciones en
toda la flota) que califica cuánto de la aplicación alcanzó realmente una ejecución, y divide
cada fallo en "nunca encontró el endpoint" vs "lo encontró, pero falló el bug".
- Las invariantes por aplicación sobre las 19 claves de respuestas y los catálogos de superficie se ejecutan en
`make test`; `dynast-bench check --all` es la puerta de CI.
## Puntuando una herramienta```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 lee un archivo findings/v1 o la salida nativa de ZAP / SARIF / nuclei / Burp / nmap
— el formato se detecta automáticamente. Comienza desde examples/ si estás
integrando una herramienta: examples/template-findings.json es un esqueleto en blanco con todos los
campos, y examples/findings.json es un archivo funcional que puedes puntuar ahora mismo.
El descubrimiento de endpoints se evalúa por separado, contra el SURFACE.yaml de cada aplicación:```bash
dynast-bench coverage nextjs endpoints.json --findings findings.json
Eso es lo que separa un **fallo de descubrimiento** (nunca llegó al endpoint - arregla el
crawler) de un **fallo de análisis** (llegó a él, no lo reportó - arregla el escáner).
Consulta [`dynast-bench/README.md`](https://github.com/j3ssie/dynast-bench/blob/main/dynast-bench/README.md#scoring) para ver el esquema, los
niveles de coincidencia y cada métrica.
### Lectura del informe (`Leg │ Precision │ Recall │ F1`)
Una **leg** es una ejecución de escaneo contra un estado objetivo:
| Leg | Qué es |
|---|---|
| `blackbox` | sin credenciales - la vista del atacante no autenticado |
| `credentialed` | el mismo objetivo con los inicios de sesión sembrados inyectados, de modo que la superficie autenticada (IDOR, escalada de privilegios) es alcanzable |
| `safe-twin` | el gemelo parcheado (`--safe`), una línea base de falsos positivos - idealmente no encuentra nada |
Las tres se ejecutan en `0.0`–`1.0`, y para las tres **más alto es mejor** (`1.0` es perfecto):
| Métrica | Fórmula | Mejor | Se lee como |
|---|---|---|---|
| **Precision** | `TP / (TP + FP)` | ↑ más alto | de todo lo reportado, cuánto era real. `0.38` = ~38% de los hallazgos eran genuinos, el resto ruido. Alto = pocas falsas alarmas. |
| **Recall** | `TP / (TP + FN)` | ↑ más alto | de los bugs realmente plantados, cuántos se encontraron. `0.73` = 8 de 11. Alto = pocos fallos. |
| **F1** | `2 × P × R / (P + R)` | ↑ más alto | media armónica de ambos - el número principal de "calidad general". Solo es alto cuando ambos lo son, por lo que penaliza ser ruidoso *y* fallar bugs. |
La única inversión: en la **leg `safe-twin` no hay nada real que encontrar**, así que
cada hallazgo allí es una falsa alarma - menos es mejor, y un informe vacío es
la puntuación perfecta.
## Cobertura de endpoints
El recall te dice cuántos bugs encontró una herramienta. No puede decirte **por qué** falló
el resto - y las dos razones necesitan correcciones opuestas:
| Fallo | Significado | Qué arreglar |
|---|---|---|
| **fallo de descubrimiento** | nunca llegó al endpoint que contiene el bug | el crawler |
| **fallo de análisis** | llegó al endpoint, no reportó el bug | el análisis |
Distinguirlos requiere una segunda entrada: los endpoints que tu herramienta dice haber encontrado.
Eso es `endpoints/v1`, puntuado contra el `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
Un rastreador que lee HTML y ejecuta JS pero nunca completa un flujo de varios pasos:``` 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%
El desglose por niveles es la parte útil: 100% en `static-html` y 0% en `flow` es
un problema de descubrimiento, no un problema del escáner, y ambos se leen de forma
idéntica en un único número de recall.
Dos reglas mantienen honesto el número:
- **El transporte no es la operación.** Un `POST /graphql` no ejercita las 25
operaciones GraphQL que hay detrás; un handshake de WebSocket no ejercita sus
eventos; un `POST /api/runs` no ejercita las herramientas de un agente. Alcanzar
una URL y ejercitar lo que vive allí se puntúan por separado.
- **La telemetría ausente no produce ningún track en absoluto**, nunca `0%`. "No
medimos esto" y "no alcanzó nada" son afirmaciones opuestas sobre una herramienta.
Los endpoints reportados que no coinciden con nada cuestan precisión pero nunca
reducen la cobertura, así que rociar una wordlist no es una forma de puntuar más
alto. Modelo completo:
[`dynast-bench/README.md#endpoint-coverage`](https://github.com/j3ssie/dynast-bench/blob/main/dynast-bench/README.md#endpoint-coverage).
## Licencia
`dynast-bench` está hecho con ♥ por [@j3ssie](https://github.com/j3ssie) para
evaluar **Vigolium** y **Gimora** (un agente autónomo de seguridad ofensiva), y se
publica bajo la [licencia 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 | Rango de red multi-host simulado | flota mixta | 32 | 5 | plan |
| nextjs | Node / Next.js 15 (implementación de referencia) | 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 + carga de especificaciones | Postgres | 19 | 5 | plan |
| websocket | Node 22 / ws + Socket.IO en tiempo real | Postgres | 28 | 6 | plan |
| weirdproxy | nginx + Apache + Traefik sobre un único origen | ninguno | 16 | 4 | plan |
| wordpress | PHP / WordPress + plugin personalizado | MySQL | 28 | 6 | plan |
| Configuración incorrecta de CORS | 942 | 14 | 14 |
| Condiciones de carrera / TOCTOU | 362 | 14 | 14 |
| Open redirect | 601 | 14 | 14 |
| Enumeración de usuarios y recursos (discrepancia observable en la respuesta) | 204, 598 | 13 | 12 |
| Inyección de código · SSTI · lenguaje de expresiones | 94, 917, 1059, 1336 | 11 | 10 |
| Criptografía y aleatoriedad débiles · transporte en claro | 295, 319, 327, 330, 338 | 11 | 6 |
| Fallos de restablecimiento de contraseña y recuperación de cuenta | 184, 640 | 9 | 9 |
| Subida de archivos sin restricciones / insegura | 434 | 9 | 9 |
| CSRF (incl. secuestro de WebSocket entre sitios) | 352 | 8 | 8 |
| Prompt injection y abuso de herramientas LLM (directo · indirecto · RAG) | 1427 | 7 | 2 |
| XXE / entidad externa XML | 611 | 5 | 5 |
| Cadena de suministro e integridad (actualizaciones sin firmar, dependencias vulnerables) | 494, 1035 | 2 | 2 |
| Exposición de red insegura (binding, configuración incorrecta del servicio) | 1327 | 2 | 1 |
| Registro insuficiente / inyección en logs | 117 | 1 | 1 |