A reproducible benchmark for prompt-injection detectors and LLM guardrails — with paired benign/attack evaluation and false-positive-aware scoring.
Most "prompt-injection detector" numbers you see are recall on a pile of attacks. That is half a metric. A guardrail that flags every message has perfect recall and is useless. The number that matters to anyone shipping an LLM is: does it stop attacks without tripping on the legitimate users who happen to talk about the same things?
InjectBench measures both at once. It scores a detector on attacks and on hard negatives — benign inputs that reuse the same roles, tools, and vocabulary as attacks — and reports the paired boundary accuracy: how often the detector flags the attack while clearing its benign twin.
Built by Enes Deniz (OWASP AI Exchange & GenAI Security contributor), Co-Founder at AltaySec.
| The usual way | InjectBench |
|---|---|
| Recall on an attack-only corpus | Recall and false-positive rate, together |
| One aggregate score | Per attack-family and per-language breakdowns |
| No notion of "close calls" | Paired boundary analysis on benign/attack twins |
| Ad-hoc scripts | A stable CLI + library + CI gate + SARIF output |
| English-only | English and Turkish out of the box |
It plugs directly into the open AltaySec / Enes Deniz prompt-injection datasets — no schema wrangling.
pip install injectbench # core, zero dependencies
pip install "injectbench[hub]" # + load datasets straight from the Hugging Face HubFrom source:
git clone https://github.com/3nesdeniz/injectbench
cd injectbench && pip install -e ".[dev,hub]"Evaluate a built-in baseline on the bundled sample:
injectbench evaluate --dataset examples/sample_data.jsonl --detector heuristicEvaluate directly against a dataset on the Hugging Face Hub:
injectbench evaluate --dataset hf:3nesdeniz/english-prompt-injection-3k --split test --detector heuristic --format markdownUse it as a CI gate — fail the build if a guardrail regresses on false positives or recall:
injectbench evaluate --dataset hf:3nesdeniz/english-prompt-injection-3k \
--detector heuristic --max-fpr 0.05 --min-recall 0.90
# exit code 2 if FPR > 0.05, exit code 3 if recall < 0.90Implement one method. That's the whole contract.
from injectbench import evaluate, load, render
from injectbench.detectors import register
from injectbench.detectors.base import Detector
@register
class MyGuardrail(Detector):
name = "my-guardrail"
def predict(self, text: str) -> int:
# call your model / API / rules; return 1 for injection, 0 for benign
return int(my_model.is_injection(text))
examples = load("hf:3nesdeniz/english-prompt-injection-3k", split="test")
result = evaluate(MyGuardrail(), examples, dataset_name="english-prompt-injection-3k")
print(render(result, "markdown"))score(self, text) -> float is optional; implement it to enable threshold sweeps.
## Overall
| Recall (attacks caught) | 1.000 |
| Precision | 0.833 |
| F1 | 0.909 |
| False-positive rate | 0.200 |
## Paired boundary analysis
Boundary accuracy (attack flagged AND benign twin cleared): 0.800 over 5 pairs.
## By attack family / By language
... per-slice recall, FPR, F1 ...
Reports render as Markdown, JSON, or SARIF 2.1.0 (so results flow into GitHub code-scanning and other security dashboards).
The two built-in reference detectors, on each dataset's test split:
| Dataset | Detector | Recall | FPR | F1 |
|---|---|---|---|---|
english-prompt-injection-3k |
regex-baseline | 0.075 | 0.016 | 0.138 |
english-prompt-injection-3k |
heuristic | 0.107 | 0.000 | 0.193 |
agentic-prompt-injection-5k |
regex-baseline | 0.043 | 0.015 | 0.081 |
agentic-prompt-injection-5k |
heuristic | 0.096 | 0.019 | 0.173 |
turkish-prompt-injection-1k |
regex-baseline | 0.089 | 0.000 | 0.163 |
turkish-prompt-injection-1k |
heuristic | 0.089 | 0.000 | 0.163 |
guardrail-hard-negatives |
heuristic | 0.040 | 0.034 | 0.073 |
Those recall numbers are low on purpose. A keyword/regex baseline barely dents these datasets, because the attacks are indirect, agentic, obfuscated, and multilingual — not "ignore previous instructions" one-liners. That gap is the whole point of the benchmark: it is the room a real guardrail has to prove it does more than grep. Reproduce any row with:
injectbench evaluate --dataset hf:3nesdeniz/english-prompt-injection-3k --split test --detector heuristic- Recall / TPR — share of injection attempts flagged. High recall alone is cheap.
- False-positive rate (FPR) — share of benign inputs wrongly flagged. The number that decides whether users tolerate your guardrail.
- Precision, F1, accuracy — standard, for completeness.
- Paired boundary accuracy — over matched (benign, attack) twins, the fraction where the detector flags the attack and clears the benign. This isolates intent discrimination from surface-pattern matching — a detector that just greps for the word "ignore" scores high on recall and terribly here.
Any .jsonl / .parquet with a text field and a label (or class) field. Optional attack_family, language, and pair_id fields unlock the sliced and paired reports. This is exactly the schema of:
3nesdeniz/english-prompt-injection-3k3nesdeniz/agentic-prompt-injection-5k3nesdeniz/turkish-prompt-injection-1k3nesdeniz/guardrail-hard-negatives
injectbench/
├── detectors/ # Detector ABC + regex baseline + heuristic + registry
├── datasets.py # load jsonl / parquet / hf: into a common Example schema
├── metrics.py # confusion matrix, paired boundary analysis (pure, tested)
├── evaluate.py # the harness: overall + per-family + per-language + paired
├── report.py # markdown / json / sarif renderers
└── cli.py # `injectbench evaluate | list-detectors`
Core has zero runtime dependencies; parquet/Hub loaders are optional extras.
- Threshold-sweep / ROC report from
score() - Adapters for popular guardrails (Llama Guard, promptfoo, garak probes)
- Cost/latency accounting per detector
- More languages and attack families as the upstream datasets grow
Contributions welcome — see CONTRIBUTING.md.
@software{deniz_injectbench_2026,
author = {Deniz, Enes},
title = {InjectBench: a reproducible benchmark for prompt-injection detectors and LLM guardrails},
year = {2026},
url = {https://github.com/3nesdeniz/injectbench},
note = {ORCID: 0009-0006-9491-3565}
}