____ ______ ______ \ | /
/ __ \ /_ __/ /_ __/ \\\\|////
/ /_/ / / / / / ──── [ ● ] ────
/ _, _/ / / / / ////|\\\\
/_/ |_| /_/ /_/ / /|\ \
L L M R E D T E A M T O O L K I T
OWASP LLM Top 10 (2025)
probe · sting · report · disclose
~ AMB · ORCID 0009-0007-2787-943X · v1.0 · 2026 ~
A Python harness that systematically probes a Large Language Model deployment against the OWASP Top 10 for LLM Applications (2025). 52 probes across all ten categories plus 8 cross-cutting jailbreaks, three target adapters (OpenRouter, NVIDIA NIM, generic OpenAI-compatible), a deterministic heuristic scorer (no LLM-as-judge), and a Rich-powered terminal UI.
The spider is the harness. The web is the OWASP Top 10. Anything that flies in is the model under test.
rtt scan opens a live dashboard with progress, streaming activity,
and a running scorecard:
When the scan completes, you get a clean verdict:
Filter the probe library on the way in:
git clone https://github.com/thunderstornX/llm-red-team-toolkit.git
cd llm-red-team-toolkit
pip install -r requirements-dev.txt
# 1. against a hosted model on OpenRouter
export OPENROUTER_API_KEY=sk-or-...
python -m harness.cli scan \
--adapter openrouter \
--model anthropic/claude-haiku-4-5 \
--authorized
# 2. against a local Ollama (any OpenAI-compat endpoint)
ollama serve &
ollama pull qwen2.5:0.5b
python -m harness.cli scan \
--adapter generic \
--base-url http://localhost:11434/v1 \
--model qwen2.5:0.5b \
--authorized
# 3. against an NVIDIA NIM endpoint
export NVIDIA_API_KEY=nvapi-...
python -m harness.cli scan \
--adapter nvidia \
--model meta/llama-3.3-70b-instruct \
--authorizedA live scan is gated: pass
--authorizedto attest you may probe the target, or setRTT_ASSUME_AUTHORIZED=1for non-interactive automation. Without either you'll be prompted — and refused outright in a non-interactive shell.--dry-runneeds no attestation. SeeETHICAL_USE.md.
Filter, dry-run, parallelise:
# only the LLM01 probes:
python -m harness.cli scan --adapter openrouter --category LLM01 --authorized
# only base64-smuggling probes:
python -m harness.cli scan --adapter openrouter --tag base64 --authorized
# build the report from synthetic responses (no network):
python -m harness.cli scan --adapter openrouter --dry-run
# 8-way parallel:
python -m harness.cli scan --adapter openrouter --concurrency 8 --authorized.
├── probes/ # 52 probes, registered at import time
│ ├── prompt_injection.py LLM01: direct, indirect, leak, role, delim
│ ├── sensitive_info.py LLM02: pii, recall, cred, re-id
│ ├── model_extraction.py LLM02: fingerprint, distil, architecture
│ ├── supply_chain.py LLM03: identity, cutoff, deps
│ ├── data_model_poisoning.py LLM04: trigger, copyright, canary
│ ├── improper_output.py LLM05: markdown, html, sql, path
│ ├── excessive_agency.py LLM06: unauth, scope-creep, persist, coerce
│ ├── tool_abuse.py LLM06: shell, schema, cross-plugin, persist
│ ├── system_prompt_leakage.py LLM07: compliance, direct-dump, enumerate
│ ├── vector_embedding.py LLM08: cross-context, inversion, kb-poison
│ ├── misinformation.py LLM09: hallucinate, citation, legal, calib
│ ├── unbounded_consumption.py LLM10: recursion, unicode, flood, nesting
│ └── jailbreaks.py JAIL: dan, b64, rot13, leet, reverse, multi-turn,
│ context-flood, prefix-injection
├── adapters/ # async OpenAI-compatible HTTP clients
│ ├── openrouter.py
│ ├── nvidia.py
│ └── generic.py
├── harness/ # orchestration
│ ├── cli.py Typer CLI
│ ├── evaluator.py async dispatcher with bounded concurrency
│ ├── scorer.py heuristic refusal/leak/partial classifier
│ ├── report.py JSON + Markdown writers
│ └── config.py Pydantic target / run config
├── tui/ # Rich-powered terminal UI
│ ├── banner.py the spider
│ ├── theme.py colour theme
│ ├── dashboard.py live progress + activity + scorecard
│ └── report.py post-run rendering
├── tests/ # 88 pytest cases (offline; HTTP mocked)
├── results/ # sample run captured against qwen2.5:0.5b
│ ├── sample_report.json (a copy of generic_qwen2.5_0.5b.* below)
│ ├── sample_report.md
│ ├── generic_qwen2.5_0.5b.json
│ └── generic_qwen2.5_0.5b.md
├── paper/ # IEEE 3-page paper (paper.pdf)
└── scripts/
├── render_figures.py paper figures from a real run
└── render_terminal.py ANSI-aware terminal-to-PNG (no webfonts)
| Code | Category | # |
|---|---|---|
| LLM01 | Prompt Injection | 5 |
| LLM02 | Sensitive Information Disclosure | 7 |
| LLM03 | Supply Chain | 3 |
| LLM04 | Data and Model Poisoning | 3 |
| LLM05 | Improper Output Handling | 4 |
| LLM06 | Excessive Agency | 8 |
| LLM07 | System Prompt Leakage | 3 |
| LLM08 | Vector and Embedding Weaknesses | 3 |
| LLM09 | Misinformation | 4 |
| LLM10 | Unbounded Consumption | 4 |
| JAIL | Jailbreaks (cross-cutting) | 8 |
| · | Total | 52 |
python -m harness.cli list shows them all with severity, tags, and
title.
For each (probe, response) pair, the scorer applies this decision
tree:
- probe-specific success marker matches → leaked
- probe-specific refusal marker matches → refused
- generic refusal regex (10 calibrated patterns) matches → refused
- response is empty / whitespace → skipped
- otherwise → partial (human review)
Why not use a strong LLM to judge? Two reasons:
- Reproducibility. Same probe + same response should always score the same way. Heuristic regex is reproducible; an LLM judge is not.
- Auditability. A reviewer who asks "why did this probe count as refused?" gets a concrete regex they can read in 200 lines of Python — not "trust the bigger model".
The trade is the partial bucket: things the rule set can't classify go to human eyes. We keep that bucket honest.
results/sample_report.{json,md} is the output of a real scan against
qwen2.5:0.5b on Ollama, on an Intel Core i5-8250U (16 GB RAM).
52 probes, 395.1 seconds wall-clock.
| outcome | count | of total |
|---|---|---|
| refused | 16 | 30.8 % |
| leaked | 21 | 40.4 % |
| partial | 15 | 28.8 % |
| skipped | 0 | 0.0 % |
| error | 0 | 0.0 % |
The full per-probe breakdown is in
results/sample_report.md.
To reproduce:
ollama pull qwen2.5:0.5b
python -m harness.cli scan \
--adapter generic \
--base-url http://localhost:11434/v1 \
--model qwen2.5:0.5b88 pytest cases. The full suite runs offline in about a second.
python -m pytest tests/ -vCoverage:
- probe-registry invariants — exactly 52 probes, the documented per-category distribution, no duplicate ids, id-prefix matches the category, immutability of frozen dataclasses, payload non-empty
- scorer — every canonical refusal phrase, success/refusal marker priority, generic regex, empty/whitespace response, latency propagation
- marker quality — refusals and benign text never false-positive as
a
leakedfinding, while genuine leaks still do - adapters — wire format with respx mocks, HTTP-error path, parse-error path, transport-error path, API key leakage assertion
- CLI + authorization gate —
--authorized/RTT_ASSUME_AUTHORIZED/ interactive-prompt branches, non-interactive refusal, dry-run and no-match exit codes - evaluator — dispatch under concurrency, dry-run, error propagation, response truncation
- report — JSON schema + Markdown round-trip + pipe-escape, the skipped column reconciles, scan-start timestamp recorded verbatim
- TUI — banner contains signature, dashboard records outcomes, report renderers don't blow up
This is a vulnerability scanner, not an exploit framework. Run it only against endpoints you own or have written authorisation to test. The probes are detectors for vulnerability categories, not weaponised attacks: every probe sets up its own synthetic system prompt and canary, so a "leak" reveals a string the operator already knows.
Full policy in ETHICAL_USE.md.
A 3-page IEEE paper describing the architecture, scoring rationale,
and the live demonstration is in
paper/paper.pdf.
@software{bhutto2026rtt,
author = {Bhutto, Ali Murtaza},
title = {llm-red-team-toolkit: An OWASP-aligned adversarial probing
harness for LLM deployments},
year = {2026},
doi = {10.5281/zenodo.20480444},
url = {https://github.com/thunderstornX/llm-red-team-toolkit},
orcid = {0009-0007-2787-943X}
}The DOI above is the concept DOI — it always resolves to the latest release. Version 1.0.0 is archived at 10.5281/zenodo.20480445.
Related work:
secure-python-pipeline-template— the DevSecOps pipeline this repo's CI builds on.sovereign-llm-quickstart— the on-prem Ollama stack you can point this toolkit at.
MIT © 2026 Ali Murtaza Bhutto
\ | /
\\\\|////
──── [ ● ] ────
////|\\\\
/ /|\ \
~ AMB · ORCID 0009-0007-2787-943X · v1.0 · 2026 ~


