> BenchProctor

Open SAST benchmark · Apache 2.0 · no signup

Measure your SAST tool against ground truth it was not trained to recognize.

BenchProctor is an open benchmark corpus for static analysis. Every test case is labeled vulnerable or safe across 11 languages, with no category or label hint in its name or identifiers. Score compatible SARIF 2.1.0 output with the bundled Python scorer.

Release 2026.07.22 is live now: 2,938,418 balanced cases across all 11 languages. Read the release update →

score_sarif.py
 $ python score_sarif.py results.sarif expectedresults-2026.07.22.csv # 2,938,418 cases | 11 languages | 242 categories CATEGORY-AVERAGED SCORING (macro-averaged) FLAT AGGREGATE SCORING SUMMARY reports TPR, FPR, and Youden's J 

Actual scorer section headings. Your metrics depend on your scanner output.

11
languages
21
frameworks
242
categories
231
unique CWEs
2,938,418
labeled cases
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.

→ Versioned releases use fresh variants while preserving the published scoring contract.

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.

→ Eleven languages and twenty-one framework targets ship standalone now. Cross-file, polyglot, and evasion shapes remain on the public roadmap.

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.

Seeded rotation

Fresh releases. The same yardstick.

A rotated release can change the actual test code, so a tool cannot rely on recognizing prior files. The published scoring contract keeps the same category identity, balance, and coverage, making results comparable across releases.

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
rotated releases
 # fresh variants, identical contract
corpus-2026.07.22/ 242 categories · 50/50 · difficulty held
corpus-next-release/ 242 categories · 50/50 · difficulty held # results stay comparable across the fixed scoring contract # prior file recognition does not transfer to the rotated corpus [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.

Current scope and roadmap

What ships now, and what comes next.

Release 2026.07.22 contains standalone cases across 11 languages. Broken-sanitizer variants ship now; the other shapes below remain on the public roadmap.

Cross-file CWE chains, roadmap

A planned shape that threads a weakness across modules and functions. It is not part of the current standalone release.

Polyglot service scenarios, roadmap

A planned shape for taint that crosses language and process boundaries. It is not part of the current standalone release.

Broken sanitizers, shipping now

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 and modern threats, roadmap

Adversarial and evasion-oriented cases are in active development. They are not yet a public release claim.

Polyglot coverage

Eleven languages, twenty-one framework targets, 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 Roadmap: cross-file ✓ Broken sanitizer

Go

net/http · Gin

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

Java

Spring · Jakarta EE

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

JavaScript

Express · Koa

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

TypeScript

NestJS · Express

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

PHP

Laravel · Symfony

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

Ruby

Rails · Sinatra

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

Bash

standalone

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

Rust

Actix · Axum

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

C

standalone

✓ Standalone Roadmap: cross-file ✓ Broken sanitizer

C++

cpp-httplib · standalone

✓ Standalone Roadmap: cross-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

Compatible SARIF 2.1.0 output can be scored. The scorer is a single standard-library Python file with no dependencies. Matching still depends on the locations and CWE data a tool provides.

Ground truth you can trust

Public labels, transparent scoring.

A benchmark is only as useful as its labels and its scoring rules. Each public bundle includes test code, an answer-key CSV, a scorer, and a manifest with integrity data. BenchProctor states that releases pass its gate suite before publication; the per-file proof metadata is not public.

what you download per suite
 # public bundle contents testcode/ the programs you scan, blind expectedresults.csv test, category, label, CWE score_sarif.py the public, dependency-free scorer benchproctor-manifest.json counts and checksums # the label for each program safe or vulnerable [ok] public labels and integrity data included

Check the public artifact

Verify the published ZIP hashes, inspect the scorer, and reproduce the score from your own SARIF output. The release does not publish its internal proof metadata or oracle output.

Challenge a label with evidence

Each public label includes its category and CWE in the answer key. If you think a label is wrong, inspect the released case and answer key, then raise the specific discrepancy.

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 reproducible score for your scanner. Releases are versioned and published periodically; 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 expectedresults-2026.07.22.csv
# 3. read TPR, FPR, and your Youden J  category-averaged + flat aggregate, per-category breakdown