Ground-truth labels for the CodeSecBench Tier C app-shaped benchmark corpus.
targets/
cst-nextjs-chat.json # truth for github.com/getdebug-ai/cst-nextjs-chat
cst-vite-rag.json
cst-sveltekit-stream.json
cst-express-agent.json
cst-fastapi-tools.json
cst-crewai-multiagent.json
schema.json # JSON Schema for the truth files
score.js # minimal vendor-side scorer (~80 lines, zero deps)
CHANGELOG.md # versioned label revisions
Each targets/*.json is a list of trueState rows — one per file:line that exercises a security pattern. Every row carries:
file,line— the exact location in the corresponding target repocategory— one ofclient-side-llm-key,prompt-injection,unsafe-tool-output,unsafe-role-merge,pii-in-prompt,unbounded-streamseverity—critical|high|medium|infotrueState—vulnerable(a real finding) orsafe(a near-miss that detectors commonly mis-flag)rationale— a paragraph explaining the adjudication, with CVE/CWE references where applicableprovenance—human-adjudicated|oracle-confirmed|fix-derivedconfidence—0(threat-model-dependent),1(single reviewer),2(oracle-confirmed or multi-rater)
Total v0.1 corpus: 6 repos × ~24 rows each ≈ 144 labeled lines spanning all 6 AI-app security categories. ~72 of them vulnerable, ~36 safe near-misses, ~36 borderline cases (the rationale strings on these explicitly mark them as borderline).
CodeSecBench works the same way every public security benchmark works (OWASP Juice Shop, NodeGoat, SQLi-Labs, OWASP Benchmark): the corpus is public, the truth is public, and the social contract is don't pre-train your detector on the truth file.
We can't enforce this. We document it. If your tool's recall on CodeSecBench is suspiciously perfect — especially on the borderline rows where reasonable engineers disagree — the community will notice and contest it through the PR process. Same review pressure that keeps any open benchmark honest.
If you publish a number against CodeSecBench, cite the truth version you measured against (see CHANGELOG.md). A number quoted against 0.1.0 is not directly comparable to a number quoted against 0.2.0 if labels were revised in between.
# Clone all 6 targets (or whichever subset you support — Python tools can skip
# the JS/TS-only ones, JS/TS tools can skip the Python-only ones).
for repo in cst-nextjs-chat cst-vite-rag cst-sveltekit-stream \
cst-express-agent cst-fastapi-tools cst-crewai-multiagent; do
git clone "https://github.com/getdebug-ai/$repo" "/tmp/$repo"
done
# Run YOUR tool over each repo, capture findings as a JSON array of
# { file, line, category, severity? } objects.
your-tool scan /tmp/cst-nextjs-chat --json > /tmp/cst-nextjs-chat.findings.json
# ... repeat for the other 5 repos
# Clone this truth repo (or just download the targets/*.json files raw).
git clone https://github.com/getdebug-ai/codesecbench-truth
cd codesecbench-truth
# Score:
node score.js \
--findings /tmp/cst-nextjs-chat.findings.json \
--truth targets/cst-nextjs-chat.jsonThe scorer outputs per-category TP / FP / FN / precision / recall, plus a per-category breakdown of which borderline rows your tool agreed/disagreed with.
Open a PR adding results/<your-tool>-<version>-<YYYY-MM-DD>.json with:
{
"tool": "your-tool",
"version": "1.2.3",
"ranAt": "2026-06-15T12:00:00Z",
"truthVersion": "0.1.0",
"perCorpus": [
{ "target": "cst-nextjs-chat", "tp": 10, "fp": 3, "fn": 2, "precisionByCategory": { ... } },
...
],
"rawFindings": "https://gist.github.com/...your-published-raw-output..."
}The leaderboard rendering lives in docs/leaderboard.md and is rebuilt from results/ on every merge.
Truth labels are not infallible — especially the confidence: 0 borderline cases. To dispute:
- Open a PR against
targets/<repo>.jsonwith the proposedtrueStatechange. - Cite: a CVE, a public advisory, a working PoC, or a published threat-model framework that supports the new label.
- Reviewers (the current maintainer + any external co-maintainers — see GOVERNANCE.md) discuss in the PR.
- Accepted disputes bump
truthVersionand add a CHANGELOG entry.
So SAST scanners that crawl the target source tree never see the labels. This is the structural reason for the split. The pointer from each target back to this repo lives in BENCHMARK.md at the target's root — humans find it; scanners don't follow external URLs.
MIT.