Prompt Injection: Limit What a Compromised Agent Can Reach
Prompt injection happens when text the agent reads (a web page, an email, a document, a tool result) contains instructions the model follows as if they came from you. It’s first on OWASP’s list of LLM application risks, and there’s no known complete fix. Models can be trained to resist it, and input filters catch known patterns, but neither is reliable against a determined attacker. Both are probabilistic defences against an adversary who can keep trying.
So the practical question isn’t “how do we stop injection?” but “what happens when it works?” Architecture and permissions can reduce the actions an attacker can cause. They do not make the remaining consequences harmless.
The design check: the lethal trifecta
Simon Willison’s lethal trifecta is a useful check for a data-exfiltration path. An agent is exposed to data theft if it combines all three of:
- access to private data, such as your documents, customer records or credentials;
- exposure to untrusted content: anything an outsider could have written, including web pages, inbound email, public issues and uploaded files;
- a way to send data out: HTTP requests, email, creating a public comment, even rendering an image URL.
With all three, an injected instruction can say “read the secret and send it here”, and the agent can comply. Removing a leg can interrupt that path, provided every alternate route is also controlled. It does not prevent output manipulation, misuse of an allowed action, or exfiltration through an overlooked channel. Meta’s Agents Rule of Two generalises this: within one session, an agent should have at most two of (untrusted input, access to sensitive systems or data, ability to change state or communicate externally), unless a human approves.
Apply it per session, not per product. A support agent can read inbound email and search internal docs, as long as it can’t reach the outside world in the same session. Replies go out through a reviewed step. A draft itself can contain stolen data or hostile instructions; a second model that sees only the draft is not automatically a safe boundary.
Architecture patterns that separate reading from acting
A 2025 paper, Design Patterns for Securing LLM Agents against Prompt Injections, sets out several patterns. Three are practical for most teams:
Plan, then execute. The agent fixes its plan (which tools, in which order) before it reads any untrusted content, and code enforces that plan. Injected text in a tool result can change the data flowing through the plan, but not add new actions. This suits workflows where the steps are predictable.
Dual model (quarantine). A privileged model plans and calls tools but never sees untrusted text directly. A quarantined model reads untrusted content and returns results as values (a label, a number, a structured extraction) that the privileged side handles as data, not as instructions. Google DeepMind’s CaMeL develops this idea further, tracking where each value came from and what it’s allowed to flow into.
Context minimisation. Strip untrusted content down to what the next step needs before it reaches a model with tools. An email triage step needs sender, subject and a category, not the full HTML body with hidden text.
These patterns cost flexibility. That’s the trade: you give up some of what an unconstrained agent could do in exchange for fewer permitted paths from untrusted input to consequential actions. Validate those paths in code; structured model output is still untrusted.
Controls for when the patterns don’t apply
Many agents need to read untrusted content and act in the same loop. Coding agents reading a repository’s issues are one example. For those, stack controls so that no single failure is enough:
- Scope credentials to the task. The agent’s permissions define the worst case. An agent triaging issues needs to read issues and add labels, not push to
mainor read secrets. Scope the token or role that narrowly, and the direct write impact is limited to labels. Read access can still expose confidential issues, and labels may trigger downstream automations, so include both in the review. - Allowlist egress. If the agent’s environment can only reach the specific hosts its tools need, “send the data to attacker.example” fails at the network layer, whatever the model decides. This includes blocking arbitrary image and link rendering in any UI that displays agent output, a classic exfiltration channel.
- Put approval gates on consequential actions, and show the approver the exact action and arguments, not the agent’s summary. An injected instruction can shape the summary.
- Check tool arguments in code. A
send_emailtool can derive allowed recipients from trusted ticket state, then validate recipients, attachments and body sensitivity. An attacker already in the thread remains a possible recipient of leaked data. - Keep untrusted content labelled and delimited in the context, and tell the model it’s data. This helps, and you shouldn’t rely on it.
- Log blocked actions and alert on them. Repeated forbidden calls warrant investigation. They can also come from configuration errors or misunderstood tool descriptions.
Test it like an attacker
Add injection cases to your eval suite: documents, issues and tool results containing instructions to call tools, reveal the system prompt or contact an external address. Assert on actions (the forbidden tool was not called, the egress request was blocked), not on whether the model “noticed” the attack. Run them on every prompt and model change. Resistance varies a lot between models and versions.
Where this stops
None of this makes an agent immune. Injection can still skew the agent’s outputs, bias a summary, or waste its budget. Write the release claim narrowly: which forbidden actions were blocked, under which credentials and egress rules, and which channels remain untested. Include approval replay and legitimate-recipient exfiltration in the test plan. The Guardrails pack linked below supplies review material; it does not enforce these controls for you.