A multi-agent system is a group of models that pass work to one another. One agent triages, hands off to another that looks up your account, which hands off again to one that can issue a refund or run a query. Each pass is called a handoff, or a delegation, or an edge, depending on the framework. And each one is a decision made by a model reading text, then honored by the runtime with no authorization check behind it. That is the soft joint. It is where control and trust move between agents, and it is exactly where the bugs live.
Multi-agent systems do not fail because any single agent is dumb. They fail at the seams between agents, where each one assumes the other did the checking.
why nowSingle-agent security does not cover this
Prompt injection, jailbreaks, tool abuse, data exfiltration: the single-agent threat model is well travelled. But none of those bugs describe what goes wrong when one agent hands control to another, because in a single-agent system there is no handoff to attack. The interesting failures of the systems shipping right now are architectural, not about any one model being fooled.
One model, one context, one set of tools. The boundary is you versus the model, and the playbook for that boundary is mature.
Several models passing control and a shared context between them. Every pass is a trust decision nobody authorizes, and that is brand-new attack surface.
Add a second agent and you inherit a whole surface at once: a value one agent writes that another reads as authority, a routing decision an attacker can name, a retrieved document a peer trusts like a first-party instruction, a partner whose self-declared powers nobody clamps. This series walks that surface one exploit at a time.
the seamsWhere the exploits live
The eight exploits are not eight unrelated tricks. They are leaks at a handful of seams, each one an implicit trust the architecture ships with by default. Learn the seams and the individual attacks stop feeling like a grab bag:
- ❹The carried context. The shared state each hop reads as memory, so a value smuggled into it is read as a decision the system made.
- ❺The user message. Untrusted text treated as instructions rather than data, so control markers in a chat message get obeyed.
- ❻The handoff route. The next agent is a free-form value the model emits, dispatched with no allow-list at the join.
- ❼The tool call. A tool runs because text asked for it, with no per-agent capability actually enforced.
- ❽Retrieval. Content a peer fetched is trusted like a first-party instruction: the indirect-injection channel.
- ❾Registry and federation. A named agent, or a cross-org partner's self-declared card, is trusted without authenticating identity or authorizing capability.
the labWhat every post is built on
The whole series runs against one scenario, a bank support desk called VulnBank: three agents on a rising privilege gradient, from a user-facing triage agent that holds nothing, up to an operations agent that can move money and read files, plus a retrieval peer that turns the chain into a mesh and, later, a cross-organization partner reached over a federation boundary. It is offline, standard-library-only Python, no API keys and no network, with a single policy switch that flips the same code between three builds:
You can read each post on its own, or clone the lab from GitHub and watch every attack succeed and then get blocked in your own terminal.
python run.py # run every exploit against both builds, see the result matrix python metrics.py # the measured harness: block rate per policy
the methodHow each post is structured
Every part in the series follows the same shape, so you always know what you are getting: the mechanism first, then proof it works, then proof the fix works, measured rather than asserted.
- The payload. The exact string or move, and the seam it targets.
- Vulnerable and hardened traces. The same step shown succeeding, then blocked, toggled in place.
- A threat model. The asset, the STRIDE category, the trust boundary, the adversary's reach.
- The structural fix. A control that does not read the attacker's wording.
- A measured block rate. Not "this should help": a number from the harness.
the lensThreat modeling, in every post
This is not a list of party tricks, it is a threat model built one exploit at a time. Every post carries its own slice: the asset at risk, the STRIDE category it falls under, the trust boundary it abuses, the adversary and exactly how much reach they need, and the OWASP LLM entry it maps to. Read end to end, those slices compose into a full STRIDE view of the agent mesh and an attack tree, several independent routes to the same crown jewel, which is how you see why closing one branch never closes the goal.
Asset, STRIDE category, trust boundary, adversary reach, and OWASP mapping, on every single page.
A STRIDE table for the whole mesh, attack trees to each crown jewel, and an audit question you can take to your own system.
the findingWhy filtering is a speed bump, not a control
The result that ties the series together is a measurement. Take the natural-language injections the attacks use, grow them into a 185-phrasing corpus across plain, obfuscated, and paraphrased forms, and score each build. An input filter, even granted flawless de-obfuscation, tops out at 43% (81 of 185) because the rest is pure paraphrase no fixed vocabulary can enumerate. The structural controls reach 100%, because they never read the wording at all.
Detection scales with the attacker's vocabulary. Prevention does not.
the defenseEight fixes, three ideas
All eight exploits, and all eight named fixes, collapse into three controls. The series earns each one attack by attack, but here is the whole argument in advance:
Data is not instructions
Never act on control content that arrived in untrusted text, carried notes, or a retrieved document. Enforced by provenance, not by pattern-matching the phrasing.
An explicit, least-privilege agent graph
Handoffs follow an allow-list, each agent holds only the capabilities it needs, the registry is authenticated, and a directed edge cannot be taken twice.
Authenticate identity and authorize capability, separately
Verify a partner's card signature, then clamp its declared powers to a local grant, because a signature proves who signed, not what they may do.
the seriesEight exploits, one seam
From a single-marker context injection, through the four-agent mesh, to the cross-organization federation boundary. Part 1 is live now; the rest release on a rolling schedule rather than all at once.
Part 1 (A1) is published. A2 through A8 land one at a time, following the arc from a simple chain, through the mesh (A5, A6), to the federation boundary (A7, A8). This page updates as each goes live, so bookmark it to follow along.
who this is forRead it if you build with agents
If you are wiring up an agent framework, reviewing one for security, or just trying to reason about what your multi-agent stack actually trusts, this series is written for you. You do not need the lab to follow the arguments, but it is there if you want to run every claim yourself. Each post ends with the audit question that surfaces its bug in a real system, so by the end you have a checklist, not just a story.