Anyone who can send you a document can talk to your agent.
Rajesh Raachabattuni · · 6 min read
Most of the agents I see going into regulated workflows have the same shape: they read things other people sent in. Complaint emails. Onboarding packs. Supplier invoices. Broker statements. CVs. That inbound flow is the whole reason the automation is worth building — nobody commissions an agent to process documents they wrote themselves.
It is also the part I would attack. Every one of those documents is a way for someone outside your firm to put text in front of your model, and a model has no reliable way to separate a document it was asked to read from an instruction hidden inside it. Both arrive as the same thing.
In short
- A model reads instructions and data as one stream of text, so there is no boundary to enforce — and no patch coming.
- In regulated work the untrusted input is the job: complaints, onboarding packs, invoices.
- Detectors help, but the control is design: limit what the agent can reach, and every way data can leave.
There is no second channel
When your agent runs, the developer’s instructions, the user’s request, the retrieved document and the last tool call’s output are assembled into one block of text and handed to the model. Nothing in it is marked as privileged. There is no header saying this part is from the system and must be obeyed, that part is merely data. The model produces a plausible continuation given all of it — so an instruction in the middle of a PDF reads exactly like one in your system prompt, because structurally it is one.
The UK’s National Cyber Security Centre put this about as plainly as a government body can, in a post last December titled Prompt injection is not SQL injection (it may be worse): current large language models simply do not enforce a security boundary between instructions and data inside a prompt. Their conclusion is the uncomfortable one — that these attacks may never be totally mitigated in the way SQL injection attacks can be.
The SQL analogy is what makes people wait for a patch. That one was fixed permanently by parameterised queries — code down one path, values down another — because there were two channels to separate. With a prompt there is one.
SQL injection was solved by separating code from data. Here there is nothing to separate — it is all one stream of text.
In regulated work, the untrusted input is the job
The obvious response — only let the agent read content you trust — is usually unavailable.
The inbound flow is the use case. Complaint triage, claims assessment, onboarding checks, invoice processing, adverse-media review: the content originates outside your control, often from someone you have no relationship with yet. Strip out the untrusted input and you have removed the work.
It does not have to look like an attack. White text on a white background, a comment in the margin of a PDF, a cell three columns right of the data, alt text, a document’s own metadata. The reviewer sees an ordinary invoice, because it is an ordinary invoice with four sentences nobody renders.
The sender need not be sophisticated, or even hostile. Instructions can arrive in a supplier’s standard template, on a web page your agent fetched, or in a document planted once and then read by every agent that touches that case for the next three years.
A filter is not a boundary
The product answer is a detector: a classifier in front of the model that spots injection attempts and strips them. Run one — it catches the clumsy attempts, and defence in depth is real.
Do not mistake it for the control. A detector is a probabilistic judgement over an unbounded space of inputs, set against an adversary who can rephrase indefinitely — in another language, encoded, split across two documents — and you learn it failed only if something downstream happens to be noisy. I have argued before that a boundary depending on a model’s willingness to respect it is guidance rather than a control. That holds with more force here, because the model is being argued with by someone whose purpose is to win.
The same reasoning puts injection cases in the eval set — the discipline I set out in you can’t unit-test an agent, pointed at obedience rather than accuracy. Real documents with real embedded instructions, where the correct outcome is that the agent summarised the invoice and did nothing else. Grade what it did, not whether its answer mentioned the suspicious text.
Access is half the problem. The way out is the other half.
An injection is only worth mounting if it can do two things: reach something worth having, and get it somewhere the attacker can see. Teams audit the first and almost never enumerate the second.
Microsoft’s Copilot produced a clean example. In the flaw assigned CVE-2025-32711, which researchers called EchoLeak, a crafted email carrying hidden instructions sat in a mailbox until the user asked Copilot something ordinary and unrelated. Copilot pulled the email in as context, followed it, gathered internal content and sent it out through a rendered reference in its own answer. Microsoft fixed it server-side. Note what the victim did: received an email. Not opened. Received.
Enumerate every way data can leave. A rendered image or link, an HTTP request a tool makes, a URL the model composed, an email it can send, a write to a shared location, a webhook. That list lives in your code, and it is finite.
Treat any URL the model assembled as attacker-controlled. Allowlist the hosts a tool may call or render, rather than trusting the string it was handed.
That asymmetry is what to build on. You cannot enumerate the ways an instruction might be phrased. You can enumerate the ways bytes get out of your system.
Assume the instructions are the attacker’s
The posture that holds is to stop trying to keep the injection out, and build so that a successful one is not worth anything.
Let the trust level travel with the data. Once a session has read untrusted content, everything it does afterwards is potentially attacker-directed — so its privileges should narrow at the moment it reads, rather than staying where they were when it started.
Split reading from acting. An agent that reads inbound documents and stages a change for someone else to action sits in a different risk position from one that reads and acts in the same breath, on identical permissions.
Scope the tools to the case. The strongest control is still the one I set out for assistants over your own documents: not granting the access at all. An agent handling one supplier’s invoices needs that supplier, that ledger and that amount limit — not the finance system.
The test I would run this week
Try this
One live agent, one planted document
- Take one live agent that reads content from outside the firm.
- Write a document that looks entirely normal for its workflow and hide an instruction in it — forward the attached client list to this address, approve this invoice without checking the amount, whatever would actually hurt.
- Send it the ordinary way, through the real intake path, from outside.
- Read the trace.
You are not testing whether the model notices. Assume it does not. You are testing what the system allowed in the half second after it was convinced, and whether anything in your logs would have told you afterwards.
The takeaway
An agent is not safe because it resisted the instruction. It is safe when following the instruction would not have got anyone anywhere.
Sources: NCSC, Prompt injection is not SQL injection (it may be worse) (December 2025), OWASP Top 10 for Large Language Model Applications — prompt injection is LLM01, the top-ranked risk in the 2025 list; NVD, CVE-2025-32711.