Skip to content

Repository files navigation

RedCell

CI Python 3.10+ License: Apache-2.0

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 network

RedCell confirming excessive agency on an MCP server: the ungated delete_account tool executes and is flagged vulnerable, while the auth-gated wire_transfer is refused and correctly passes

What makes it different

It 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.


Quickstart

redcell scan --demo       # scan the built-in vulnerable mock
redcell list-probes       # see the probe catalogue

A full offline scan of the built-in vulnerable mock: 20 findings across five OWASP categories, graded F

Scan 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.

Use it in CI

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

Scanning an MCP server (the headline feature)

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-header values 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

⚠️ --active genuinely executes the tools it flags — delete_account really 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.

Results

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.py

Generated 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.

GitHub code scanning (SARIF)

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.

RedCell findings in the GitHub Security tab

What it checks

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

How it's built

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 via AgentTarget. 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: subclass Probe, 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]', set GROQ_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.

Adding a probe

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()

Roadmap

  • 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_tool target contract).
  • CI gate--fail-on <severity> blocks a pipeline on findings.
  • SARIF output--format sarif for native GitHub code scanning.
  • Expanded payload corpora per category; MITRE ATLAS mapping alongside OWASP.

Tests

pip install -e '.[dev]'
pytest -q

The suite runs the full probe set against the vulnerable mock and asserts the known categories fire — a controlled baseline for validating detection.

Changelog

See docs/CHANGELOG.md.

License

Apache-2.0. See LICENSE for the full text and NOTICE for attribution.

About

Security scanner for LLM apps and MCP agents. Probes mapped to the OWASP LLM Top 10, with deep MCP/agent coverage — excessive agency, tool poisoning, insecure auth, and injection-sequence detection.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages