> BenchProctor

Open SAST benchmark · Apache 2.0 · no signup

Measure your SAST tool against ground truth it can't overfit.

BenchProctor is a machine-verifiable benchmark corpus for static analysis. Every test case is labeled vulnerable or safe across 9 languages, stripped of every naming hint, and rotated each quarter, so a score reflects detection rather than memorization. Score anything that emits SARIF 2.1.0.

Java ships first, production-ready, full public launch by end of June. Python is next. Why one at a time →

score_sarif.py
 $ python score_sarif.py results.sarif expectedresults-2026.2.csv # 22,400 cases · 9 languages · 234 categories matching SARIF locations to labels…
TP 9,812 FN 388 TPR 96.2%
FP 517 TN 11,283 FPR 4.4% category-averaged Youden J  0.918 flat aggregate  0.917 [ok] wrote score_report.json

Illustrative output. Your numbers are whatever your scanner earns.

9
languages
18
frameworks
234
categories
219
unique CWEs
85.5%
OWASP 2025
50/50
TP / TN balance

Why another benchmark

The benchmarks we had were measuring the wrong thing.

PROBLEM 01

Hand-authored corpora get memorized.

Most suites ship a fixed set of human-written cases. The tools, and the data they were trained on, overfit to them, and a high score stops meaning real-world accuracy.

→ Cases are generated combinatorially, and the corpus rotates every quarter.

PROBLEM 02

Filenames leak the answer key.

When a path, identifier, or comment says sqli_true_positive_01, you are testing string matching, not analysis. A scanner can score high for entirely the wrong reason.

→ Zero comments, zero CWE tags, file names that reveal nothing. The CSV is the only ground truth.

PROBLEM 03

One language. One file. No defenses.

Real findings cross files, services, and languages, and sit next to sanitizers that almost work. Single-file, Java-only suites never exercise any of that.

→ Nine languages and eighteen frameworks, with broken-sanitizer, evasion, and cross-service variants.

What a test case is

Every case is a real taint flow, not a template.

Each category is one vulnerability class expressed as a taint flow. Untrusted input enters at a source, travels to a sink, and a defense either holds or it doesn't. A vulnerable case is missing an effective sanitizer; its safe twin has one. Every case is graded by difficulty, and each category comes out balanced 50/50 vulnerable to safe.

SOURCE

Where untrusted input enters: query params, request bodies, headers, env vars, files, message payloads.

PROPAGATOR

How the data travels to the sink: concatenation, decoding, parsing, indirection, collection passthrough.

SANITIZER

What would neutralize it: parameterization, output encoding, validation, allow-lists, strong crypto.

SINK

The dangerous call: query execution, command exec, HTML render, file open, redirect, deserialization.

a case and its safe twin
 # vulnerable case source http_request_body
propagator json_parse, field_access
sink sql_query
label vulnerable # safe twin, same flow, one defense that holds source http_request_body
propagator json_parse, field_access
sanitizer parameterized_query
sink sql_query
label safe 

The defense in a safe case always fits the sink it protects. A scanner never gets a free win from a nonsense pairing like an HTML encoder guarding a SQL query, so passing means the tool actually reasoned about the flow.

Quarterly rotation

A new corpus every quarter. The same yardstick.

Every quarter the corpus rotates. The actual test code changes completely, so you can't pre-train against next quarter's files. Everything that decides what a score means stays fixed, so last quarter's number is still comparable to this one. The target moves; the ruler reads the same.

Held constant across every rotation
CWE identity per category
fixed
Difficulty distribution
≥20% trivial · ≥50% realistic · ≥20% hard
TP / TN balance
50 / 50
Language & framework coverage
unchanged
quarterly releases
 # fresh variants, identical contract
corpus-2026.2/ 234 categories · 50/50 · difficulty held
corpus-2026.3/ 234 categories · 50/50 · difficulty held # a 2026.2 score stays comparable to 2026.3 # nothing you trained on last quarter is in this one [ok] new code, identical contract

Anti-leakage by construction

The test file gives the scanner nothing but code.

A benchmark only measures analysis if the file can't be classified by its name or its comments. Every file in the corpus holds to these properties, so nothing is left to authoring discipline.

01

No comments

Emitted code carries none. There is no natural-language hint for a model to latch onto.

02

No CWE tags or category names

Not in the code, not in identifiers, not in filenames. Nothing in a test file names its own vulnerability class.

03

Meaningless file IDs

The name and position of a file tell you nothing about its category or its label. There is no ordering to read a pattern off of.

04

CSV-only ground truth

The answer key lives in one place the scanner never reads: a separate expectedresults CSV that only the scorer ever opens.

Beyond single files

Cases that fight back.

Detecting a bare eval(input) is table stakes. The corpus is weighted toward the findings that separate a real analyzer from a pattern matcher, and toward where the threat landscape is actually moving.

Cross-file CWE chains

