SecAudit
RealVuln 2.0.0Tier 0run 2026-08-20

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.

result.json · overall (micro)1762 labels
  • 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.

12.5 blind — the engine had not read this corpus
61.2 now, three corpus-informed rounds later

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.

RoundF3F2 precisionrecall TP / FP / FN
2026-08-12the first run — blind, on a corpus the engine had not read12.513.50.40720.1158204 / 297 / 1558
2026-08-13targeted at the first run's diagnosis, still blind to the rest of the corpus13.314.40.39250.1243219 / 339 / 1543
2026-08-13the configuration and crypto-hygiene round, corpus-informed24.626.10.50370.2327410 / 404 / 1352
2026-08-13the first structural round — authorization and ReDoS26.027.50.51060.2463434 / 416 / 1328
2026-08-13structural analyses: rate limiting, uploads, mass assignment30.932.50.54050.2951520 / 442 / 1242
2026-08-13rate-limit rule narrowed (F3 31.5)31.533.00.54190.3008530 / 448 / 1232
2026-08-16language families, vendored assets, template XSS, Python SQL35.837.70.66010.3405600 / 309 / 1162
2026-08-16vendored-asset filter35.938.00.70750.3405600 / 248 / 1162
2026-08-16JavaScript round: ReDoS front end, prototype-pollution rewrite, published build output35.938.00.70750.3405600 / 248 / 1162
2026-08-16prototype pollution: only a key the caller chose35.938.00.70840.3405600 / 247 / 1162
2026-08-16pathlib: the sink library nothing modelled39.341.30.70860.3740659 / 271 / 1103
2026-08-16req.url: the Node source nothing modelled39.341.30.70860.3740659 / 271 / 1103
2026-08-17the 2026-08-16 round, re-measured: 273 false positives, not 27139.241.30.70710.3740659 / 273 / 1103
2026-08-17quadratic ReDoS, and the JavaScript pattern reported where it runs39.241.30.70410.3740659 / 277 / 1103
2026-08-17prototype pollution: the functions nobody could delimit, the callback's key, and the walk39.241.30.70330.3740659 / 278 / 1103
2026-08-17import-resolved shell and vm receivers39.241.30.70330.3740659 / 278 / 1103
2026-08-17Config + credentials41.643.50.67180.3995704 / 344 / 1058
2026-08-18Nine rule families the corpus had none for59.560.30.67600.58741035 / 496 / 727
2026-08-18Django views stop being public by default59.760.40.66950.58971039 / 513 / 723
2026-08-19The Secure cookie flag the HttpOnly rule was hiding60.661.10.65590.60051058 / 555 / 704
2026-08-19HTML built by concatenation, which is where the JavaScript XSS labels live60.560.90.64710.60051058 / 577 / 704
2026-08-19PHP gets an instrument, and the instrument finds three broken rules60.560.90.64710.60051058 / 577 / 704
2026-08-19PHP superglobal rules — the source is spelled in the sink60.560.90.64710.60051058 / 577 / 704
2026-08-19A suppression that reads the line, not the file60.560.90.64710.60051058 / 577 / 704
2026-08-19A vendored library does not arrive as one file60.561.10.65770.59991057 / 550 / 705
2026-08-19PHP taint — one assignment hop, one file60.561.10.65770.59991057 / 550 / 705
2026-08-19Three structural questions JavaScript could not be asked60.561.10.65770.59991057 / 550 / 705
2026-08-20A real application in the noise floor, and two defects it found60.561.10.65770.59991057 / 550 / 705
2026-08-20The framework's own login: a route with no handler to read61.161.60.65620.60671069 / 560 / 693
2026-08-20SSRF reaches the ordinary HTTP surface; four spellings of the debug switch61.261.60.65600.60731070 / 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.

CategoryBest reportedF3 precisionrecall
Security-specializedKolega.Dev73.00.3880.809
General-purpose LLMClaude Sonnet 4.651.70.7850.498
Rule-based SASTSemgrep17.70.2050.175
Rule-based SAST + taint + structuralSecAudit Tier 061.20.65600.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.

RepositoryF3 precisionrecall TPFPFN
vc-codex-high-seeded-v2-marketplace-commerce-fastapi85.60.6880.88022103
vc-codex-high-seeded-v2-fintech-lending-fastapi81.60.7270.8282495
vc-codex-seeded-v2-logistics-dispatch-fastapi79.70.7740.8002476
vc-codex-high-seeded-v2-support-desk-fastapi77.50.6880.78622106
vc-codex-high-seeded-v2-logistics-dispatch-fastapi77.20.6220.79323146
vc-codex-high-seeded-v2-property-management-fastapi76.60.5250.80821195
vc-kimi-code-seeded-v2-property-management-fastapi76.40.7420.7672387
vc-codex-high-seeded-v2-hr-payroll-django76.00.7600.7601966
vc-kimi-code-seeded-v2-crm-saas-django73.80.7140.7412087
vc-kimi-code-seeded-v2-hr-payroll-django73.60.5790.75922167
vc-codex-seeded-v2-property-management-fastapi73.50.6760.74223118
vc-codex-high-seeded-v2-crm-saas-django73.10.5430.76019166
vc-codex-high-seeded-v2-education-lms-django73.10.5430.76019166
vc-claude-code-seeded-v2-logistics-dispatch-fastapi72.70.7270.7272499
intentionally-vulnerable-python-app72.50.8330.714512
vc-codex-high-seeded-v2-legal-case-django71.70.6920.7201887
lets-be-bad-guys71.10.7390.7081767
vc-codex-seeded-v2-support-desk-fastapi70.70.7780.7002169
vc-kimi-code-seeded-v2-logistics-dispatch-fastapi70.70.6880.71022109
vc-kimi-code-seeded-v2-legal-case-django70.00.7830.6921858
vc-claude-code-seeded-v2-marketplace-commerce-fastapi69.80.8150.68822510
vc-claude-code-seeded-v2-legal-case-django69.60.5950.71022159
vc-kimi-code-seeded-v2-marketplace-commerce-fastapi69.30.6130.70419128
vfapi69.01.0000.667603
vc-kimi-code-seeded-v2-support-desk-fastapi67.90.6790.6791999
vc-kimi-code-seeded-v2-healthcare-clinic-django67.80.8000.66720510
vc-claude-code-seeded-v2-support-desk-fastapi67.40.6570.676231211
vc-claude-code-seeded-v2-crm-saas-django67.10.6130.67919129
vc-kimi-code-seeded-v2-fintech-lending-fastapi67.10.7100.66722911
vc-codex-seeded-v2-marketplace-commerce-fastapi66.40.7600.65519610
vc-codex-seeded-v2-crm-saas-django66.10.8150.64722512
vc-codex-high-seeded-v2-healthcare-clinic-django65.40.6540.6541799
vc-claude-code-seeded-v2-fintech-lending-fastapi64.60.5760.655191410
vc-codex-seeded-v2-education-lms-django64.50.8460.62922413
vc-claude-code-seeded-v2-property-management-fastapi64.00.6770.636211012
vc-codex-seeded-v2-hr-payroll-django63.30.8570.61524415
vulpy63.00.5000.649373720
vc-claude-code-seeded-v2-healthcare-clinic-django62.70.4520.655192310
vc-claude-code-seeded-v2-education-lms-django62.30.6060.625201312
vc-codex-seeded-v2-legal-case-django61.90.7690.60620613
vc-codex-seeded-v2-fintech-lending-fastapi61.10.8150.59522515
vc-kimi-code-seeded-v2-education-lms-django60.70.6070.607171111
vc-claude-code-seeded-v2-hr-payroll-django60.50.4470.630172110
dvblab60.20.7220.5911359
vc-codex-seeded-v2-healthcare-clinic-django58.10.8520.56123418
insecure-web57.50.8330.556514
damn-vulnerable-flask-app54.40.6670.533847
threatbyte53.60.5180.538141312
python-ssti52.61.0000.500101
vulnpy49.50.7920.475381042
vulnerable-flask-app42.90.4290.42991212
vulnerable-python-apps42.30.6000.4099613
python-insecure-app40.01.0000.375305
vampi39.50.3530.4006119
djangoat37.20.4420.365192433
extremely-vulnerable-flask-app36.20.6880.34411521
dvpwa32.30.7000.3047316
flask-xss31.60.6000.3009621
dsvw27.90.8750.2597120
vulnerable-tornado-app22.70.5000.2143311
dsvpwa20.30.7500.1886226
dvga14.80.3570.1395931

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-playground
  • realvuln-pygoat
  • realvuln-python-app
  • realvuln-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.

SecBench.js2026-08-20
  • 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.

result.json
  • 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.py takes --repo and scores one repository at a time. There is no --all-repos flag.

  • 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.py disagrees 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.