Skip to content

thunderstornX/llm-red-team-toolkit

Repository files navigation

llm-red-team-toolkit

DOI License: MIT tests Probes OWASP

        ____    ______  ______                       \   |   /
       / __ \  /_  __/ /_  __/                       \\\\|////
      / /_/ /   / /     / /                       ──── [ ● ] ────
     / _, _/   / /     / /                            ////|\\\\
    /_/ |_|   /_/     /_/                            /   /|\   \

        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.


See it run

rtt scan opens a live dashboard with progress, streaming activity, and a running scorecard:

dashboard

When the scan completes, you get a clean verdict:

summary

Filter the probe library on the way in:

list


Quickstart

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 \
    --authorized

A live scan is gated: pass --authorized to attest you may probe the target, or set RTT_ASSUME_AUTHORIZED=1 for non-interactive automation. Without either you'll be prompted — and refused outright in a non-interactive shell. --dry-run needs no attestation. See ETHICAL_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

What's in the box

.
├── 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)

Probe distribution

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.


How scoring works (and why no LLM-as-judge)

For each (probe, response) pair, the scorer applies this decision tree:

  1. probe-specific success marker matches → leaked
  2. probe-specific refusal marker matches → refused
  3. generic refusal regex (10 calibrated patterns) matches → refused
  4. response is empty / whitespace → skipped
  5. otherwise → partial (human review)

Why not use a strong LLM to judge? Two reasons:

  1. Reproducibility. Same probe + same response should always score the same way. Heuristic regex is reproducible; an LLM judge is not.
  2. 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.


Real sample run (verifiable, not made up)

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.5b

Tests

88 pytest cases. The full suite runs offline in about a second.

python -m pytest tests/ -v

Coverage:

  • 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 leaked finding, 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

Ethical use

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.


Paper

A 3-page IEEE paper describing the architecture, scoring rationale, and the live demonstration is in paper/paper.pdf.


Citing this work

@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:


License

MIT © 2026 Ali Murtaza Bhutto

                  \   |   /
                  \\\\|////
              ──── [ ● ] ────
                  ////|\\\\
                  /   /|\   \

~ AMB · ORCID 0009-0007-2787-943X · v1.0 · 2026 ~

About

Adversarial probing harness for LLM deployments. 52 probes: OWASP LLM Top 10 (2025) + 8 jailbreaks. Rich-based TUI.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages