Attack lab

RAGProbe attack lab

Attacks that go beyond a single prompt — and where the defences do and do not hold. Everything below is a real run of the code in this repository, rebuilt on every push.

100%→0%of attacks work on the demo app: grade F before, A after

The demo: Acme Helpdesk, grade F to A

A fictional support assistant, built to be vulnerable. Scan it as it ships, add defences one step at a time, rescan. Every number is a real run.

$ ragprobe demo
Step 1: As it ships - no defences
none
F
100% of attacks work0% stopped
wrongly blocks 0% of harmless requests
Step 2: Add a hardened system prompt
hardened
F
100% of attacks work0% stopped
wrongly blocks 0% of harmless requests
Step 3: Add an input screen (injection detector)
detector
A
0% of attacks work100% stopped
wrongly blocks 0% of harmless requests
Step 4: Turn on the layered defence
layered
A
0% of attacks work100% stopped
wrongly blocks 0% of harmless requests
Why step 2 changes nothing: this simulated victim ignores prompt wording by design, so the gain comes from the code-level layers (input screen, tool policy, output filter), which do not depend on the model behaving. Against a live model prompt hardening can help — measure it with ragprobe security run --victim live.

Poisoned tools (MCP)

The attacker's text hides in a tool instead of the user's message: a tool description that orders a harmful action, a tool that turns malicious after you approve it (rug-pull), or a tool result that tells the agent to leak data through another tool. 5 scenarios against a worst-case obedient agent; an attack succeeds when a planted code leaves the sandbox by email.

$ ragprobe security mcp
DefenceData leaked95% CIPoison withheldBlocked by policy
none
100%
57–1000/50/5
detector
40%
12–773/50/5
tool-policy
0%
0–430/55/5
layered
0%
0–433/52/5
The lesson: a rule about what a tool may do (mail only to known contacts) stops this whole class however the poison arrives. A screen that reads the words only catches some of it, because the harm is an action, not text.

Multi-turn attacks

The attack is built over several turns and each turn looks harmless on its own: plant two halves of a code in a “memory game”, then ask for them joined, or climb from a friendly “audit” to a direct request. 4 chains against the real simulated victim, which reads the whole history just as a model's context does.

$ ragprobe security multiturn
DefenceAttack success95% CIA turn withheld
none
100%
51–1000/4
detector
75%
30–952/4
layered
50%
15–852/4
An honest gap: the layered defence does not stop every chain. These still get through, because no single message is suspicious and the assembled marker is not a secret the output filter redacts:
mt-role-escalationmt-split-marker
Closing this needs a view of the whole conversation, not just each message — reported here, not hidden.

Hidden injections

The words are still readable to the model, but a keyword detector no longer matches them: letters separated by invisible characters, look-alike letters from another alphabet, stretched spacing, or the instruction buried in HTML. The bars show how many of 6 injections the detector still catches — as received, and after the text is cleaned up first.

$ ragprobe security obfuscation
Hiding trickCaught, as receivedCaught, after cleaning
no obfuscation (baseline)
83%
83%
zero-width
0%
83%
homoglyph
0%
83%
html-comment
83%
83%
html-hidden
83%
83%
markdown-alt
83%
83%
spaced-out
0%
83%
leetspeak
0%
83%
The lesson: tricks that keep the words intact are caught anyway, but the character-level ones drop the detector to 0% until the text is normalised first — which restores it, and leaves ordinary sentences untouched.

Generated by RAGProbe · reproduce with ragprobe security lab

Step 3 of 5