Someone else's corpus,someone else's scorer.
1762 labelled vulnerabilities across 62 real repositories, none of them labelled here and none of them scored here. This page is the whole result, including the parts that are unflattering — the four families still near zero, the 0 repositories where nothing labelled was found, and the disclosure that the number stopped being blind three rounds ago.
secaudit · Tier 0 only (--no-deps --no-scanners, no LLM) — no network, no API key, no paid plan.
- 61.2F3 (recall × 9)
- 61.6F2
- 0.656precision
- 0.607recall
- 1070true positives
- 561false positives
- 692missed
- 248true negatives
The same scorer reports a stricter reading of this identical run: F3 57.0, recall 0.5623, counting 141 further labels as missed on the same 1070 true positives. The honest range for this run is 57.0 – 61.2. micro is quoted because it is the aggregate the benchmark's own baselines are quoted in; 57.0 is the figure for anything else.
Disclosureread this before quoting the number
This number is not blind, and it has not been for three rounds.
The first two runs were blind: the engine had never seen this corpus, and 12.5 is what it scored on code it had not read. Every run after that was measured once the benchmark's own false negatives had been read and the missing rules written.
Every rule added is of a kind any SAST ships — weak PRNG for tokens, cookie flags, CSRF exemptions, a committed fallback signing key, open redirect, credentials in logs — so no rule is fitted to a particular fixture. The selection, however, was corpus-informed three times, and the advantage compounds. The gap between 12.5 and 61.2 is the size of it. 12.5 is the conservative figure for an unseen codebase, and the honest successor to this measurement is a benchmark this repository has not seen.
Run history30 runs, one corpus
The cost of each round, alongside its score.
Same corpus, same scorer, same clone. Each round re-scores the previous engine on the current checkout first and reproduces its committed figures digit for digit, which is what makes the movement attributable to the engine rather than to the corpus.
| Round | F3 | F2 | precision | recall | TP / FP / FN | |
|---|---|---|---|---|---|---|
| 2026-08-12 | the first run — blind, on a corpus the engine had not read | 12.5 | 13.5 | 0.4072 | 0.1158 | 204 / 297 / 1558 |
| 2026-08-13 | targeted at the first run's diagnosis, still blind to the rest of the corpus | 13.3 | 14.4 | 0.3925 | 0.1243 | 219 / 339 / 1543 |
| 2026-08-13 | the configuration and crypto-hygiene round, corpus-informed | 24.6 | 26.1 | 0.5037 | 0.2327 | 410 / 404 / 1352 |
| 2026-08-13 | the first structural round — authorization and ReDoS | 26.0 | 27.5 | 0.5106 | 0.2463 | 434 / 416 / 1328 |
| 2026-08-13 | structural analyses: rate limiting, uploads, mass assignment | 30.9 | 32.5 | 0.5405 | 0.2951 | 520 / 442 / 1242 |
| 2026-08-13 | rate-limit rule narrowed (F3 31.5) | 31.5 | 33.0 | 0.5419 | 0.3008 | 530 / 448 / 1232 |
| 2026-08-16 | language families, vendored assets, template XSS, Python SQL | 35.8 | 37.7 | 0.6601 | 0.3405 | 600 / 309 / 1162 |
| 2026-08-16 | vendored-asset filter | 35.9 | 38.0 | 0.7075 | 0.3405 | 600 / 248 / 1162 |
| 2026-08-16 | JavaScript round: ReDoS front end, prototype-pollution rewrite, published build output | 35.9 | 38.0 | 0.7075 | 0.3405 | 600 / 248 / 1162 |
| 2026-08-16 | prototype pollution: only a key the caller chose | 35.9 | 38.0 | 0.7084 | 0.3405 | 600 / 247 / 1162 |
| 2026-08-16 | pathlib: the sink library nothing modelled | 39.3 | 41.3 | 0.7086 | 0.3740 | 659 / 271 / 1103 |
| 2026-08-16 | req.url: the Node source nothing modelled | 39.3 | 41.3 | 0.7086 | 0.3740 | 659 / 271 / 1103 |
| 2026-08-17 | the 2026-08-16 round, re-measured: 273 false positives, not 271 | 39.2 | 41.3 | 0.7071 | 0.3740 | 659 / 273 / 1103 |
| 2026-08-17 | quadratic ReDoS, and the JavaScript pattern reported where it runs | 39.2 | 41.3 | 0.7041 | 0.3740 | 659 / 277 / 1103 |
| 2026-08-17 | prototype pollution: the functions nobody could delimit, the callback's key, and the walk | 39.2 | 41.3 | 0.7033 | 0.3740 | 659 / 278 / 1103 |
| 2026-08-17 | import-resolved shell and vm receivers | 39.2 | 41.3 | 0.7033 | 0.3740 | 659 / 278 / 1103 |
| 2026-08-17 | Config + credentials | 41.6 | 43.5 | 0.6718 | 0.3995 | 704 / 344 / 1058 |
| 2026-08-18 | Nine rule families the corpus had none for | 59.5 | 60.3 | 0.6760 | 0.5874 | 1035 / 496 / 727 |
| 2026-08-18 | Django views stop being public by default | 59.7 | 60.4 | 0.6695 | 0.5897 | 1039 / 513 / 723 |
| 2026-08-19 | The Secure cookie flag the HttpOnly rule was hiding | 60.6 | 61.1 | 0.6559 | 0.6005 | 1058 / 555 / 704 |
| 2026-08-19 | HTML built by concatenation, which is where the JavaScript XSS labels live | 60.5 | 60.9 | 0.6471 | 0.6005 | 1058 / 577 / 704 |
| 2026-08-19 | PHP gets an instrument, and the instrument finds three broken rules | 60.5 | 60.9 | 0.6471 | 0.6005 | 1058 / 577 / 704 |
| 2026-08-19 | PHP superglobal rules — the source is spelled in the sink | 60.5 | 60.9 | 0.6471 | 0.6005 | 1058 / 577 / 704 |
| 2026-08-19 | A suppression that reads the line, not the file | 60.5 | 60.9 | 0.6471 | 0.6005 | 1058 / 577 / 704 |
| 2026-08-19 | A vendored library does not arrive as one file | 60.5 | 61.1 | 0.6577 | 0.5999 | 1057 / 550 / 705 |
| 2026-08-19 | PHP taint — one assignment hop, one file | 60.5 | 61.1 | 0.6577 | 0.5999 | 1057 / 550 / 705 |
| 2026-08-19 | Three structural questions JavaScript could not be asked | 60.5 | 61.1 | 0.6577 | 0.5999 | 1057 / 550 / 705 |
| 2026-08-20 | A real application in the noise floor, and two defects it found | 60.5 | 61.1 | 0.6577 | 0.5999 | 1057 / 550 / 705 |
| 2026-08-20 | The framework's own login: a route with no handler to read | 61.1 | 61.6 | 0.6562 | 0.6067 | 1069 / 560 / 693 |
| 2026-08-20 | SSRF reaches the ordinary HTTP surface; four spellings of the debug switch | 61.2 | 61.6 | 0.6560 | 0.6073 | 1070 / 561 / 692 |
Precision rises alongside recall in every round after the first. The repository treats that as evidence of rules rather than curve fitting; a round in which precision falls is treated as a signal to narrow the rule rather than to keep it.
Baselines
Against what the benchmark itself publishes.
RealVuln publishes a best-reported figure per category. Three of these four rows are theirs; the bold one is this engine, measured by their scorer on their corpus. Every figure on this page keeps the scorer's own units — F3 on 0–100, precision and recall as fractions — so the rows stay comparable to the published table. The landing page states the same measurements as percentages throughout.
| Category | Best reported | F3 | precision | recall |
|---|---|---|---|---|
| Security-specialized | Kolega.Dev | 73.0 | 0.388 | 0.809 |
| General-purpose LLM | Claude Sonnet 4.6 | 51.7 | 0.785 | 0.498 |
| Rule-based SAST | Semgrep | 17.7 | 0.205 | 0.175 |
| Rule-based SAST + taint + structural | SecAudit Tier 0 | 61.2 | 0.6560 | 0.6073 |
Above rule-based SAST on both metrics, and well below both a general-purpose model and the purpose-built system — both of which were measured the way this one no longer is. The supportable claim is “above Semgrep on a corpus since read”, not “better than Semgrep”.
Where the recall goes
Every family, largest labelled pool first.
The ordering carries more information than the bars: the four families at the top hold more than half of every label in the corpus, and they are the four this tier scores worst on. A score is determined by the large pools rather than by the ones a scanner finds easy.
- other536 / 83165%
- sensitive_data_exposure61 / 14143%
- security_misconfiguration90 / 10883%
- xss49 / 9850%
- broken_access_control2 / 763%
- missing_auth7 / 749%
- sql_injection67 / 7194%
- hardcoded_credentials25 / 5248%
- command_injection39 / 4685%
- denial_of_service31 / 4470%
- open_redirect37 / 4092%
- path_traversal23 / 3959%
- ssrf16 / 3743%
- xxe32 / 3689%
- insecure_deserialization27 / 3479%
- code_injection28 / 3093%
- xpath_injection0 / 40%
- http_header_injection0 / 10%
The pattern classes are near their ceiling: command injection, open redirect, code injection and XXE all exceed 80%. The pools that determine the score are not pattern classes. Roughly 130 of the labels in sensitive_data_exposure and security_misconfiguration sit on a function definition, the same shape as broken_access_control and missing_auth. Those require the business-logic pass rather than a further rule, and no rule added here has reached them.
Per repository62 of 66
All of them, best first — including the ones that scored nothing.
Ranked by F3. Published in full rather than as a top five, because the top five on this project's own page was two rounds stale before anything compared it to the scorer output, and a leaderboard nobody reconciles flatters itself.
| Repository | F3 | precision | recall | TP | FP | FN |
|---|---|---|---|---|---|---|
| vc-codex-high-seeded-v2-marketplace-commerce-fastapi | 85.6 | 0.688 | 0.880 | 22 | 10 | 3 |
| vc-codex-high-seeded-v2-fintech-lending-fastapi | 81.6 | 0.727 | 0.828 | 24 | 9 | 5 |
| vc-codex-seeded-v2-logistics-dispatch-fastapi | 79.7 | 0.774 | 0.800 | 24 | 7 | 6 |
| vc-codex-high-seeded-v2-support-desk-fastapi | 77.5 | 0.688 | 0.786 | 22 | 10 | 6 |
| vc-codex-high-seeded-v2-logistics-dispatch-fastapi | 77.2 | 0.622 | 0.793 | 23 | 14 | 6 |
| vc-codex-high-seeded-v2-property-management-fastapi | 76.6 | 0.525 | 0.808 | 21 | 19 | 5 |
| vc-kimi-code-seeded-v2-property-management-fastapi | 76.4 | 0.742 | 0.767 | 23 | 8 | 7 |
| vc-codex-high-seeded-v2-hr-payroll-django | 76.0 | 0.760 | 0.760 | 19 | 6 | 6 |
| vc-kimi-code-seeded-v2-crm-saas-django | 73.8 | 0.714 | 0.741 | 20 | 8 | 7 |
| vc-kimi-code-seeded-v2-hr-payroll-django | 73.6 | 0.579 | 0.759 | 22 | 16 | 7 |
| vc-codex-seeded-v2-property-management-fastapi | 73.5 | 0.676 | 0.742 | 23 | 11 | 8 |
| vc-codex-high-seeded-v2-crm-saas-django | 73.1 | 0.543 | 0.760 | 19 | 16 | 6 |
| vc-codex-high-seeded-v2-education-lms-django | 73.1 | 0.543 | 0.760 | 19 | 16 | 6 |
| vc-claude-code-seeded-v2-logistics-dispatch-fastapi | 72.7 | 0.727 | 0.727 | 24 | 9 | 9 |
| intentionally-vulnerable-python-app | 72.5 | 0.833 | 0.714 | 5 | 1 | 2 |
| vc-codex-high-seeded-v2-legal-case-django | 71.7 | 0.692 | 0.720 | 18 | 8 | 7 |
| lets-be-bad-guys | 71.1 | 0.739 | 0.708 | 17 | 6 | 7 |
| vc-codex-seeded-v2-support-desk-fastapi | 70.7 | 0.778 | 0.700 | 21 | 6 | 9 |
| vc-kimi-code-seeded-v2-logistics-dispatch-fastapi | 70.7 | 0.688 | 0.710 | 22 | 10 | 9 |
| vc-kimi-code-seeded-v2-legal-case-django | 70.0 | 0.783 | 0.692 | 18 | 5 | 8 |
| vc-claude-code-seeded-v2-marketplace-commerce-fastapi | 69.8 | 0.815 | 0.688 | 22 | 5 | 10 |
| vc-claude-code-seeded-v2-legal-case-django | 69.6 | 0.595 | 0.710 | 22 | 15 | 9 |
| vc-kimi-code-seeded-v2-marketplace-commerce-fastapi | 69.3 | 0.613 | 0.704 | 19 | 12 | 8 |
| vfapi | 69.0 | 1.000 | 0.667 | 6 | 0 | 3 |
| vc-kimi-code-seeded-v2-support-desk-fastapi | 67.9 | 0.679 | 0.679 | 19 | 9 | 9 |
| vc-kimi-code-seeded-v2-healthcare-clinic-django | 67.8 | 0.800 | 0.667 | 20 | 5 | 10 |
| vc-claude-code-seeded-v2-support-desk-fastapi | 67.4 | 0.657 | 0.676 | 23 | 12 | 11 |
| vc-claude-code-seeded-v2-crm-saas-django | 67.1 | 0.613 | 0.679 | 19 | 12 | 9 |
| vc-kimi-code-seeded-v2-fintech-lending-fastapi | 67.1 | 0.710 | 0.667 | 22 | 9 | 11 |
| vc-codex-seeded-v2-marketplace-commerce-fastapi | 66.4 | 0.760 | 0.655 | 19 | 6 | 10 |
| vc-codex-seeded-v2-crm-saas-django | 66.1 | 0.815 | 0.647 | 22 | 5 | 12 |
| vc-codex-high-seeded-v2-healthcare-clinic-django | 65.4 | 0.654 | 0.654 | 17 | 9 | 9 |
| vc-claude-code-seeded-v2-fintech-lending-fastapi | 64.6 | 0.576 | 0.655 | 19 | 14 | 10 |
| vc-codex-seeded-v2-education-lms-django | 64.5 | 0.846 | 0.629 | 22 | 4 | 13 |
| vc-claude-code-seeded-v2-property-management-fastapi | 64.0 | 0.677 | 0.636 | 21 | 10 | 12 |
| vc-codex-seeded-v2-hr-payroll-django | 63.3 | 0.857 | 0.615 | 24 | 4 | 15 |
| vulpy | 63.0 | 0.500 | 0.649 | 37 | 37 | 20 |
| vc-claude-code-seeded-v2-healthcare-clinic-django | 62.7 | 0.452 | 0.655 | 19 | 23 | 10 |
| vc-claude-code-seeded-v2-education-lms-django | 62.3 | 0.606 | 0.625 | 20 | 13 | 12 |
| vc-codex-seeded-v2-legal-case-django | 61.9 | 0.769 | 0.606 | 20 | 6 | 13 |
| vc-codex-seeded-v2-fintech-lending-fastapi | 61.1 | 0.815 | 0.595 | 22 | 5 | 15 |
| vc-kimi-code-seeded-v2-education-lms-django | 60.7 | 0.607 | 0.607 | 17 | 11 | 11 |
| vc-claude-code-seeded-v2-hr-payroll-django | 60.5 | 0.447 | 0.630 | 17 | 21 | 10 |
| dvblab | 60.2 | 0.722 | 0.591 | 13 | 5 | 9 |
| vc-codex-seeded-v2-healthcare-clinic-django | 58.1 | 0.852 | 0.561 | 23 | 4 | 18 |
| insecure-web | 57.5 | 0.833 | 0.556 | 5 | 1 | 4 |
| damn-vulnerable-flask-app | 54.4 | 0.667 | 0.533 | 8 | 4 | 7 |
| threatbyte | 53.6 | 0.518 | 0.538 | 14 | 13 | 12 |
| python-ssti | 52.6 | 1.000 | 0.500 | 1 | 0 | 1 |
| vulnpy | 49.5 | 0.792 | 0.475 | 38 | 10 | 42 |
| vulnerable-flask-app | 42.9 | 0.429 | 0.429 | 9 | 12 | 12 |
| vulnerable-python-apps | 42.3 | 0.600 | 0.409 | 9 | 6 | 13 |
| python-insecure-app | 40.0 | 1.000 | 0.375 | 3 | 0 | 5 |
| vampi | 39.5 | 0.353 | 0.400 | 6 | 11 | 9 |
| djangoat | 37.2 | 0.442 | 0.365 | 19 | 24 | 33 |
| extremely-vulnerable-flask-app | 36.2 | 0.688 | 0.344 | 11 | 5 | 21 |
| dvpwa | 32.3 | 0.700 | 0.304 | 7 | 3 | 16 |
| flask-xss | 31.6 | 0.600 | 0.300 | 9 | 6 | 21 |
| dsvw | 27.9 | 0.875 | 0.259 | 7 | 1 | 20 |
| vulnerable-tornado-app | 22.7 | 0.500 | 0.214 | 3 | 3 | 11 |
| dsvpwa | 20.3 | 0.750 | 0.188 | 6 | 2 | 26 |
| dvga | 14.8 | 0.357 | 0.139 | 5 | 9 | 31 |
0 of 62 repositories scored 0.0 — nothing labelled was found in either. They are in the table, at the bottom.
What was not scored, and why it matters
realvuln-owasp-web-playgroundrealvuln-pygoatrealvuln-python-apprealvuln-vulnerable-api
Upstream repositories are gone - git clone returns 'Repository not found' for all four. Not a choice made here, and not a random sample: all four are deliberately vulnerable teaching apps, the densest and most pattern-obvious repos in the corpus, so their absence works against this score rather than for it.
The other languagetwo runs
What the JavaScript side does, and what it did before it had read this.
Every figure above describes Python. This one describes JavaScript, and it has been measured twice. The first run was blind — no rule in this engine had been written or chosen by reading a label from this benchmark — and it scored 0.2286. That is the figure for unseen code and it does not improve. What the blind run bought was a diagnosis; the number here is what the engine scores after acting on it. The two corpora are not comparable in either direction — that one is applications, this one is libraries, and a library has no request handler for the reachability analysis to start from.
- 54.4%recall
- 312sinks found
- 573sinks labelled
- 575npm packages
Recall is the sound metric here and it is the only one published. The benchmark labels one vulnerability per package and says nothing about the rest of it, so an unmatched-finding ratio would be a lower bound on noise rather than a precision — putting it beside the figure above would compare two different measurements that share a name.
- redos28 / 8732.2%
- prototype-pollution72 / 18538.9%
- command-injection62 / 10161.4%
- code-injection22 / 3366.7%
- path-traversal128 / 16776.6%
The blind run said three things and this is what each became. ReDoS scored zero because that analysis was Python-only — stated in the roadmap before a package was fetched, so it could not afterwards be presented as a discovery; it reads JavaScript now, and 8 of 87 is the honest size of criteria that describe exponential backtracking when most real reports are polynomial. Prototype pollution is still the worst class, but the rule that scored the nine has been retired for one that reports the write rather than the loop. And 35 sinks under dist/ or build/ were called a scoping decision rather than a detection failure — the engine reads those directories now when the package publishes them, 29 became reachable, and two were found. That claim was the flattering half and it was wrong.
Provenance
Two digests, so a published figure cannot outlive its engine.
A figure is reproducible only when what produced it can be identified. The ground-truth digest pins the corpus; the engine digest covers every module that can change what the measured run emits. A published figure outliving the code it came from is not hypothetical here: it occurred, with every gate green, because nothing compared the result to the engine. A gate does now.
Recomputing the ground-truth digest on Windows gives a different value for an identical corpus — it hashes raw bytes and joins paths with the platform separator, so CRLF and backslashes each change it. Normalise to LF with forward slashes and it reproduces exactly.
- Ground truthsha256:af5901bffc94380b50f31909655c12edfe0762e82162051d8b1238c66698593f
- Enginesha256:8f426cd2c1196ae15e6d5460220e6fa5041bd473a6d66cc2ad2806c9f8fc0db9
- Benchmarkhttps://github.com/kolega-ai/Real-Vuln-Benchmark · 2.0.0
- TierTier 0 only (--no-deps --no-scanners, no LLM)
- Re-verified2026-08-15
Reproduce it
About fifteen minutes, end to end.
Nothing here is computed by this repository. run.py writes Semgrep-format JSON and the benchmark's own scorer is the authority — a tool that grades itself against someone else's corpus has reintroduced exactly the problem the corpus was there to solve. The full 62-repository scan is 1.4 minutes; the rest is cloning.
Four of the 66 repositories cannot be cloned at all, so any reproduction lands on 62 the same way this one did.
git clone https://github.com/kolega-ai/Real-Vuln-Benchmark
cd Real-Vuln-Benchmark && python3 clone_repos.py && cd -
python3 eval/realvuln/run.py --benchmark ../Real-Vuln-Benchmark
cd ../Real-Vuln-Benchmark
for r in repos/*/; do python3 score.py --repo "$(basename "$r")" --scanner secaudit; done
python3 dashboard.py --scanners secaudit
Four notes, so the next run does not rediscover them
- 1
score.pytakes--repoand scores one repository at a time. There is no--all-reposflag. - 2
Windows needs
PYTHONUTF8=1, or the scorer aborts writing its own markdown report under a non-UTF-8 console codepage. - 3
Scoring a second scanner overwrites the per-repo scorecards for the same date. The previous set has to be captured before the next scanner is scored.
- 4
compute_gt_hash.pydisagrees with the published digest on Windows for an identical corpus. Normalise to LF with forward slashes and it matches — the first reading says the ground truth moved, which would mean no run is comparable to any other.
What this does not say
Three things this number is not.
- 1
It is not a measurement of the LLM tier
Tier 1 is the tier meant to reach the classes the deterministic one structurally cannot. Measuring it needs paid inference across every repository here, and it is a stated non-goal rather than backlog. The tier ships off by default and the claim was narrowed instead.
- 2
It is not a measurement of the JavaScript engine
RealVuln v1 is Python-only. The structural analyses grew a JavaScript/TypeScript front end, so the same four questions are now asked of both languages and only one of the two answers has ever been scored by someone else. The JS side ships as a regression floor, not as a score.
- 3
Not a claim about any other repository
The families holding most of the labels are the ones still near zero, and no pattern added here touches them. A corpus of deliberately vulnerable applications is not the shape of a production codebase either — in both directions.
None of these is a caveat added after the fact. Each one is a bound this measurement has had from the first run, and the report the tool writes states its own bounds the same way.
Read the code that produced it.
The scorer output is committed, the harness is one command, and the CI gate fails the build if a figure on this page stops matching the raw result.