guardrailprobe
Provider-agnostic AI guardrail benchmarking tool. Tests your guardrail layer — not your model — across 10 backends against the OWASP LLM Top 10.
What it does
guardrailprobe fires 78 attack probes at your guardrail endpoints and tells you which ones let attacks through. It produces:
- Pass/fail per probe across OWASP LLM01–LLM10 and content-moderation categories
- Side-by-side comparison of multiple backends in a single run
- Signed benchmark reports (PDF with RFC 3161 timestamp, JSON, Markdown)
- Flask dashboard for ad-hoc probe runs and report browsing
No framework lock-in. No cloud account required. Just point it at an endpoint and run.
Supported backends
| Backend | Adapter key | Notes |
|---|---|---|
| NVIDIA NeMo Guardrails | nemo |
Requires pip install guardrailprobe[nemo] (includes nemoguardrails, langchain, langchain-openai, langchain-aws, langchain-community) |
| Guardrails AI | guardrails_ai |
Regex fallback always available; SDK optional |
| Microsoft Presidio | presidio |
Requires pip install guardrailprobe[presidio] |
| Lakera Guard | lakera |
Requires LAKERA_GUARD_API_KEY |
| OpenAI Moderation | openai_moderation |
Requires OPENAI_API_KEY |
| Azure Content Safety | azure_content_safety |
Requires AZURE_CONTENT_SAFETY_KEY + endpoint |
| Azure Prompt Shields | azure_prompt_shields |
Shares credentials with azure_content_safety — no separate key |
| AWS Bedrock Guardrails | aws_bedrock |
Requires AWS_ACCESS_KEY_ID + guardrail ID |
| Meta LlamaFirewall | llama_firewall |
Requires pip install guardrailprobe[llamafirewall] |
| LLM Guard | llm_guard |
Requires pip install guardrailprobe[llm_guard] |
Adapters with missing credentials return SKIPPED gracefully — partial configurations run fine.
Installation
pip install guardrailprobe
With optional SDK extras:
# All extras
pip install "guardrailprobe[all]"
# Pick what you need
pip install "guardrailprobe[nemo,guardrails_ai,presidio]"
Skip the spaCy model download (e.g. in CI):
GUARDRAILPROBE_SKIP_SPACY=1 pip install guardrailprobe
Quick start
# 1. Set up credentials — interactive wizard (or copy .env.example to .env and edit manually)
guardrailprobe init
# 2. Check which backends are ready
guardrailprobe status
# 3. Run a benchmark (current month, all configured backends)
guardrailprobe run --output-dir ./reports
# 4. Run against specific backends only
guardrailprobe run --backends lakera,openai_moderation --output-dir ./reports
# 5. Launch the dashboard
guardrailprobe dashboard
Run it in CI (GitHub Action)
Gate every push/PR on your guardrail safety score with guardrailprobe-action:
- uses: askuma/guardrailprobe-action@v1
with:
backend: lakera
api_key: ${{ secrets.LAKERA_GUARD_API_KEY }}
threshold: "90" # fail the build below 90/100
The action pins a guardrailprobe release, so scores are comparable across runs. See the action README for all inputs.
Docker
Start the dashboard
docker compose up
Open http://localhost:8080. The container starts even without a .env file — the Setup Guide card in the dashboard lists exactly which environment variables each unready adapter needs.
docker-compose.yml bind-mounts ./site-packages and ./hf_cache from the host. If they don't
exist before the first docker compose up, Docker auto-creates them owned by root, which the
container's non-root user can't write to — ./hf_cache writes then fail silently and models get
re-downloaded on every run instead of being cached. Create them first:
mkdir -p site-packages hf_cache
docker compose up
If you skip this and see a "not writable" warning in the container logs, fix it with:
sudo chown -R 1000:1000 hf_cache
Configure credentials
cp .env.example .env
# Fill in the keys for the backends you want to test, then:
docker compose up
The .env file is optional. Any variables already exported in your shell are passed through automatically via the environment: block in docker-compose.yml.
PDF report signing
docker-compose.yml always sets GUARDRAIL_SIGNING_KEY_P12=/app/certs/guardrail_signing.p12 and
bind-mounts ./certs:/app/certs:ro (read-only — the container should never be able to modify the
key it signs with). Because that env var is always set inside the container, the
fail-closed behaviour applies from the first run: if
./certs/guardrail_signing.p12 doesn't exist on the host, guardrailprobe run exits with an
error instead of silently falling back to the demo cert. This is intentional, not a bug — a
container should never produce reports signed with a throwaway dev cert just because the real key
wasn't mounted yet.
Generate the cert on the host before the first Docker run (the mount is read-only, so this can't be done from inside the container):
mkdir -p certs
pip install guardrailprobe # or: pip install -e . from a checkout
guardrailprobe cert generate --output certs/guardrail_signing.p12
docker compose up
Adapter status in Docker
Here is the out-of-the-box status for each adapter and what you need to enable it:
| Adapter | Dependencies | What you need |
|---|---|---|
guardrails_ai |
None (regex fallback built-in) | Nothing — works without credentials |
presidio |
spaCy model (bundled in image) | Nothing — runs locally |
nemo |
nemoguardrails + LangChain stack (bundled) |
One LLM provider (priority order): AWS Bedrock (any credential form — IAM role, env vars, instance profile — recommended, ~8 min/run, no per-minute rate limits), OLLAMA_BASE_URL (local, GPU recommended), NEMO_OPENAI_API_KEY, OPENROUTER_API_KEY (16 req/min free tier, ~15 min/run), OPENAI_API_KEY, AZURE_OPENAI_API_KEY, or ANTHROPIC_API_KEY. Works without any LLM key in colang pattern-matching mode. |
aws_bedrock |
boto3 SDK (bundled) |
AWS_BEDROCK_GUARDRAIL_ID, AWS_DEFAULT_REGION, AWS credentials |
lakera |
None — direct REST via httpx |
LAKERA_GUARD_API_KEY |
openai_moderation |
None — direct REST via httpx |
OPENAI_API_KEY |
azure_content_safety |
None — direct REST via httpx |
AZURE_CONTENT_SAFETY_KEY + AZURE_CONTENT_SAFETY_ENDPOINT |
azure_prompt_shields |
None — direct REST via httpx |
Same as azure_content_safety — no separate key |
llama_firewall |
Volume-mounted (not in image) | See below |
llm_guard |
Volume-mounted (not in image) | See below |
Azure HTTP 401s: if azure_content_safety or azure_prompt_shields pass the credential
pre-flight check (a key and endpoint are set) but every probe still comes back HTTP 401 in the
report, that's a problem with your Azure resource, not with guardrailprobe — a revoked/rotated
key, a wrong region, or a wrong resource type (Azure Content Safety keys are unusually long;
double-check you copied the key for a Content Safety resource, not a different Cognitive Services
resource). The report now surfaces this explicitly instead of silently dropping the backend.
LlamaFirewall (Meta PromptGuard 2)
LlamaFirewall runs a local ML model and is excluded from the Docker image to keep it lean.
Requirements: Python 3.10–3.12 on the host (PyTorch is a transitive dependency).
# 1. Install into ./site-packages on your host
# --ignore-installed avoids false conflicts with other packages in your host environment
python3.12 -m pip install llamafirewall --target ./site-packages --ignore-installed
# 2. Restart the container — the entrypoint detects the package automatically
docker compose up
On startup you will see:
[guardrailprobe] site-packages mounted — llama_firewall: YES llm_guard: NO
First run — model download (one-time): LlamaFirewall uses Meta's Llama-Prompt-Guard-2-86M model, which is gated on HuggingFace. You must:
- Accept the license at huggingface.co/meta-llama/Llama-Prompt-Guard-2-86M
- Set
HF_TOKEN=<your-token>in.envbefore starting the container
The model is cached in ./hf_models (bind-mounted at /app/hf_models inside the container). Subsequent runs load from cache and work fully offline.
PyTorch / CUDA compatibility: If the host torch version does not match the CUDA driver, the first scan raises Cannot copy out of meta tensor. The adapter automatically sets CUDA_VISIBLE_DEVICES="" to force CPU-only inference, which sidesteps this. If you still see the error, verify torch is installed in ./site-packages:
python3.12 -c "import torch; print(torch.__version__)"
If the model never loads successfully, guardrailprobe marks all LlamaFirewall probes as SKIPPED (not BLOCK) so benchmark results are not inflated.
LLM Guard (Protect AI)
LLM Guard runs PromptInjection and Toxicity scanners locally and is also excluded from the Docker image.
Requirements: Python 3.9–3.12 on the host.
# 1. Install into ./site-packages on your host
python3.12 -m pip install llm-guard --target ./site-packages --ignore-installed
# 2. Restart the container
docker compose up
On startup you will see:
[guardrailprobe] site-packages mounted — llama_firewall: NO llm_guard: YES
First run — model download (one-time): PromptInjection and Toxicity scanners download their model weights from HuggingFace on first use. No token or license is required — the models are public. Weights are cached in ./hf_models.
Scanner init failures: If llm_guard is installed but scanners fail to load (common when transformers or torch versions mismatch), the adapter logs the exact error and falls back to pattern-only detection. Probes not matched by patterns are returned as SKIPPED — not BLOCK — so benchmark results stay accurate. To diagnose:
python3.12 -c "from llm_guard.input_scanners import PromptInjection; PromptInjection()"
No environment variables are required. LLM Guard runs fully offline once models are cached.
Install both at once
python3.12 -m pip install llamafirewall llm-guard --target ./site-packages --ignore-installed
docker compose up
The container prints the detected status for each package at startup and skips any that are absent — no configuration required.
Custom HTTP guardrail (opt-in)
GenericHTTPAdapter lets you test any proprietary or self-hosted guardrail that exposes an HTTPS API. It is not registered by default — it won't appear in benchmark runs unless you register it manually:
from guardrailprobe.adapters import REGISTRY
from guardrailprobe.adapters.generic_http import GenericHTTPAdapter
REGISTRY.register(GenericHTTPAdapter())
| Variable | Required | Description |
|---|---|---|
CUSTOM_GUARDRAIL_URL |
Yes | Target endpoint — must start with https:// |
CUSTOM_GUARDRAIL_API_KEY |
No | Bearer token or API key |
CUSTOM_GUARDRAIL_AUTH_HEADER |
No | Header name for the key (default: Authorization) |
Add to .env:
CUSTOM_GUARDRAIL_URL=https://your-guardrail-api.example.com/check
CUSTOM_GUARDRAIL_API_KEY=your-key-here
# CUSTOM_GUARDRAIL_AUTH_HEADER=X-Api-Key # only needed if the API uses a non-standard header
One-shot benchmark via Docker
docker compose run --rm guardrailprobe \
guardrailprobe run --year 2026 --month 6 --output-dir /app/reports
Reports are written to the guardrailprobe_reports named volume and also to ./docs/benchmarks on the host (via the ./docs bind mount).
Ollama (local LLM for NeMo, GPU recommended)
The container uses network_mode: host so localhost:11434 inside the container reaches the host's Ollama process directly, without exposing Ollama to the LAN.
# Start Ollama on the host (separate terminal)
ollama serve
ollama pull llama3.2
# Enable in .env
echo "OLLAMA_BASE_URL=http://localhost:11434" >> .env
docker compose up
Ollama is disabled by default (OLLAMA_BASE_URL=). CPU inference with llama3.2 is too slow for NeMo's 3-call-per-probe workflow (~20 s/probe); a GPU is recommended. Without OLLAMA_BASE_URL, NeMo uses AWS Bedrock if credentials are present (priority 1 — recommended for benchmarks).
Skip the spaCy model download (CI / constrained environments)
docker compose build --build-arg SKIP_SPACY=1
Probes
78 built-in attack probes across the 10 OWASP LLM Top-10 categories used in
results_by_category in every generated report:
| Category | OWASP ref | Probes |
|---|---|---|
| Prompt Injection | LLM01 | 7 |
| Insecure Output Handling | LLM02 | 21 |
| Training Data Poisoning | LLM03 | 5 |
| Model Denial of Service | LLM04 | 6 |
| Supply Chain Vulnerabilities | LLM05 | 5 |
| Sensitive Info Disclosure | LLM06 | 12 |
| Insecure Plugin Design | LLM07 | 6 |
| Excessive Agency | LLM08 | 6 |
| Overreliance | LLM09 | 5 |
| Model Theft | LLM10 | 5 |
| Total | 78 |
20 of the 78 probes (CM-001–CM-020) are content-moderation-flavored payloads
(hate speech, violence, harassment, etc.). They don't get their own category —
probes.py tags CM-001–CM-015 under LLM02 and CM-016–CM-020 under
LLM06, which is why those two categories have more probes than the others.
See METHODOLOGY.md for probe design, scoring, and reproduction steps.
Reports
Each guardrailprobe run produces three artifacts in the output directory:
reports/
benchmark_2026_08.pdf # Signed PDF with RFC 3161 timestamp
benchmark_2026_08.json # Machine-readable full results
benchmark_2026_08.md # Human-readable summary
To sign reports with your own certificate:
guardrailprobe cert generate # self-signed P12 for testing
guardrailprobe cert show # inspect the active signing cert
guardrailprobe cert verify report.pdf # verify an existing report
Certificate precedence
--p12 PATHflag onguardrailprobe run(highest priority)GUARDRAIL_SIGNING_KEY_P12environment variable- Auto-generated self-signed demo cert (only when env var is unset)
# Use a specific cert for one run
guardrailprobe run --p12 /path/to/prod.p12 --p12-pass-file /path/to/passphrase.txt
# Or set the env var permanently
export GUARDRAIL_SIGNING_KEY_P12=/path/to/prod.p12
export GUARDRAIL_SIGNING_KEY_PASS=mypassphrase # or use --p12-pass-file
Fail-closed behaviour: if GUARDRAIL_SIGNING_KEY_P12 or --p12 is set but
the file does not exist, guardrailprobe exits with an error rather than
silently falling back to a demo certificate. This prevents misconfigured paths
from producing production reports signed with a worthless dev cert. Unset the
variable to opt into auto-generation.
Configuration
Choose either approach — both produce the same .env file.
Option A — Interactive wizard (recommended)
guardrailprobe init
Walks through each backend and prompts for keys. Press Enter to skip any adapter you don't have credentials for. Writes only what you enter to .env.
Option B — Edit manually
cp .env.example .env
# Open .env and fill in the keys for the backends you want to test
Key variables
| Variable | Backend |
|---|---|
LAKERA_GUARD_API_KEY |
Lakera Guard |
OPENAI_API_KEY |
OpenAI Moderation; NeMo fallback (priority 5) |
AZURE_CONTENT_SAFETY_ENDPOINT + AZURE_CONTENT_SAFETY_KEY |
Azure Content Safety and Azure Prompt Shields |
AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY |
AWS Bedrock Guardrails; NeMo LLM via Bedrock (priority 1, recommended — ~8 min/run, no rate limits) |
AWS_BEDROCK_GUARDRAIL_ID + AWS_DEFAULT_REGION |
AWS Bedrock Guardrails — guardrail ID and region |
CUSTOM_GUARDRAIL_URL |
Generic HTTP adapter — any HTTPS guardrail endpoint (opt-in) |
CUSTOM_GUARDRAIL_API_KEY |
Generic HTTP adapter — optional API key |
GUARDRAIL_SIGNING_KEY_P12 |
PDF signing certificate path |
NeMo-specific variables
| Variable | Default | Description |
|---|---|---|
NEMO_BEDROCK_MODEL |
amazon.nova-pro-v1:0 |
Bedrock model for NeMo intent classification (priority 1 — recommended) |
NEMO_OPENAI_API_KEY |
— | Dedicated OpenAI key for NeMo only (priority 3); avoids sharing with OpenAI Moderation backend |
NEMO_OPENAI_MODEL |
gpt-4o-mini |
Model when using OpenAI or OpenRouter as NeMo LLM |
OPENROUTER_API_KEY |
— | OpenRouter free-tier LLM for NeMo (priority 4, 16 req/min limit — ~15 min/run) |
OPENROUTER_MODEL |
nvidia/nemotron-3-nano-30b-a3b:free |
Model when using OpenRouter |
OLLAMA_BASE_URL |
(empty) | Local Ollama endpoint for NeMo (priority 2); set to http://localhost:11434 to enable. Requires GPU — CPU inference is too slow for NeMo's 3-call-per-probe flow |
OLLAMA_MODEL |
llama3.2 |
Ollama model name |
AZURE_OPENAI_API_KEY + AZURE_OPENAI_ENDPOINT |
— | Azure OpenAI as NeMo LLM (priority 6) |
ANTHROPIC_API_KEY |
— | Anthropic Claude as NeMo LLM via LangChain (priority 7) |
After either option, verify which backends are ready:
guardrailprobe status
Python API
from guardrailprobe import GuardrailBackend
from guardrailprobe.runner import RedTeamRunner
from guardrailprobe.probes import ProbeLibrary, AttackCategory
runner = RedTeamRunner()
library = ProbeLibrary()
# Run all probes against one backend
report = runner.run(GuardrailBackend.LAKERA, library.all_probes())
print(f"Pass rate: {report.pass_rate:.1%}")
# Compare backends
comparison = runner.compare_backends(
[GuardrailBackend.LAKERA, GuardrailBackend.OPENAI_MODERATION],
library.all_probes(),
)
print(f"Best overall: {comparison.best_overall}")
# Filter probes
injection_probes = library.get_by_category(AttackCategory.PROMPT_INJECTION)
critical_probes = library.get_by_severity("critical")
cm_probes = library.get_content_moderation_probes()
Development
GUARDRAILPROBE_SKIP_SPACY=1 pip install -e ".[dev]"
pytest tests/ -v
ruff check guardrailprobe/ tests/
Related projects
- guardrailprobe-action — GitHub Action that runs this benchmark in CI and fails the build below a safety-score threshold.
- guardrailmesh — unified enforcement layer for the same 10 guardrail backends; benchmark with guardrailprobe, enforce with guardrailmesh.
License
Apache-2.0 — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file guardrailprobe-0.1.5.tar.gz.
File metadata
- Download URL: guardrailprobe-0.1.5.tar.gz
- Upload date:
- Size: 147.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e64f49a782b669a1e442c0d451ff50b0e6b585cfee0d442e9ad1d5c371eba211
|
|
| MD5 |
9e4dc4f33d8e73fbf61fbd4e90093080
|
|
| BLAKE2b-256 |
8fa615930873a0a3d504c72e35b3912654a30ac2bf62f98ab19db0c9d207865f
|
Provenance
The following attestation bundles were made for guardrailprobe-0.1.5.tar.gz:
Publisher:
release.yml on askuma/guardrailprobe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
guardrailprobe-0.1.5.tar.gz -
Subject digest:
e64f49a782b669a1e442c0d451ff50b0e6b585cfee0d442e9ad1d5c371eba211 - Sigstore transparency entry: 2345303205
- Sigstore integration time:
-
Permalink:
askuma/guardrailprobe@f05c669cf7ca2f68679b29375bb5db1163ddaa17 -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/askuma
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f05c669cf7ca2f68679b29375bb5db1163ddaa17 -
Trigger Event:
push
-
Statement type:
File details
Details for the file guardrailprobe-0.1.5-py3-none-any.whl.
File metadata
- Download URL: guardrailprobe-0.1.5-py3-none-any.whl
- Upload date:
- Size: 116.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
146c8beaa843cc60ebff66750a1a748f5d16fcae50273593f32e442e19db9a59
|
|
| MD5 |
b36c3e59e42dbc69e1e5e9d76c0d1eee
|
|
| BLAKE2b-256 |
b368daab8099a110326cbbbb1aacb55a3d58ba4cf713c9941aa6ff47861f4a7c
|
Provenance
The following attestation bundles were made for guardrailprobe-0.1.5-py3-none-any.whl:
Publisher:
release.yml on askuma/guardrailprobe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
guardrailprobe-0.1.5-py3-none-any.whl -
Subject digest:
146c8beaa843cc60ebff66750a1a748f5d16fcae50273593f32e442e19db9a59 - Sigstore transparency entry: 2345303241
- Sigstore integration time:
-
Permalink:
askuma/guardrailprobe@f05c669cf7ca2f68679b29375bb5db1163ddaa17 -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/askuma
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f05c669cf7ca2f68679b29375bb5db1163ddaa17 -
Trigger Event:
push
-
Statement type: