A security scanner for LLM applications and tool-using agents.
LLM apps ship with no security-testing layer. Traditional scanners don't understand prompts, and they understand tool calls even less — a WAF can't tell you whether your agent can be talked into deleting an account.
RedCell probes both layers. It runs the same adversarial methodology against chat endpoints and MCP agents, maps every finding to the OWASP Top 10 for LLM Applications (2025), and grades the result.
pip install -e .
redcell scan --demo # zero setup, no API key, no networkIt tests agents, not just prompts. Point RedCell at an MCP server and it enumerates the server's tools, flags the destructive ones, and — on an authorised target — confirms excessive agency by actually attempting the unauthorised call. A tool call that succeeds when it should have been gated is a confirmed finding, not an inference from what the model claimed it would do. Passive enumeration is the default; active exploitation is opt-in.
Its indirect-injection detection can tell obeying from quoting. When the payload arrives inside a document the model is asked to summarise, the naive check — did the marker appear in the output? — fires on any model that faithfully quotes the document. RedCell's attacks instead carry success signals that cannot appear in a summary: a computed value, a string transform, a closed-domain answer absent from the source. A model only emits them by executing the injected instruction.
The numbers are two claims, not one. Every genuinely-vulnerable case in the test corpus is detected, and hardened controls — a chat target that refuses injection, an MCP server whose destructive tools are auth-gated, a summariser that quotes a malicious document without obeying it — produce zero false positives. See Results.
⚠️ Authorised testing only. RedCell is a defensive red-teaming tool. Run it against systems you own or have explicit permission to test. The payloads are standard, publicly documented patterns, included so defenders can find these weaknesses before attackers do.
redcell scan --demo # scan the built-in vulnerable mock
redcell list-probes # see the probe catalogueScan a real OpenAI-compatible endpoint (OpenAI, Groq, Ollama, LM Studio, or your own FastAPI wrapper):
redcell scan \
--target-url http://localhost:11434/v1 \
--model llama3 \
--system-prompt "You are a support bot for ACME."Passing --system-prompt plants a secret canary in the context, so the
leak / sensitive-info probes score reliably instead of falling back to
heuristics. Filter categories and export a report with
redcell scan --demo -c LLM01 -c LLM07 -o report.md -f md.
--fail-on <severity> exits non-zero when any finding is at or above the
threshold, so a pipeline can block on it. It's opt-in: without the flag a scan
always exits 0, so adding RedCell to a build never breaks it by surprise.
redcell scan --demo --fail-on high -o report.json -f json| Exit code | Meaning |
|---|---|
0 |
Scan completed; nothing at or above the threshold |
1 |
At least one finding at or above --fail-on |
2 |
Usage error (bad target arguments, or an unknown severity) |
RedCell covers the MCP attack surface across four angles — this agent-side breadth is what distinguishes it from prompt-layer scanners:
| Angle | What it checks | OWASP |
|---|---|---|
| Excessive agency | Will a destructive tool actually execute when called ungated? | LLM06 |
| Tool poisoning | Does a tool's description/metadata embed instructions, hidden characters, or exfiltration directives aimed at the model? | LLM01 |
| Insecure auth | Does the server expose destructive tools with no authentication/gating at all? | LLM06 |
| Injection sequences | Does a benign read chain into an exfiltration where the sequence, not either call, is the finding? (detection logic; see roadmap) | LLM01 |
Point RedCell at an MCP server over stdio or HTTP/SSE:
redcell scan --mcp-command "python my_mcp_server.py" # local, stdio
redcell scan --mcp-url https://host/mcp # hosted, HTTP/SSE
redcell scan --mcp-url https://host/mcp --mcp-header "Authorization: Bearer $TOKEN"🔒 Credentials.
--mcp-headervalues are treated as secrets: they are sent only to the server and are never logged, never placed in the target name, and never written to reports or SARIF.
The destructive checks work in two tiers, mirroring how DAST tools separate safe crawling from active exploitation:
Passive (default) — always safe to run. Enumerates tools and flags the
dangerous ones from their MCP annotations (destructiveHint) and name/signature,
without ever invoking them. Excessive-agency and insecure-auth findings are
advisory (MEDIUM/LOW); tool-poisoning is a pure metadata read with no active
variant.
Active (--active) — authorised, disposable targets only. Attempts the
unauthorised call to confirm exploitability. A call that succeeds when it
should have been gated is a confirmed finding (HIGH); a refusal flips to PASS.
Calls use recognisable redcell-probe sentinel arguments.
redcell scan --mcp-command "python my_mcp_server.py" --active
⚠️ --activegenuinely executes the tools it flags —delete_accountreally deletes. Run it only against a server you own or a disposable/test instance.
Why passive is the default is a deliberate detection-confidence vs. operational-safety trade-off. Passive over-reports — it flags a properly-guarded destructive tool it can't distinguish from an ungated one — but it never has a side effect, so the dangerous behaviour is an opt-in choice rather than what happens if you run the obvious command. Active buys back the fidelity (the guarded tool clears to PASS) at the cost of real side effects.
RedCell is validated against controlled targets: a deliberately-vulnerable mock, a mock MCP server, and hardened targets that should yield no findings (a chat model that refuses injection and never leaks its canary; an MCP server whose destructive tools are all auth-gated). The evaluation harness reproduces the numbers:
python evaluation/run_eval.pyGenerated by python evaluation/run_eval.py. LLM06 uses --active.
RedCell is validated on two axes — functional correctness (does a detector fire on a real vulnerability?) and precision (does it stay silent on a clean target?):
| OWASP | Category | Vulnerable mock ¹ | Hardened control ² | Live model (llama3.2) ³ |
|---|---|---|---|---|
| LLM01 | Prompt Injection | 8 / 8 | 0 | 2–3 / 8 |
| LLM02 | Sensitive Information Disclosure | 4 / 4 | 0 | 0–1 / 4 |
| LLM05 | Improper Output Handling | 3 / 3 | 0 | 0 / 3 |
| LLM06 † | Excessive Agency | 1 / 2 | 0 | n/a |
| LLM07 | System Prompt Leakage | 4 / 4 | 0 | 1–2 / 4 |
| LLM09 | Misinformation | 1 / 1 | 0 | 0 / 1 |
| Total | 21 / 22 | 0 | — |
¹ Functional correctness. Every case the mock is deliberately vulnerable to is detected — 20 / 20 chat cases, plus the one ungated MCP tool. The detectors work end-to-end on known positives.
² Precision. On the hardened controls (a chat model that refuses injection and never leaks its canary; an MCP server whose destructive tools are all auth-gated), RedCell raises 0 false positives.
† LLM06 counts destructive tools, not prompts: the vulnerable MCP server exposes 2, but only delete_account is ungated — RedCell confirms exactly it, while the auth-gated wire_transfer correctly PASSes.
³ Live model. Chat probes against llama3.2 (served from http://localhost:11434/v1) with a planted canary — 10 independent runs on 2026-07-22. Cells show the observed min–max across those runs.
Repeats separate two kinds of result: LLM01, LLM02, LLM07 moved between runs — a boundary finding, where the model sometimes resists and sometimes complies.
Passive vs. active (LLM06). In passive mode RedCell flags 2 destructive tools on the hardened MCP server as advisory MEDIUM exposures; --active invokes them, both are refused, and they clear to PASS (0 confirmed) — the detection-confidence vs. operational-safety trade-off, made measurable.
The table above measures the chat-layer detectors (and one MCP tool) with a dated live-model reference. The four MCP-breadth checks — excessive agency, tool poisoning, insecure auth, and injection-sequence detection — are validated by the hermetic test suite (a poisoned + hardened mock MCP server over both stdio and HTTP/SSE): each probe flags its planted positive and spares a precision/clean case, including the boundary cases where a heuristic would false-positive.
RedCell emits SARIF 2.1.0 (redcell scan … --format sarif), so its findings
upload to GitHub and render natively under Security → Code scanning — the
same surface CodeQL and commercial scanners use. Findings carry OWASP category
tags and a security-severity score, so GitHub sorts and filters them like any
other alert. A workflow (.github/workflows/sarif.yml) runs the demo scan and
uploads the SARIF on every push to main.
| OWASP | Probe | What it does |
|---|---|---|
| LLM01 | Direct prompt injection | Instruction-override, delimiter breaks, translation smuggling, payload splitting |
| LLM01 | Indirect / cross-context injection | Hides instructions in "retrieved" documents; scores obeying (an out-of-band action), not quoting |
| LLM01 | Tool poisoning (agent/MCP) | Inspects tool metadata for model-directed instructions, hidden characters, and exfiltration directives |
| LLM02 | Sensitive info disclosure | Tries to extract planted secrets / credentials |
| LLM05 | Improper output handling | Coaxes raw XSS/SQLi-shaped markup out of the model |
| LLM06 | Excessive agency (agent/MCP) | Enumerates and (opt-in) invokes destructive tools |
| LLM06 | Insecure auth (agent/MCP) | Flags destructive tools exposed with no authentication/gating |
| LLM07 | System prompt leakage | Tries to make the model recite its hidden instructions |
| LLM09 | Misinformation | Seed check for confident falsehoods |
target ──> engine ──> [ probe ──> attack(s) ──> detector ] ──> report
Four extension points, each independent:
- Targets (
redcell/targets/) — anything you can send a prompt to, plus tool-callers viaAgentTarget.OpenAICompatTarget,MockVulnerableTarget, and MCP adapters over both stdio (MCPTarget) and HTTP/SSE (MCPHttpTarget), sharing one protocol layer. - Probes (
redcell/probes/) — a category + severity + a set of attacks. Adding one is: subclassProbe, list attacks, pick a detector,@register. - Detectors (
redcell/detectors/) — decide if an attack worked. Precise rule-based detectors ship by default; an optional Groq-powered LLM judge handles fuzzier cases (pip install -e '.[judge]', setGROQ_API_KEY). - Report (
redcell/report.py) — console, JSON (for CI), Markdown (for write-ups).
Because probes only ever see the Target / AgentTarget interface, the same
probe runs against a cloud API, a local model, or an MCP agent unchanged.
from redcell.probes.base import Probe, register
from redcell.detectors.rules import MarkerEchoDetector
from redcell.models import Attack, OwaspCategory, Severity
@register
class MyProbe(Probe):
id = "llm01-my-variant"
name = "My injection variant"
category = OwaspCategory.LLM01
severity = Severity.HIGH
def attacks(self):
return [Attack(id="mv-1", prompt="...", success_marker="OK")]
def detector(self):
return MarkerEchoDetector()- Agent target adapter — LLM06 fires live against MCP tool-callers.
- Indirect / cross-context injection — payloads via retrieved content, scored by out-of-band action so quoting isn't mistaken for obeying.
- MCP server scanning (breadth) — HTTP/SSE transport, tool poisoning,
insecure auth, and injection-sequence detection.
- Injection-driven sequences: drive a live multi-step agent to
generate traces (detection logic shipped; live-agent driving exceeds the
current
list_tools/call_tooltarget contract).
- Injection-driven sequences: drive a live multi-step agent to
generate traces (detection logic shipped; live-agent driving exceeds the
current
- CI gate —
--fail-on <severity>blocks a pipeline on findings. - SARIF output —
--format sariffor native GitHub code scanning. - Expanded payload corpora per category; MITRE ATLAS mapping alongside OWASP.
pip install -e '.[dev]'
pytest -qThe suite runs the full probe set against the vulnerable mock and asserts the known categories fire — a controlled baseline for validating detection.
See docs/CHANGELOG.md.
Apache-2.0. See LICENSE for the full text and
NOTICE for attribution.
