CaMeL: defeating prompt injection by system design, not a better prompt
Google DeepMind's CaMeL separates control flow from untrusted data, tags every value with capabilities and checks policies at tool calls. AgentDojo results and limits.
- CaMeL has the model plan as code from the user's request alone, so outside data cannot change the steps of the task
- Every value carries a tag for where it came from and who may read it, and policies check those tags at tool calls in code, not by asking the model
- On AgentDojo it completes 77% of tasks with provable security, against 84% for an undefended system
- The costs are about 2.8 times the tokens, hand-written policies, no tasks whose actions depend on outside data, and prompt injection is not fully solved
CaMeL is a Google DeepMind paper (Defeating Prompt Injections by Design, March 2025, revised June 2025) that stops trying to teach the model not to be fooled by injected text, and instead designs the system around the model so injected text cannot direct anything in the first place. Simon Willison, who coined the term prompt injection, called it the first credible mitigation he had seen that does not just add more AI.
Facts checked against the arXiv paper 2503.18813 v2, the README of the published code and Simon Willison’s write-up, as of October 2026. The code below illustrates the idea; it is not code from the paper.
Why fix it in the structure
The basics of prompt injection and layered defences are in the prompt injection article. The question CaMeL answers is why defences built from models alone fall short.
An AI detector is probabilistic. In security, catching 99% is a fail: an attacker can try indefinitely until they find the 1% that gets through. What you want is a rule enforced by code, not a rule the model is asked to follow.
The starting point: the Dual LLM pattern and its hole
In 2023 Simon Willison proposed the Dual LLM pattern:
- P-LLM (privileged) has tools and sees only the user’s request
- Q-LLM (quarantined) has no tools, reads untrusted text, and returns results as variable references the P-LLM can pass around without seeing the content
The paper’s example shows the hole. The user asks: “Can you send Bob the document he requested in our last meeting?” The plan: find the meeting notes → extract Bob’s email and the document name → fetch the document → send it. Extracting the email means the Q-LLM reads the notes. If someone planted text in the notes to swap in an attacker’s address, the Q-LLM returns the wrong address. The plan was not altered, but the data flowing through the plan was, and the document goes to the wrong person.
How CaMeL works
CaMeL (CApabilities for MachinE Learning) adds two things to the Dual LLM pattern.
1. The plan is code, run by its own interpreter. The P-LLM turns the request into code in a restricted subset of Python. A custom interpreter built on Python’s ast module runs it and records where every value came from.
2. Every value carries a capability tag saying two things:
- Provenance: from the user (literals in the P-LLM’s code), from CaMeL itself (results of transformations), or from a particular tool, sometimes down to the sender of an email
- Readers: public, or a set of users. Tools can set their own tags; a cloud storage tool can tag a document with its editors
The Q-LLM has no tools and must answer in a fixed schema, using the provider’s structured output feature. A have_enough_information field lets it say the input was insufficient, but it cannot tell the P-LLM what it needs, because that channel would become a way for an attacker to send instructions.
An illustration (not the paper’s code):
# P-LLM wrote this from the user's request alone; it never saw the notes
notes = get_last_meeting_notes() # tagged: source = tool, untrusted
req = query_quarantined_llm( # Q-LLM: no tools, fixed output schema
"Find Bob's email and the document he asked for", notes,
schema=DocRequest) # {email: str, doc_name: str}
doc = find_document(req.doc_name) # tagged: readers = document editors
send_email(to=req.email, attachment=doc) # the policy runs before this call
# policy for send_email, enforced in code by the interpreter
def send_email_policy(to, attachment):
if to.source == "user": # address typed by the user
return ALLOW
if to.value in attachment.readers: # already allowed to read this doc
return ALLOW
return ASK_USER # otherwise a human decides
If the notes were injected with an attacker’s address, req.email is tagged as coming from untrusted data and the attacker is not among the document’s readers, so the send stops for a human to decide, however thoroughly the model was fooled.
A rule enforced in code does not care what the model believes. It looks only at where data came from and where it is going.
Results on AgentDojo
AgentDojo is a security benchmark for tool-calling agents with four suites: Workspace, Banking, Travel and Slack.
| Measure | Undefended | CaMeL |
|---|---|---|
| Tasks solved (per the abstract) | 84% | 77%, with provable security |
| Successful attacks, Claude 3.5 Sonnet (949 attacks) | next-best defence (tool filter) still let 8 through | 0 |
| Successful attacks, Gemini 2.5 Pro | 300 | 0 |
| Tokens per task (median) | 1× | 2.82× input, 2.73× output |
Two injection tasks still succeed, one in Banking and one in Travel; the authors say the Travel one is not a real prompt injection, just the model printing a review that contains the injected text. Travel had the lowest utility because its tool outputs are poorly documented and the P-LLM misparses them. It improved with better models alone: Claude Sonnet 3.5 scored 25%, 3.7 scored 55%, Sonnet 4 scored 75%, with no change to CaMeL.
Worked example: what you give up
- Tasks: 77 ÷ 84 = 0.917, so CaMeL keeps about 92% of the tasks the undefended system completes, losing about 8%
- Tokens: suppose a task normally takes 10,000 input and 1,000 output tokens. Under CaMeL that becomes 10,000 × 2.82 = 28,200 input and 1,000 × 2.73 = 2,730 output, nearly three times the cost per task
- Cutting cost: the paper tried Claude 3.5 Haiku as the Q-LLM with Claude 4 Sonnet as the P-LLM; utility fell about 1% while median task cost fell about 12%
For an agent that sends email, moves data or edits documents on someone’s behalf, nearly tripling the cost to bring successful attacks in this benchmark to zero is often worth it. For a chat bot with no dangerous tools, it may not be.
Limits the paper states itself
- Prompt injection is not fully solved. The authors say so directly
- Policies must be written and maintained, a burden on users or administrators
- Approval fatigue. Ask too often and people approve everything, including the harmful action
- Tasks where the data decides the action (“data requires action”), such as “do what this email says”, are out of reach: the P-LLM cannot plan from data it never reads
- Side channels, such as indirect inference or one-bit leaks via exceptions; a STRICT mode reduces some of them
- Out of scope: attacks that do not change data flow, such as a distorted summary, or tricking the user into a phishing link
Common mistakes when borrowing the idea
- Letting the model that reads outside data also call tools. That reopens the original hole. Keep planner and reader strictly apart
- Letting the Q-LLM return free text that you then splice into instructions. Enforce a schema and validate values; an email field must be an email
- Putting the policy in the prompt instead of in code at the tool layer. A policy the model decides can be talked out of
- Asking the user every time, until the prompt becomes a button clicked without reading. Ask only when untrusted data is about to reach a new destination
- Shipping the research code to production. Its README says it may have bugs and will not be maintained
Sources
FAQ
Can I use CaMeL today? Is there a product?
Not a product. The published code exists to reproduce the paper. Its README says it is not a Google product, may contain bugs and will not be maintained. What you can use right away is the design.
How is this different from using another AI to detect prompt injection?
A detector is probabilistic, and 99% detection is a failing grade in security because an attacker only needs the 1% that slips through. CaMeL enforces in code; untrusted data cannot alter the plan by construction, and data leaving the system always passes a policy.
What is the simplest version I can build myself?
Check permissions at the tool layer in code, for example email may go only to recipients the user typed or that are on an allowlist, and tag data read from outside with its origin. You get most of the benefit without writing an interpreter.
Commands and settings were tested on sample systems. Try them on a test machine before production.