Attacks that spread between agents
When agents talk to each other, a single poisoned message can travel through a system that has no idea it is under attack.
In 60 seconds
Attacks that spread between agents
When agents talk to each other, a single poisoned message can travel through a system that has no idea it is under attack.
Failure modes specific to teams of agents
| Name | What happens | Why it is hard to spot |
|---|---|---|
| Injection relay | A tainted message is forwarded inward as trusted | Each hop looks like normal internal traffic |
| Permission pooling | Individually safe agents combine into an unsafe chain | No single agent looks over-privileged |
| Consensus theatre | A critic agent approves because it read the same poisoned text | It creates a paper trail of "review" that means nothing |
| Runaway loops | Agents call each other until budget or rate limits blow | Looks like activity, not failure |
| Blame diffusion | The bad action is the last step of a long, reasonable chain | Every individual step passes review |
Design rules that hold up
- 1
Taint labels travel with data
Every message carries where its content originally came from. Tainted content can never trigger a write action without a human. - 2
Structured messages only
Agents exchange typed fields, not free prose. A JSON object with three named string fields is a much smaller doorway than a paragraph. - 3
One internet-facing agent
Exactly one component touches untrusted content, and that component has zero tools beyond reading. Everything else lives behind it. - 4
Look at the union of powers
Draw the whole graph and ask what the most damaging path through it is. Design against that path. - 5
Hard budgets everywhere
Steps, time, spend, per-agent and system-wide. Loops are guaranteed eventually; make them cheap. - 6
One trace, one ID
Every hop shares a correlation ID so you can reconstruct the whole chain after the fact.
Watch and read more
Lab
Taint tracking across a multi-agent graph, measured in hops.
The problem
from dataclasses import dataclass
@dataclass
class Message:
content: str
taint: str | None # None = trusted; else the origin
hops: int = 0
def forward(self, content):
return Message(content, self.taint, self.hops + 1)
def can_authorise(msg: Message) -> bool:
return msg.taint is None # tainted data never authorises a writeYou are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1The Planner summarises the Reader's output. The summary contains no attacker words. Is it still tainted?Reveal
Q2Your system needs tainted research to trigger a purchase. Design it so that is safe, or prove it cannot be.Reveal
Questions people ask
Should agents talk in natural language at all?
It is convenient and it widens the doorway. Structured messages between agents are meaningfully safer. Save natural language for the boundary with humans, where it earns its keep.
How do I test a multi-agent system?
Inject at every entry point and see how far the taint travels. Your test is not "did an agent get fooled" — it is "how many hops before something irreversible happens." That number is the thing to drive down.
Is one big agent safer than five small ones?
Often, yes, if the small ones collectively hold more power. One agent with three tools beats five agents holding twelve tools between them. Count the union, not the parts.
What about agents from different companies talking?
Then you have crossed a trust boundary with someone else's security posture on the far side. Treat every inbound message as fully untrusted content, exactly like an email from a stranger — because that is precisely what it is.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.
Sign in with your phone number to take the test and save your progress