Un benchmark DAST di app intenzionalmente vulnerabili con chiavi di risposta ground-truth per valutare gli scanner.
⚠️ Questo repository contiene applicazioni DELIBERATAMENTE INSICURE. Esistono
solo per fare benchmark di strumenti di sicurezza - scanner DAST, motori SAST e
agenti di sicurezza LLM. Ogni app si lega a 127.0.0.1, mostra un banner BEN VISIBILE e non
contiene dati reali. Non distribuire mai nulla di tutto ciò su una rete pubblica.
Una suite di 19 app intenzionalmente vulnerabili, una per stack, ciascuna con un
ground truth documentato e verificabile a macchina. L'obiettivo è misurare quanto bene
uno scanner o un agente (a) trova i bug piantati, (b) ignora il codice quasi-identico ma sicuro
che si trova proprio accanto a loro, e (c) non allucina risultati sul
gemello corretto.
Le app a colpo d'occhio
19 app · 549 vulnerabilità piantate · 146 quasi-errori · 546 PoC eseguibili ·
594 endpoint catalogati. Ogni app si avvia su 127.0.0.1:13311, include una
coppia gemella vuln/+safe/ e una build --solo a immagine singola.
stampa questa tabella in tempo reale; stampa il suo catalogo endpoint.
Sidecar extra per stack (Mailpit, MinIO, Redis, Jenkins, Prometheus, Ollama, …)
sono elencati in Le app più sotto.
I documenti di design per stack vivono in benchmark-plans/ -
inizia da lì per il catalogo completo delle vulnerabilità di ogni app. Questo README è la
guida operativa: come è organizzato il repository e come eseguire e valutare un'app.
Classi di vulnerabilità coperte
Ogni bug piantato porta un CWE e una categoria OWASP. Raggruppati per classe - ogni
bug contato una volta, sotto il suo CWE primario - i bug piantati si suddividono
approssimativamente come segue (rollup rigenerato l'ultima volta a 480 bug; i conteggi per-app sopra sono attuali):
Classe
CWE
Bug
App
Esposizione di dati sensibili (errori, log, endpoint di debug, backup, sorgente)
200, 209, 489, 524, 532, 538, 540, 548
39
16
Credenziali predefinite / hardcoded / trapelate
321, 522, 798, 1104, 1392
38
18
Autorizzazione mancante o rotta (BFLA, verticale + orizzontale)
Per categoria OWASP (Top 10 2021 per le app web, API Top 10 2023 dove l'app è
solo API):
OWASP
Bug
OWASP API
Bug
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
Due tracce non-web affiancano queste: l'app network pianta 32 risultati a livello
host/porta e servizio per scanner di rete, e le due app LLM
(llmchat, llmagent) piantano bug di prompt-injection, abuso di strumenti e avvelenamento RAG
valutati su una traccia separata di canali di iniezione.
Ogni bug è anche etichettato con una difficoltà di rilevamento (118 E, 68 E-M, 202
M, 61 M-H, 100 H), una distanza di taint (351 in-file, 83 cross-file, 87
cross-service, 28 config) e una raggiungibilità (368 pre-auth, 181
user), così il recall può essere suddiviso lungo ciascuno di questi assi invece di
essere riportato come un unico numero. I cataloghi per-app vivono in
benchmark-plans/.
Struttura del repository```
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/ ...
root@kitploit:~
## Esecuzione delle app (CLI `dynast-bench`)
La CLI è il modo più semplice per gestire la suite: verifica l'avvio tramite health-check, arbitra
le porte condivise e parla `--json` così un harness di scanner può consumarla.
Richiede [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
Tutto vive in una porzione tranquilla dell'intervallo effimero, così la suite non
entra in conflitto con il solito gruppo 3000/8000/8080/5432 - e ogni app possiede una porta fissa,
così un URL significa sempre la stessa app, da sola o in un gruppo di cinque:
intervallo
cosa
13311–13339
l'app sotto test - l'URL a cui punti uno scanner, una porta per app nell'ordine di list (aspnet 13311, fastapi 13312, … nextjs 13322)
13340–13484
i sidecar di quell'app (mailpit, phpMyAdmin, Jenkins, Prometheus, …), 5 ciascuno
13500–13599
pool di rilocazione
dynast-bench list è la mappa. Se qualcosa sta già ascoltando su una porta posseduta da un'app,
dynast-bench start la lascia stare e pubblica quel singolo servizio dal
pool di rilocazione, poi stampa (e riporta con --json) l'URL reale.
Nulla si lega mai oltre 127.0.0.1. dynast-bench doctor mostra quali porte
delle app sono libere; i target make non rilocano e pubblicano i default di compose
(13311+) un'app alla volta, rispettando DYNAST_PORT=<n>; --port N la fissa.
Esecuzione delle app (Makefile di primo livello)
I Makefile restano il contratto di basso livello e funzionano in modo autonomo:```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
shorthands: make run-nextjs make validate-nextjs
root@kitploit:~
Due modi per eseguire ogni app:
- **Compose** (`make run`) - la topologia multi-servizio canonica che i ground
truth target (app + Postgres/Redis/ecc. come container separati).
- **Standalone** (`make solo`) - un'immagine autonoma per app
(`vuln/Dockerfile.standalone`) con i datastore + un sink SSRF interno
incorporati, così `docker build` + `docker run` funziona senza compose. Il
comportamento e i PoC sono identici (i nomi dei servizi compose sono alias di `127.0.0.1`).
La root resta volutamente piccola: questo README, la guida di design, il
tooling condiviso e le app. Tutto ciò che è operativo per una data app è dentro
la cartella di quella stessa app.
## Anatomia per app
Ogni app sotto `vulnerable-apps/` ha la stessa identica struttura:```
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
Il gemello vuln/safe
Ogni app include due cartelle di varianti separate invece di branch git o
file di patch:
vuln/ - l'app con ogni bug inserito. Il target predefinito; ciò a cui
uno scanner punta.
safe/ - la stessa app con ogni bug inserito corretto e nient'altro
modificato (query parametrizzate, output escapato, authz aggiunta,
deserializzatori sicuri, …).
diff -ru vulnerable-apps/<stack>/vuln vulnerable-apps/<stack>/safe è la verità di base. Deve
toccare esattamente le righe indicate in ground-truth/VULNERABILITIES.yaml e nient'
altro. Scansionare la variante safe/ misura il tasso di falsi positivi di uno strumento:
ogni rilevamento lì è un falso allarme, perché il gemello è pulito per costruzione.
Poiché il contesto di build Docker di ogni variante è la sua stessa cartella (vuln/ o
safe/), la ground-truth/ dell'app si trova fuori da ogni contesto di build e
non può essere incorporata in un'immagine - la chiave delle risposte non può trapelare
nell'app in esecuzione, per costruzione.
Verità di base (ground-truth/)
Due chiavi delle risposte, perché ci sono due domande. VULNERABILITIES.yaml dice
cosa c'è di sbagliato nell'app; SURFACE.yaml dice cosa c'è presente in assoluto.
VULNERABILITIES.yaml registra una voce per ogni bug inserito:```yaml
id: SQLI-001
variant_paths: # same relative path in both variants
vuln: vuln/app/routes/search.py
safe: safe/app/routes/search.py
symbol: search_posts
route: "GET /posts/search?q="
cwe: CWE-89
owasp: "A03:2021-Injection"
severity: high # info | low | medium | high | critical
difficulty: E # E | E-M | M | M-H | H (detection difficulty)
taint: in-file # in-file | cross-file | cross-service
reachability: pre-auth # pre-auth | user | admin
near_miss: SAFE-SQLI-001 # id of the safe twin planted nearby
match: # machine anchors for the scorer (generated)
http: { method: GET, path: "/posts/search", params: [q] }
file: { path: vuln/app/routes/search.py, symbol: search_posts, lines: [18, 24] }
markers: [GLOBEX-CONFIDENTIAL-MARKER-7f3a]
poc: ground-truth/verify/sqli_001.sh
root@kitploit:~
`SURFACE.yaml` registra una voce per ogni **operazione esposta dall'app**, vulnerabile
o benigna che sia - è il denominatore per la [copertura degli
endpoint](#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
Le operazioni benigne sono lì apposta: un catalogo con solo le rotte
vulnerabili misurerebbe la copertura della chiave di risposta, non quella
dell'app.
verify/ contiene un PoC eseguibile per ogni bug - termina con 0 contro vuln/ e
non-zero contro safe/. Questa è la definizione eseguibile di "il bug è
reale (ed effettivamente corretto nel gemello)."
Il runner condiviso (dynast-bench/tools/poc-runner.sh) aggiunge un terzo esito che
il codice di uscita non può trasportare da solo: l'harness non ha potuto essere eseguita. Punta la suite
su una porta su cui nulla è in ascolto e una buona metà dei PoC di qualsiasi app esce con 1 -
indistinguibile da una correzione genuina. Quindi il runner esegue un health-check sul target
prima di credere a un rifiuto, applica una scadenza per singolo PoC e fa fallire entrambe le gambe
in caso di timeout, strumento mancante o target che smette di rispondere. "La suite non ha potuto
essere eseguita" non viene mai registrato come "la vulnerabilità è corretta".
Una sola toolchain, usata da ogni app, così i risultati tra stack diversi sono confrontabili:
dynast-bench.ts - la CLI: start/stop/reset/clean, gating di salute, arbitraggio
delle porte, verifica dei PoC, punteggio, --json per le harness.
src/schema/ - tipi + validatori per i due formati di report
(findings/v1, endpoints/v1) e le due chiavi di risposta
(VULNERABILITIES.yaml, SURFACE.yaml), la tabella delle famiglie CWE usata per
il credito parziale, e i normalizzatori di path/route/operazione attraverso cui passano
entrambi i lati di un confronto.
src/normalize/ - adattatori che convertono l'output grezzo degli scanner (OWASP ZAP, SARIF
da Semgrep/CodeQL/Snyk, nuclei, Burp XML, nmap XML) in quel formato. Il
formato viene rilevato automaticamente, quindi score accetta direttamente l'output nativo.
src/scorer/ - confronta i finding con la chiave di risposta ed emette
precision / recall / F1, recall per difficoltà / severità / raggiungibilità /
taint / CWE, un punteggio di discriminazione sui quasi-mancati, e un rapporto
di duplicati (rumore). Accanto a questo, una traccia di copertura degli endpoint
valuta quanto dell'app una run ha effettivamente raggiunto e suddivide ogni mancato rilevamento in
"endpoint mai trovato" vs "trovato, ma bug mancato".```
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
root@kitploit:~
[`examples/`](https://github.com/j3ssie/dynast-bench/blob/HEAD/examples/) contiene file che puoi valutare immediatamente: un run di findings, un run di falsi positivi, tre tracce di endpoint e due modelli vuoti, ciascuno documentato con i numeri che 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
Full reference - lo schema di finding, i tier di matching, ogni metrica:
dynast-bench/README.md.
Interfaccia Makefile uniforme (identica in ogni app)```
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
root@kitploit:~
## Le app
| App | Stack | DB | Servizi extra | Documento di progettazione |
|------------|--------------------------------|------------|----------------------|------------|
| fastapi | Python / FastAPI + Jinja2 | Postgres | MinIO, Mailpit | [fastapi.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/fastapi.md) |
| golang | Go / chi | Postgres | Prometheus, Grafana | [golang.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/golang.md) |
| gin | Go / Gin | Postgres | chromium, ImageMagick (in-image) | [README](https://github.com/j3ssie/dynast-bench/blob/HEAD/vulnerable-apps/gin/README.md) |
| nextjs | Node / Next.js 15 | Postgres | Redis, Mailpit | [nextjs.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/nextjs.md) |
| nestjs | Node / NestJS + Handlebars | Postgres | Redis, nginx | [nestjs.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/nestjs.md) |
| springboot | Java / Spring Boot + Thymeleaf | Postgres | Jenkins, Prometheus | [springboot.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/springboot.md) |
| rails | Ruby / Rails 7.2 | Postgres | MinIO, nginx | [rails.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/rails.md) |
| wordpress | PHP / WordPress + plugin | MySQL | nginx, Mailpit | [wordpress.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/wordpress.md) |
| php | PHP / procedurale LAMP | MySQL | phpMyAdmin, Mailpit | [php.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/php.md) |
| jsp | Java / JSP + Servlets (Tomcat) | Postgres | Mailpit | [jsp.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/jsp.md) |
| aspnet | C# / ASP.NET Core Razor Pages | SQL Server | Mailpit | [aspnet.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/aspnet.md) |
Più tre app **solo API** (GraphQL, WebSocket, Swagger/OpenAPI), una
flotta **network-range** per scanner di host/porte, e due app **LLM**:
| App | Stack | DB | Servizi extra | Documento di progettazione |
|------------|---------------------------------------------|-------------------|--------------------------------------|------------|
| llmchat | Python / FastAPI + LangChain (chatbot RAG) | Postgres+pgvector | Redis, Ollama, servizio interno | [llmchat.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/llmchat.md) |
| llmagent | Node / Fastify + Vercel AI SDK + MCP (agente)| Postgres | Redis, Ollama, partner-MCP, servizio interno | [llmagent.md](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/llmagent.md) |
Entrambe le app LLM eseguono un **modello locale** tramite un container Ollama
solo interno (`gemma3:1b` per la chat, `qwen2.5:1.5b` per il tool calling) - nessuna API key, nessun egress,
nessun costo per esecuzione - e includono un backend scriptato `LLM_BACKEND=stub` così che i
PoC ground-truth rimangano deterministici contro un modello stocastico.
Vedi [`benchmark-plans/README.md`](https://github.com/j3ssie/dynast-bench/blob/HEAD/benchmark-plans/README.md) per il
modello di dominio condiviso, la matrice di copertura OWASP-Top-10, e i principi
di progettazione del benchmark (near-misses, taint distance, bug solo logici).
## Per iniziare```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
Oppure esegui direttamente un'app tramite il suo 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
root@kitploit:~
## Stato
- **19 app con una chiave di risposta completa**: 549 vulnerabilità inserite, 146
quasi-mancati, 546 PoC e un `Dockerfile.standalone` ciascuna (`--solo`).
`dynast-bench list` stampa la tabella aggiornata.
- **`nextjs` è l'implementazione di riferimento** - costruita e validata end-to-end
(35 vulnerabilità + 15 quasi-mancati). `make validate APP=nextjs` dimostra che ogni PoC
è sfruttabile su `vuln/` e corretto su `safe/`; `make solo APP=nextjs` lo esegue da
un'unica immagine. Copia i suoi pattern.
- **CLI `dynast-bench` - costruita**: esegue, verifica, valuta e pulisce qualsiasi app, in
modalità compose o immagine singola, con `--json` per gli harness.
- **Lo scorer - costruito** (`dynast-bench/src/`): output dello scanner → risultati
normalizzati → precision/recall/F1, recall per difficoltà, un punteggio di discriminazione sui
quasi-mancati, e tracce separate per discovery (rete) e canale di injection (LLM).
- **Copertura degli endpoint - costruita**: un `SURFACE.yaml` per app (~600 operazioni in
tutta la flotta) che valuta quanto dell'app un'esecuzione ha effettivamente raggiunto, e suddivide
ogni mancato in "endpoint mai trovato" vs "trovato, ma bug mancato".
- Gli invarianti per-app su tutte le 19 chiavi di risposta e i cataloghi di superficie vengono eseguiti in
`make test`; `dynast-bench check --all` è il gate CI.
## Valutazione di uno strumento```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 legge un file findings/v1 oppure un output nativo di ZAP / SARIF / nuclei / Burp / nmap: il formato viene rilevato automaticamente. Inizia da examples/ se stai integrando uno strumento: examples/template-findings.json è uno scheletro vuoto con tutti i campi, mentre examples/findings.json è un file funzionante che puoi valutare subito.
La scoperta degli endpoint viene valutata separatamente, rispetto al SURFACE.yaml di ciascuna app:```bash
dynast-bench coverage nextjs endpoints.json --findings findings.json
root@kitploit:~
Questo è ciò che distingue un **mancato rilevamento di scoperta** (non ha mai raggiunto l'endpoint - correggi il crawler) da un **mancato rilevamento di analisi** (lo ha raggiunto, ma non lo ha segnalato - correggi lo scanner).
Consulta [`dynast-bench/README.md`](https://github.com/j3ssie/dynast-bench/blob/HEAD/dynast-bench/README.md#scoring) per lo schema, i livelli di corrispondenza e ogni metrica.
### Lettura del report (`Leg │ Precision │ Recall │ F1`)
Una **leg** è una singola scansione eseguita contro uno stato del target:
| Leg | Cosa rappresenta |
|---|---|
| `blackbox` | nessuna credenziale - la vista dell'attaccante non autenticato |
| `credentialed` | stesso target con i login seed iniettati, così la superficie autenticata (IDOR, escalation dei privilegi) è raggiungibile |
| `safe-twin` | il gemello patchato (`--safe`), una baseline di falsi positivi - idealmente non trova nulla |
Tutte e tre vanno da `0.0` a `1.0`, e per tutte e tre **più alto è meglio** (`1.0` è perfetto):
| Metrica | Formula | Migliore | Significato |
|---|---|---|---|
| **Precision** | `TP / (TP + FP)` | ↑ più alto | di tutto ciò che viene segnalato, quanto era reale. `0.38` = ~38% dei risultati erano genuini, il resto rumore. Alto = pochi falsi allarmi. |
| **Recall** | `TP / (TP + FN)` | ↑ più alto | dei bug effettivamente piantati, quanti sono stati trovati. `0.73` = 8 su 11. Alto = pochi mancati rilevamenti. |
| **F1** | `2 × P × R / (P + R)` | ↑ più alto | media armonica dei due - il numero "qualità complessiva" principale. È alto solo quando lo sono entrambi, quindi penalizza sia l'essere rumorosi *che* il mancare i bug. |
L'unica inversione: sulla **leg `safe-twin` non c'è nulla di reale da trovare**, quindi ogni risultato lì è un falso allarme - meno è meglio, e un report vuoto è il punteggio perfetto.
## Copertura degli endpoint
Il Recall ti dice quanti bug ha trovato uno strumento. Non può dirti **perché** ha mancato il resto - e i due motivi richiedono correzioni opposte:
| Mancato rilevamento | Significato | Cosa correggere |
|---|---|---|
| **mancato rilevamento di scoperta** | non ha mai raggiunto l'endpoint che contiene il bug | il crawler |
| **mancato rilevamento di analisi** | ha raggiunto l'endpoint, ma non ha segnalato il bug | l'analisi |
Distinguerli richiede un secondo input: gli endpoint che il tuo strumento dichiara di aver trovato.
Questo è `endpoints/v1`, valutato rispetto al `SURFACE.yaml` di ciascuna 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 crawler che legge HTML ed esegue JS ma non completa mai un flusso multi-step:```
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
La ripartizione per livelli è la parte utile: 100% su `static-html` e 0% su `flow` è
un problema di scoperta, non un problema dello scanner, e questi valori si leggono in modo identico in
un singolo numero di recall.
Due regole mantengono onesto il numero:
- **Il trasporto non è operazione.** Un `POST /graphql` non esercita le 25
operazioni GraphQL che ci sono dietro; un handshake WebSocket non esercita i suoi
eventi; un `POST /api/runs` non esercita gli strumenti di un agente. Raggiungere un URL
ed esercitare ciò che vi risiede sono valutati separatamente.
- **La telemetria mancante non produce alcuna traccia**, mai `0%`. "Non abbiamo misurato
questo" e "non ha raggiunto nulla" sono affermazioni opposte su uno strumento.
Gli endpoint segnalati che non corrispondono a nulla costano precisione ma non riducono mai la copertura,
quindi sparare una wordlist non è un modo per ottenere un punteggio più alto. Modello completo:
[`dynast-bench/README.md#endpoint-coverage`](https://github.com/j3ssie/dynast-bench/blob/HEAD/dynast-bench/README.md#endpoint-coverage).
## Licenza
`dynast-bench` è realizzato con ♥ da [@j3ssie](https://github.com/j3ssie) per fare benchmark di
**Vigolium** e **Gimora** (un agente autonomo di sicurezza offensiva), ed è
rilasciato sotto la [licenza MIT](https://github.com/j3ssie/dynast-bench/blob/HEAD/LICENSE).