Assay
CVE Recall Benchmark

We measure our own miss rate.

222 real vulnerabilities from the GitHub Advisory Database. Assay scanned each project's code as it existed before the fix. The headline is the blind number: recall on the 164 CVEs no rule was learned from. Below: what it caught, what it missed, and how the number moves as the system learns from its misses. Most benchmarks hide the misses. The misses are the roadmap.

Last run 2026-07-20 · re-runs automatically every Monday · raw results TSV

32.9%
of 164 blind holdout CVEs caught at the exact vulnerable code — advisories no rule was learned from, strictest definition
+6.1
points of blind recall in the last teaching cycle — 26.8% → 32.9% on the same 164-CVE holdout
39.6%
across all 222 scored CVEs — includes the 58 CVEs the rules were taught from, so it overstates blind performance

The blind-holdout number, run by run

Every benchmark run appends a row to a public TSV. Each point below is a real run, on the holdout slice only; the labels are what changed between them. The holdout itself grows as the corpus expands — a bigger denominator moves the number down without the detectors getting worse.

0%20%40%60%18.2%2026-07-19npm, 11 CVEs18.2%2026-07-20npm, 11 CVEs28.6%2026-07-20npm + pip, 98 CVEs41.8%2026-07-20npm + pip, 67 holdout26.8%2026-07-20npm + pip, 164 holdout32.9%2026-07-20npm + pip, 164 holdout
18.2%First measurement
18.2%Pruned 22 broken rules — number held, proving they were pure noise
28.6%Corpus expanded to 98 CVEs
41.8%Loop turn 1: harvested the 70 misses, learned 58 rules
26.8%Corpus expanded to 164 holdout CVEs — older, more CWE-diverse. A bigger denominator, not a regression
32.9%Loop turn 2: harvested 120 misses, learned 39 rules (375 candidates rejected)

Three ways to count a catch. We lead with the strictest.

A finding only counts as a true catch if it lands on the code the fix later changed, and stops firing once the fix is applied. Looser definitions inflate the number; we show all three — computed on the 164 blind holdout CVEs.

Discriminative32.9%

A finding inside the fix-changed region, from a detector that goes quiet after the fix. It flagged the actual vulnerability.

Strict70.7%

A finding inside the fix-changed region, but the detector still fires on the patched code too. Right place, weaker signal.

Lenient97.6%

A finding anywhere in a changed file. This is the number most tools would report. We don't lead with it.

The fine print, before you ask

  • Taught CVEs are labeled, not hidden. 58 of the 222 CVEs fed the rule catalog, so they can't count as blind catches. On those, recall is 58.6%. The headline 32.9% is the 164 untouched holdout CVEs only.
  • Generalization: demonstrated, but small. Rules learned from one CVE have caught 5 holdout CVEs they were never taught from:
    • CVE-2026-9495 Access-control flaw in @koa/router (CWE-284), via turn-2 rule learned from a different advisory
    • CVE-2026-48054 Code injection (CWE-94), via turn-2 rule learned from a different advisory
    • GHSA-72r4-9c5j-mj57 pnpm patch-removal path traversal (CWE-22), via turn-1 rule learned from a different advisory
    • GHSA-534h-c3cw-v3h9 Nuxt vite-node world-connectable IPC socket (CWE-276), via three corroborating turn-1 rules
    • CVE-2026-59731 Authorization via non-canonical URL paths (CWE-647), via turn-1 rule learned from a different advisory
    Three additional raw catches all traced to one over-firing rule (noise-capped in 50 files, worst 461 fires in one file) and are not counted. The stronger prospective test — advisories that didn't exist when the rules were learned — runs every Monday. That's the number to watch.
  • Noisy detectors don't get credit. A detector firing more than 20 times in one file is dropped for that file. Without this cap the benchmark reports a meaningless 100%. We know because our first version did.

What we miss today

CWE-79Cross-site scripting8
CWE-863Incorrect authorization6
CWE-407Algorithmic complexity6
CWE-1333Regex denial of service5
CWE-770Unbounded resource allocation5
CWE-400Uncontrolled resource consumption5
CWE-284Improper access control5
CWE-693Protection mechanism failure5
CWE-22Path traversal5
CWE-352Cross-site request forgery4
CWE-20Improper input validation4
CWE-78OS command injection4

Holdout misses from the latest run. This list is the work queue for the next teaching cycle.

What the loop learned last cycle

  • Denial of service via unbounded recursion in nested-object parsing and property traversal
  • Prototype pollution through user-controlled keys in nested object assignment
  • Authentication bypass when polluted prototypes shadow built-in object methods
  • Memory exhaustion from unvalidated request-body size limits in HTTP handling
  • Credential injection via unsanitized configuration objects passed to HTTP clients
  • Resource exhaustion via attacker-controlled deep nesting in form serialization

Each rule was extracted from a confirmed CVE fix, then validated before promotion. 375 candidates failed validation and were discarded.

How it works

  1. 1.Pull recent security advisories with linked fix commits from the GitHub Advisory Database (npm and pip).
  2. 2.For each advisory, fetch every changed file twice: as it existed before the fix (vulnerable) and after (patched).
  3. 3.Scan the vulnerable version with deterministic detectors only — pattern rules and learned rules, no LLM at scan time.
  4. 4.Score a catch only when a finding overlaps the code the fix changed, and the detector goes quiet on the patched version.
  5. 5.Feed the misses back: harvest their fix diffs into new candidate rules, validate, promote survivors, re-run.

Methodology & limitations

Dataset
GitHub Advisory Database — npm and pip advisories with linked fix commits
Corpus
222 CVEs scored (npm + pip, up to 500 discovered per ecosystem)
Split
164 blind holdout · 58 taught (rules were learned from their fixes)
Detectors
437 promoted learned rules + claimless checks, deterministic only — no LLM at scan time
Run date
2026-07-20
Rule admission
Candidate rules must fire on their source vulnerability and pass synthetic precision/recall validation before promotion; 375 of 422 candidates were rejected last cycle
Noise cap
A detector firing more than 20 times in one file is dropped for that file — its region hits carry no localization signal
Known limitations
  • JavaScript/TypeScript and Python files only
  • A catch requires line-region overlap with the fix; a correct finding elsewhere in the repo counts as a miss
  • Holdout CVEs predate the rules (retrospective). The prospective test — advisories published after the rules were learned — is the weekly Monday run
  • Numbers are internal, not independently validated

Raw data: results-recall.tsv — one row per run, appended automatically.

Run the same detectors on your own repo.

npx tryassay assess .