Benchmark LLM agents on fixing real-world CVE vulnerabilities
Benchmark for evaluating LLM agents on fixing real-world CVEs inside sandboxed Docker containers, scored against maintainer tests.
Why it matters
Evaluate how well AI coding agents can identify and fix real-world security vulnerabilities by running them against actual CVE cases in sandboxed Docker environments, scoring their fixes against maintainer security test suites.
Outcomes
What it gets done
Run AI agents inside isolated Docker containers with vulnerable codebases from real CVE disclosures
Provide agents with file editing, search, and pytest tools to diagnose and patch security flaws
Validate fixes by executing hidden security test suites that distinguish vulnerable from patched code
Generate benchmark results comparing models across advisory, diagnose, and locate prompt types
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/giovannigatti-cve-bench | bash Overview
Cve Bench
CVE-Bench is a benchmark that evaluates LLM agents by having them fix real-world security vulnerabilities inside Docker containers, then scores their work against the maintainer's own security test suite across three prompt types with varying detail. Use it to measure LLM agent security-remediation ability against real CVEs with controlled prompt detail; skip it for general code-generation benchmarks or if you cannot run Docker with LLM API access.
What it does
CVE-Bench is a benchmark for evaluating LLM agents on fixing real-world security vulnerabilities. Each task packages a real CVE: a vulnerable SHA and a fixed SHA from an actual GitHub repository, plus hidden security tests, test_security.py, that xfail on the vulnerable code and pass once it is fixed. Agents run inside sandboxed Docker containers, a shared base image plus one task image per CVE built from setup.sh, and are given seven tools (list_files, read_file, search_in_files, edit_file, create_file, delete_file, run_pytest), all of which validate paths against the repository root to block directory traversal; the agent has no access to test_security.py or the git history, and its loop runs for at most 20 turns before the run is recorded as-is. Before running the benchmark, validate.py checks each task's security tests across three phases - vulnerable (tests must fail or xfail), fixed (tests must pass), and regression (the project's own non-security tests must still pass) - and exits 1 if any phase fails for any task. The benchmark itself supports three prompt types with different amounts of information: advisory (the full GHSA advisory, the richest prompt), diagnose (a behavioral description only, no file or function names), and locate (file and function only, no description of the flaw). Runs execute concurrently across tasks, up to 20 workers, with per-provider rate limiting to one active request at a time to avoid 429s; each run writes a JSON result file recording model, prompt type, timings, token usage, and per-test outcomes, and a run is solved only if all security tests pass and no regression tests fail. generate_charts.py then reads all result files and renders SVG charts via Bokeh, requiring Chrome or Chromium for headless export.
python benchmark.py --model openai:gpt-5.5 poolside:laguna-m.1 --prompt-type advisory
When to use - and when NOT to
Use CVE-Bench when you need to measure how well an LLM agent can diagnose and patch a real, previously-disclosed security vulnerability under varying amounts of information, a full advisory versus a behavioral description versus a bare file and function location, and you want the result validated against the project's own maintainer-written test suite rather than a synthetic check. It is not a general code-generation or bug-fixing benchmark: every task is a real CVE with a verified vulnerable and fixed SHA pair, and it requires Docker plus at least one of an OpenAI, Anthropic, or Poolside API key, so it is not usable in an environment without container support or model API access. Adding a new task means creating its meta.json, setup.sh, run_tests.sh, test_security.py, and the three prompt variants, then validating and building it before it can be benchmarked.
Inputs and outputs
Input is one or more provider:model-id strings, such as openai:gpt-5.5, anthropic:claude-haiku-4-5-20251001, or poolside:laguna-m.1, a prompt-type selection of advisory, diagnose, or locate, defaulting to all three, and optionally a specific task-ID subset or a --clean flag to wipe prior results for the selected scope. Output is one JSON result file per task, model, and prompt-type combination, containing the CVE ID, model and prompt type, timestamps, model and test durations, the full turn-by-turn tool-call trace with token counts, and the pass or fail outcome of every security and regression test; generate_charts.py then turns the full results directory into SVG charts.
Integrations
OpenAI API, Anthropic API, and Poolside API as the three supported model providers, each configured via its own API key environment variable; Docker for sandboxed per-task execution; Poetry for Python dependency management; pytest for running the security and regression suites inside each container; and Bokeh with chromedriver-binary for headless SVG chart generation.
Who it's for
Researchers and teams benchmarking LLM agents' real-world security-remediation ability - comparing models and providers, or comparing how much context, full advisory versus behavioral description versus bare location, an agent needs to fix a genuine vulnerability. The project is MIT licensed.
Source README
CVE-Bench
A benchmark for evaluating LLM agents on fixing real-world security vulnerabilities. Agents run inside sandboxed Docker containers and are scored against the maintainer's security test suite.
Requirements
- Python 3.12+
- Docker
OPENAI_API_KEY,ANTHROPIC_API_KEY, and/orPOOLSIDE_API_KEYin your environment (or a.envfile)
Install dependencies:
pip install poetry
poetry install
Task structure
Each task lives under tasks/{CVE-ID}/ and contains:
tasks/CVE-2026-33175/
├── meta.json # GHSA ID, CWE, CVSS, repo URL, vulnerable and fixed SHAs
├── setup.sh # Clones repo, checks out the vulnerable SHA, installs dependencies
├── run_tests.sh # Injects test_security.py into the repo and runs pytest
├── test_security.py # Security tests (xfail on vulnerable code, pass on the fix)
├── advisory.md # Full GHSA advisory (richest prompt)
├── diagnose.md # Behavioural description only — no file or function names
├── locate.md # File and function only — no description of the flaw
└── Dockerfile # Optional; only present when the task needs extra system deps
meta.json example:
{
"ghsa_id": "GHSA-xxxx-xxxx-xxxx",
"cwe": ["CWE-287"],
"cvss": 9.1,
"repo": {
"url": "https://github.com/org/project",
"vulnerable_sha": "abc123^",
"fixed_sha": "abc123"
}
}
setup.sh is idempotent and safe to re-run. test_security.py is kept hidden from the agent during the run and injected only after the agent finishes.
Building Docker images
python build.py
This builds:
- A shared base image (
cve-bench/base) - Python 3.12, git, poetry, and the harness. - One task image per task (
cve-bench/{task-id}) - extends the base, copies the task directory, and runssetup.sh.
Options:
# Build specific tasks only
python build.py --task CVE-2026-33175 CVE-2026-42561
# Skip rebuilding the base image
python build.py --skip-base
Task images are built in parallel (up to 5 workers). If a task directory contains a Dockerfile, it is used instead of the generic docker/task.Dockerfile.
Validating tasks
Before running the benchmark, verify that each task's security tests correctly distinguish vulnerable from fixed code:
python validate.py
For each task, this runs three phases inside the task container:
| Phase | What it checks |
|---|---|
| vulnerable | Security tests must fail (or xfail) on the vulnerable SHA |
| fixed | Security tests must pass on the fixed SHA |
| regression | Non-security tests must pass on the fixed SHA |
Results are displayed as a live table. Exit code is 1 if any task fails any phase.
# Validate specific tasks only
python validate.py --task CVE-2026-33175 GHSA-r758-8hxw-4845
# Skip rebuilding images before validation
python validate.py --skip-build
Running the benchmark
python benchmark.py --model openai:gpt-5.5 poolside:laguna-m.1 --prompt-type advisory
Options:
| Flag | Description | Default |
|---|---|---|
--model |
One or more provider:model-id strings |
all configured models |
--prompt-type |
advisory, diagnose, locate, or any combination |
all three |
--task |
One or more task IDs | all tasks |
--clean |
Delete existing results for the selected scope before starting | off |
Supported providers:
| Provider | Format | API key env var |
|---|---|---|
| OpenAI | openai:gpt-5.5 |
OPENAI_API_KEY |
| Anthropic | anthropic:claude-haiku-4-5-20251001 |
ANTHROPIC_API_KEY |
| Poolside | poolside:laguna-m.1 |
POOLSIDE_API_KEY |
Each run produces a JSON result file in results/:
results/{task-id}__{provider}:{model}__{prompt-type}.json
Existing result files are skipped automatically. Runs execute concurrently across tasks (up to 20 workers), with per-provider rate limiting (one active request per provider at a time) to avoid 429s.
Result format
Each result file is a JSON object with the following structure:
{
"cve_id": "CVE-2026-33175",
"model_id": "openai:gpt-5.5",
"prompt_type": "advisory",
"timestamp": "2026-05-01T12:00:00",
"model_duration_s": 142.3,
"test_duration_s": 8.1,
"turns": [
{
"tool_calls_and_results": [...],
"input_tokens": 12400,
"output_tokens": 310
}
],
"tests": [
{
"kind": "security",
"name": "test_email_verified",
"outcome": "passed"
}
]
}
tests[].kind is either "security" (from test_security.py) or "regression" (from the project's own test suite). A run is considered solved only if all security tests pass and no regression tests fail.
Generating charts
python generate_charts.py
Reads all result files from results/ and writes SVG charts to docs/images/charts/. Requires Chrome/Chromium for Bokeh's headless export (via chromedriver-binary).
Harness architecture
The harness runs inside each Docker container as python -m harness.run. It is responsible for loading the prompt, running the agentic loop, and writing the result file.
src/harness/
├── run.py # Entry point; parses args, wires components, calls BenchmarkRunner
├── client/
│ ├── factory.py # Parses provider:model-id, returns the correct LLMClient
│ ├── _client.py # Abstract LLMClient, ToolCall and LLMTurn dataclasses
│ ├── anthropic.py # Anthropic SDK integration
│ └── oai.py # OpenAI SDK integration (also used for Poolside)
├── agent/
│ ├── core.py # Agentic loop: calls client, dispatches tool calls, threads messages
│ └── runner.py # Wraps Agent, tracks timing and turn list
├── bench/
│ ├── runner.py # Orchestrates setup → agent → security tests → regression tests
│ ├── result.py # BenchmarkResult and TestResult dataclasses, JSON serialisation
│ └── repository.py # Writes result files to disk
└── task/
├── tools.py # Tool implementations: ListFiles, ReadFile, SearchInFiles,
│ # EditFile, CreateFile, DeleteFile, RunPytest
└── prompt_loader.py # Reads advisory.md / diagnose.md / locate.md
Tools available to the agent:
| Tool | Description |
|---|---|
list_files |
List files and directories in the repository |
read_file |
Read file contents, optionally a line range |
search_in_files |
Regex search across the codebase with optional file glob |
edit_file |
Replace a range of lines in an existing file |
create_file |
Create a new file |
delete_file |
Delete a file |
run_pytest |
Run the project's test suite; returns a JSON report |
All tools validate paths against the repository root to prevent directory traversal. The agent does not have access to test_security.py or to the git history.
The agent loop runs for at most 20 turns. If the turn ceiling is reached, the run is recorded as-is and the security tests are still executed against whatever state the agent left the repository in.
Adding a task
- Create
tasks/{CVE-ID}/and addmeta.json,setup.sh,run_tests.sh,test_security.py,advisory.md,diagnose.md,locate.md. - Make
setup.shandrun_tests.shexecutable (chmod +x). - Validate:
python validate.py --task {CVE-ID}. - Build:
python build.py --task {CVE-ID}.
Disclosure
This work was conducted as independent research. At the time of conducting the research and preparing this repository, I had no institutional affiliation.
Citing this work
@misc{gattipinheiro2026cvebench,
author = {Gatti Pinheiro, Giovanni},
title = {{CVE-Bench}: Benchmarking {LLM} Agents on Real-World Security Vulnerability Fixes},
year = {2026},
howpublished = {\url{https://giovannigatti.github.io/cve-bench}},
note = {Code available at \url{https://github.com/GiovanniGatti/cve-bench}}
}
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.