
Anatomy of an Agent Loop
Yesterday we established that agents broke the threat model because there is no boundary between data and instructions inside a context window. That is the why. Today is the where.
If you want to attack an agent, or defend one, you need to know what actually happens between the moment a user types a request and the moment something changes in the world. Most people picture a black box with a prompt going in and an answer coming out. That picture is why so many deployments ship with holes in them.
The real thing is a loop. Six stages, running over and over until the agent decides it is done. Every stage is a place where an attacker can stand. By the end of this post you should be able to look at any agent product and name its attack surface without reading a single line of its source code.
The loop, stage by stage
Here is the cycle in full. Read it once, then we will take each stage apart.
ASSEMBLE build the context window
INFER model decides: answer, or call a tool
SELECT choose which tool and with what arguments
EXECUTE the tool runs, somewhere, as some identity
INGEST tool output goes back into the context window
DECIDE loop again, or stop and answer
Stages 2 through 6 repeat. A single user request can run this cycle twenty times. Each iteration grows the context window with content the user never saw and never approved.
That last sentence is the most important one in this post. Hold onto it.
Stage 1: Assemble
Before the model sees anything, an orchestrator builds a single flat block of text. Typically it contains:
- The system prompt, written by the developer
- Tool definitions, which means each tool’s name, description and parameter schema
- Conversation history from previous turns
- Retrieved context from a vector store or a file, if the product does RAG
- Persistent memory, if the product remembers things across sessions
- The user’s current message
Every one of those arrives as text in the same buffer. There is no header saying this part is trusted and that part is not. The model infers importance from position, formatting and phrasing, which is exactly the thing an attacker can control.
Where an attacker stands here. Anything in this list that the attacker can write to. If memory is writable by content the agent reads, the attacker writes to memory. If the vector store indexes public documents, the attacker publishes a document. If tool definitions come from a third party server, the attacker controls tool definitions. This is the layer where tool poisoning lives, and we spend Day 8 on it.
The detail most people miss. Tool descriptions are attacker reachable in any MCP deployment where the server is not first party. Microsoft’s team made the structural point directly: because the protocol blends instructions with data, changing a tool’s metadata can redirect the agent as effectively as changing its code. The description is not documentation. It is program text.
Stage 2: Infer
The model reads the assembled block and produces one of two things: a text response, or a structured request to call a tool.
There is no separate reasoning engine here making a security decision. There is one forward pass over a buffer that contains both your instructions and the attacker’s. Whichever is more specific, more recent, more urgent sounding, or better positioned tends to win. Researchers have spent two years trying to make the model reliably prefer the developer’s instructions, and the honest summary of that effort from Sysdig’s review of the field is that adaptive attacks bypass essentially every published defense.
Where an attacker stands here. Nowhere directly, and that is the point. There is no hook, no filter, no policy engine at this stage in most products. The decision is made inside the weights. If you are hoping to catch the attack here, you are hoping the model will notice. It will not, reliably.
Stage 3: Select
The model has decided to call a tool. Now it picks which one, and fills in the arguments.
Selection is driven almost entirely by tool descriptions, because that is the only information the model has about what each tool does. Argument construction is driven by whatever is in context, including content that arrived from a previous tool call.
Where an attacker stands here. Two places.
First, selection. If an attacker controls a description, they can write one that makes their tool look like the obvious choice for a job it has no business doing. That is tool shadowing, Day 10.
Second, arguments. Even if the tool is legitimate, the arguments may be built from attacker controlled text. A send_email tool with a legitimate purpose becomes an exfiltration primitive when the recipient field is populated from a support ticket the agent just read. The tool is not vulnerable. The tool is being used correctly, for the attacker’s purpose.
A useful reframing. Stop asking whether a tool is dangerous. Ask what the most damaging thing is that this tool can do when every argument is attacker chosen. That is the actual capability you granted.
Stage 4: Execute
The tool runs. Three questions decide how bad a compromise at this stage gets.
As what identity? Most agent deployments use a single service token with broad scope, because scoping per user is work. That means the agent’s effective permissions are the union of everything that token can reach, regardless of who asked. This is the classic confused deputy problem wearing new clothes, and it is why the GitHub MCP incident worked: a booby trapped public issue sent the agent into the user’s private repositories and back out through a pull request in the public repo, because the server’s token carried blanket access and nothing stopped it crossing that boundary.
Where? On the user’s workstation, in a container, on a shared server, inside your VPC. A coding agent executing on a developer laptop has an entirely different blast radius than a chatbot calling a REST API.
With what guardrail? Usually an allowlist of permitted commands or endpoints. Which is worth understanding precisely, because CVE-2026-22708 against Cursor showed the failure mode: an attacker poisons the execution environment so that allowlisted commands like git branch deliver arbitrary payloads, and the allowlist makes it easier by auto approving the very commands the attacker needs. An allowlist constrains the name of the verb. It does not constrain what the verb resolves to or what arguments it receives.
Where an attacker stands here. This is the payoff stage, not usually the entry stage. But it is also where the environment itself can be pre poisoned, as in that Cursor case, and where a compromised MCP server executes attacker code directly. The protocol layer has its own history here: CVE-2025-6514, a remote code execution flaw rated 9.6, was disclosed in core MCP infrastructure used by hundreds of thousands of developers.
Stage 5: Ingest
The tool returns output. That output goes straight back into the context window and becomes part of the next inference.
This stage deserves more attention than it gets, because it is the one that converts a read into a compromise.
Consider the sequence. The user asks the agent to triage support tickets. The agent calls list_tickets. A ticket body contains instructions. Those instructions are now in the context window with the same status as the system prompt. The next inference runs with the attacker’s text inside the program.
That is the entire Supabase class of incident: an attacker posing as a normal user embedded a malicious instruction inside a support ticket message, and the agent connected to the database through MCP acted on it.
Nobody approved that content. Nobody reviewed it. The user asked for ticket triage and the attacker got a turn at the prompt.
Where an attacker stands here. Everywhere. Every tool that reads external content is an injection vector: web fetch, email read, file read, database query, issue tracker, calendar, RAG retrieval, even the output of a shell command that included attacker controlled filenames.
The rule to internalize. Tool output is untrusted input, always, no exceptions. Most products treat it as trusted because it came from a tool the developer configured. The tool being trusted says nothing about the content the tool returns.
Stage 6: Decide
The model reads the updated context and decides whether to loop again or produce a final answer. If it loops, we return to stage 2 with a larger, dirtier context.
Where an attacker stands here. In the iteration count. An injected instruction that survives one loop survives all of them, and the further into a run you are, the less likely a human is reading individual steps. Long agent runs are where approval fatigue turns into approval theatre.
Mapping attacks onto the loop
Here is the whole series, placed on the loop.
| Stage | Attack classes | Covered on |
| Assemble | Tool poisoning, rug pulls, memory poisoning, RAG poisoning | Days 8, 9, 20 |
| Infer | Direct jailbreaks, instruction hierarchy bypass | Day 15 |
| Select | Tool shadowing, cross server attacks, argument injection | Days 10, 12 |
| Execute | Confused deputy, allowlist bypass, RCE in the server | Days 11, 18 |
| Ingest | Indirect prompt injection, all delivery vectors | Days 16, 19 |
| Decide | Persistence across iterations, exfil channel abuse | Day 21 |
Pin that table. When you test an agent in week three, you are not hunting randomly. You are walking six stages and asking the same questions at each.
Why the loop makes defense hard
Three structural properties fall out of the design, and none of them are bugs you can patch.
Context is append only within a run. Once attacker text enters at stage 5, it is present for every subsequent inference. There is no rollback. Some frameworks summarize or truncate long contexts, which helps by accident and hurts by accident, because the summarizer is also a model reading the same poisoned text.
Privilege does not decrease across iterations. A human who reads a suspicious email gets more cautious. An agent that reads a suspicious document gets more instructions. There is no mechanism by which processing untrusted content reduces what the agent is willing to do next. If anything the opposite happens, because the injected text is more recent and more specific than the system prompt.
The approval surface and the action surface are different sizes. A user approves a goal. The agent takes dozens of actions to reach it. Anything that inspects at the goal level cannot see what happens at the action level, and anything that inspects at the action level exhausts the human within a week.
This is why the defenses that actually work, which we get to in week four, are architectural rather than detective. The dual LLM pattern, information flow control, and capability scoping all accept that injection will succeed and constrain what a successful injection can reach. None of them try to spot the bad text.
Homework for Day 2
Take the agent you inventoried yesterday and draw its loop on paper. Literally draw it.
- Write out what goes into the assemble stage. Every source. Include memory and retrieval if present.
- For each tool, note the identity it executes as and where it runs.
- For each tool, write the worst thing it can do with fully attacker chosen arguments.
- Circle every tool whose output can contain text from outside your organization. Those are your stage 5 entry points.
- Count the maximum iterations the agent will run before returning to the human.
Now answer one question: if an attacker owned one line of text in any circled tool’s output, what is the shortest path from there to the most valuable thing in your stage 4 list?
That path is your first attack chain. Write it down. On Day 7 you will build a lab that lets you run it.
Day 3 goes under the hood of MCP itself: the transport, the handshake, the message types, and the exact fields an attacker cares about.