Instead of one weakness in one file, a chain threads a group of smaller CWEs across modules and functions so they compound into a larger compromise. These are the low-severity findings that line up into privilege escalation and root access, and tools that reason file-by-file miss the path.

Polyglot microservice scenarios

Taint that crosses language and process boundaries the way real systems do: HTTP, gRPC, subprocess, environment variables, and message queues (Kafka, RabbitMQ, Redis), across 2-, 3-, and 4-service topologies with multiple sources, sinks, and paths.

Broken sanitizers

A sanitizer is present but bypassed: a flawed regex, wrong-context escaping, an insufficient length limit. Scanners that trust the mere presence of a sanitizer mislabel these as safe.

Adversarial & modern threats

Built to defeat naive static analysis and to cover where the threat landscape is actually moving, the age of AI rather than just textbook SQLi. That means SAST-evasion (homoglyphs, encoding round-trips, dynamic dispatch, eval/exec wrappers, Unicode-normalization bypasses), software supply-chain shapes (glassworm-style), and AI/LLM prompt-injection-era patterns.

Polyglot coverage

Nine languages, eighteen frameworks, real idioms.

In each language, the code reads the way a developer in that ecosystem would actually write it: framework DTOs, Pydantic models, form classes, ORM calls, not toy snippets. Adding a language changes nothing about the categories, so coverage stays uniform across the matrix.

Python

Flask · Django · FastAPI

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

Go

net/http · Gin

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

Java

Spring · Jakarta EE

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

JavaScript

Express · Koa

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

TypeScript

NestJS · Express

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

PHP

Laravel · Symfony

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

Ruby

Rails · Sinatra

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

Bash

standalone

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

Rust

Actix · Axum

✓ Standalone ✓ Multi-file ✓ Broken sanitizer

OWASP Top 10 2025

Mapped to the current OWASP Top 10.

213 of the 249 CWEs mapped by the OWASP Top 10 2025 are covered as SAST code-pattern test cases (85.5%). The remainder is config-level, supply-chain, or runtime-only, none of it expressible as a static code pattern (A03 is composition analysis, a separate discipline from SAST).

A01 Broken Access Control 37/40
A02 Security Misconfiguration 11/16
A03 Software Supply Chain 0/6
A04 Cryptographic Failures 30/32
A05 Injection 31/37
A06 Insecure Design 27/39
A07 Authentication Failures 34/36
A08 Software & Data Integrity 8/14
A09 Logging & Alerting Failures 5/5
A10 Exceptional Conditions 22/24

Scoring

Scoring is arithmetic on a labeled set.

Run your scanner, hand the SARIF and the answer-key CSV to a one-file scorer, read three numbers. No service, no account, no telemetry. The math is fully in your hands.

The whole formula

                detected   ignored
 vulnerable        TP        FN
 safe              FP        TN

 TPR  = TP / (TP + FN)     detection rate
 FPR  = FP / (FP + TN)     false-alarm rate
 J    = TPR − FPR          Youden's J

What the score means

  • +1.0 Perfect: every vulnerability caught, zero false alarms.
  • 0.0 No better than guessing. A flag-everything scanner lands here.
  • −1.0 Inverted: it flags the safe code and misses the real bugs.

Category-averaged is the headline

Every category is weighted equally, so a handful of huge categories can't carry the score. A flat aggregate is reported alongside for comparison.

Tool-agnostic by design

Anything that emits SARIF 2.1.0 can be scored. The scorer is a single standard-library Python file with no dependencies, nothing to trust but the source you can read.

Ground truth you can trust

The answer key is locked and verified before it ships.

A benchmark is only as good as its labels. Every label is checked before a release goes out, and the scorer is proven to agree with the answer key it reads. A misplaced label can't reach you, because it fails that verification and the release stops.

what you download per suite
 # two files, nothing more testcode/ the programs you scan, blind expectedresults.csv test, category, label, CWE # the label for each program safe or vulnerable [ok] verified before release, scorer proven to match

Verified before publication

100% TPR 0% FPR 1.00 Youden J
A flawless tool would score a perfect Youden J against the answer key, and every release is held to that before it ships. If the labels and the scorer ever disagreed, the release would fail before it could mislead you.

Challenge a label with evidence

Every label ships with the code that justifies it and its CWE, all in the open. If you think one is wrong, you don't have to take it on faith. Read the case and make the argument with the file in front of you.

The ecosystem

Better alone. Unfair together.

Five focused tools for shipping with AI agents. Each stands on its own; together they cover code, action, orchestration, memory, and proof.

Get started

Free. Apache 2.0. No signup.

Three commands stand between you and a real accuracy number for your scanner. Corpora are versioned and released every quarter; the scorer is a single standard-library Python file.

three steps
 # 1. run your scanner, export SARIF 2.1.0 $ your-tool scan ./corpus --format sarif -o results.sarif
# 2. score against the answer key $ python score_sarif.py results.sarif corpus/expectedresults-*.csv
# 3. read TPR, FPR, and your Youden J  category-averaged + flat aggregate, per-category breakdown