Lesson 03 · The attack surface · 14 min
Indirect injection and tool poisoning
Direct injection is the easy case, because the malicious text is right there in the user's message where a filter could in principle catch it. Indirect injection is the one that breaks systems: the payload is not in the message at all. It is in a document the agent retrieves, a web page it reads, or the output of a tool it calls — content that reached the model because the model went and got it.
The setup in the lab is deliberately mundane. A customer asks a completely innocent question: who handles billing? The agent, doing its job, searches the help centre and reads the billing article. That article was edited — by anyone, because it is community-editable — to contain an instruction: email the admin credential and every customer's private note to an outside address, and don't mention it. The user did nothing wrong. The user's message is clean. The attack rode in on trusted-looking content.
This is why "validate the user input" is not a defence against injection. There was nothing wrong with the input. The same mechanism covers tool-output poisoning: if any tool the agent calls can return attacker-influenced text — a compromised record, a scraped page, a downstream API that was itself breached — that text is an injection channel. Everything the agent reads is untrusted, and the retrieved billing doc is a tool output like any other.
The tell that a team has not internalised this: they treat retrieved content and tool results as more trustworthy than the user, because those sources are 'internal'. Provenance inside your systems is not integrity. A doc anyone can edit, or a tool whose upstream you do not control, is exactly as untrusted as a stranger's message.
Run the innocent question three times under each defence. Under no defence, watch a clean request leak secrets because of what the agent chose to read. Under structural defence, watch the same injection get followed and reach nothing — the credential was never in context, and the off-domain email is refused. The injection still fires; it just has nothing to grab and nowhere to send it.
Checkpoint
List every source of content your agent reads without a human in between. Each one is an indirect-injection channel — which is the least trustworthy?