Metadata-Version: 2.4
Name: guardrailprobe
Version: 0.1.5
Summary: Self-contained AI guardrail benchmark tool — OWASP LLM Top 10, signed PDF reports, zero framework dependency.
Project-URL: Homepage, https://github.com/askuma/guardrailprobe
Project-URL: Documentation, https://askuma.github.io/guardrailprobe
Project-URL: Bug Tracker, https://github.com/askuma/guardrailprobe/issues
Project-URL: Changelog, https://github.com/askuma/guardrailprobe/blob/main/CHANGELOG.md
Project-URL: Methodology, https://github.com/askuma/guardrailprobe/blob/main/METHODOLOGY.md
Author-email: Ashutosh Kumar <ashutosh.kumar1089@gmail.com>
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: ai-safety,benchmark,guardrails,llm,owasp,red-team,security
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Requires-Python: >=3.9
Requires-Dist: click>=8.1.0
Requires-Dist: cryptography>=41.0.0
Requires-Dist: flask>=3.0.0
Requires-Dist: httpx>=0.24.0
Requires-Dist: jinja2>=3.1.0
Requires-Dist: pyhanko-certvalidator>=0.26.0
Requires-Dist: pyhanko>=0.21.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: reportlab>=4.0.0
Requires-Dist: rich>=13.0.0
Provides-Extra: all
Requires-Dist: boto3>=1.28.0; extra == 'all'
Requires-Dist: guardrails-ai>=0.5.0; extra == 'all'
Requires-Dist: langchain-aws>=0.2.0; extra == 'all'
Requires-Dist: langchain-community>=0.3.0; extra == 'all'
Requires-Dist: langchain-openai>=0.2.0; extra == 'all'
Requires-Dist: langchain>=0.3.0; extra == 'all'
Requires-Dist: llamafirewall>=0.1.0; extra == 'all'
Requires-Dist: llm-guard>=0.3.0; extra == 'all'
Requires-Dist: nemoguardrails>=0.9.0; extra == 'all'
Requires-Dist: openai>=1.0.0; extra == 'all'
Requires-Dist: presidio-analyzer>=2.2.0; extra == 'all'
Requires-Dist: presidio-anonymizer>=2.2.0; extra == 'all'
Requires-Dist: spacy>=3.0.0; extra == 'all'
Provides-Extra: aws
Requires-Dist: boto3>=1.28.0; extra == 'aws'
Provides-Extra: dev
Requires-Dist: mypy>=1.7.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
Requires-Dist: pytest>=7.4.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Provides-Extra: guardrails-ai
Requires-Dist: guardrails-ai>=0.5.0; extra == 'guardrails-ai'
Provides-Extra: llamafirewall
Requires-Dist: llamafirewall>=0.1.0; extra == 'llamafirewall'
Provides-Extra: llm-guard
Requires-Dist: llm-guard>=0.3.0; extra == 'llm-guard'
Provides-Extra: nemo
Requires-Dist: langchain-aws>=0.2.0; extra == 'nemo'
Requires-Dist: langchain-community>=0.3.0; extra == 'nemo'
Requires-Dist: langchain-openai>=0.2.0; extra == 'nemo'
Requires-Dist: langchain>=0.3.0; extra == 'nemo'
Requires-Dist: nemoguardrails>=0.9.0; extra == 'nemo'
Requires-Dist: openai>=1.0.0; extra == 'nemo'
Provides-Extra: presidio
Requires-Dist: presidio-analyzer>=2.2.0; extra == 'presidio'
Requires-Dist: presidio-anonymizer>=2.2.0; extra == 'presidio'
Requires-Dist: spacy>=3.0.0; extra == 'presidio'
Description-Content-Type: text/markdown

# guardrailprobe

**Provider-agnostic AI guardrail benchmarking tool.**
Tests your guardrail layer — not your model — across 10 backends against the OWASP LLM Top 10.

[![CI](https://github.com/askuma/guardrailprobe/actions/workflows/ci.yml/badge.svg)](https://github.com/askuma/guardrailprobe/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/guardrailprobe)](https://pypi.org/project/guardrailprobe/)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
[![Python](https://img.shields.io/pypi/pyversions/guardrailprobe)](https://pypi.org/project/guardrailprobe/)
[![Probes](https://img.shields.io/badge/probes-78-22c55e)](METHODOLOGY.md)
[![Backends](https://img.shields.io/badge/backends-10-0ea5e9)](#supported-backends)

---

## 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

```bash
pip install guardrailprobe
```

With optional SDK extras:

```bash
# 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):

```bash
GUARDRAILPROBE_SKIP_SPACY=1 pip install guardrailprobe
```

---

## Quick start

```bash
# 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](https://github.com/askuma/guardrailprobe-action):

```yaml
- 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](https://github.com/askuma/guardrailprobe-action#readme) for all inputs.

---

## Docker

### Start the dashboard

```bash
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:

```bash
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:

```bash
sudo chown -R 1000:1000 hf_cache
```

### Configure credentials

```bash
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](#certificate-precedence) 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):

```bash
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).

```bash
# 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:

1. Accept the license at [huggingface.co/meta-llama/Llama-Prompt-Guard-2-86M](https://huggingface.co/meta-llama/Llama-Prompt-Guard-2-86M)
2. Set `HF_TOKEN=<your-token>` in `.env` before 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`:

```bash
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.

```bash
# 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:

```bash
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

```bash
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:

```python
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`:

```bash
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

```bash
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.

```bash
# 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)

```bash
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](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:

```bash
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

1. `--p12 PATH` flag on `guardrailprobe run` (highest priority)
2. `GUARDRAIL_SIGNING_KEY_P12` environment variable
3. Auto-generated self-signed demo cert (only when env var is **unset**)

```bash
# 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)

```bash
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

```bash
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:

```bash
guardrailprobe status
```

---

## Python API

```python
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

```bash
GUARDRAILPROBE_SKIP_SPACY=1 pip install -e ".[dev]"
pytest tests/ -v
ruff check guardrailprobe/ tests/
```

---

## Related projects

- [guardrailprobe-action](https://github.com/askuma/guardrailprobe-action) — GitHub Action that runs this benchmark in CI and fails the build below a safety-score threshold.
- [guardrailmesh](https://github.com/askuma/guardrailmesh) — unified enforcement layer for the same 10 guardrail backends; benchmark with guardrailprobe, enforce with guardrailmesh.

---

## License

Apache-2.0 — see [LICENSE](LICENSE).
