Every Step Was Allowed. The Sequence Was the Attack.

TV
Thiago Victorino
6 min read
Every Step Was Allowed. The Sequence Was the Attack.

“Every step is allowed. The sequence is the attack.” Ben Hirschberg, CTO and co-founder of ARMO, wrote that line in September 2026 to describe prompt injection through tool output, and it is the most compact statement of the problem I have read. Picture the shape, my example rather than his: an agent reads a Jira ticket whose body tells it to fetch a file, summarize it, and post the summary to a URL. Reading Jira is allowed. Fetching the file is allowed. Posting to a URL is allowed. Each call clears its check. The chain exfiltrates data.

We have already covered why models follow instructions found in content (roles are style, not structure), how a poisoned package carries an instruction into the loop (the supply-chain weapon), and how a plugin can forge the consent step (the Vercel plugin case). This essay takes the next step: why the two common controls both miss this class, and what control has the right shape.

Two screens, two moments

Two checkpoints anchor most of the agent security designs I have reviewed.

The first is input screening. Before content reaches the model, a classifier or a set of rules looks for injection patterns: “ignore previous instructions”, encoded payloads, suspicious URLs. This screen treats the tool result as text and asks whether the text looks hostile.

The second is action screening. Before the agent executes a tool call, a policy engine asks whether this agent may call this tool with these arguments. Allowlists, scopes, human approval for the dangerous ones. This screen treats the call as a request and asks whether the request is authorized.

Each checkpoint observes one moment of the loop. Input screening sees the moment content arrives. Action screening sees the moment a call leaves. The attack lives in the space between them, in the causal link from “this content arrived” to “this call left”. Neither screen has that link in its field of view.

Run the Jira example through both. The ticket body reads as a plausible task description; a classifier tuned to catch “ignore previous instructions” has nothing to catch. The three calls that follow are each within the agent’s authorized tool set. Input screen: green. Action screen: green, green, green. The data is gone.

The blind spot comes from where the controls sit, and tuning does not move them. You can make the input classifier stricter and you will pay for it in false positives on legitimate tickets that happen to contain instructions, because tickets are instructions. You can make the action policy tighter and you will remove capabilities the agent needs to do its job. Neither adjustment gives the control a view of the sequence.

Who wrote this field

Before any new control, Hirschberg makes an argument about inventory that I think is the more useful half of his post.

Teams classify trust at the level of the tool. Jira is an internal system, so its output is trusted. The database is ours, so its rows are trusted. The file share is behind SSO, so its contents are trusted. This is how tool lists get labelled, and it is the wrong unit.

His line: “Tool output fails that test far more often than the tool list suggests. The tool is Jira, and the field is a ticket body a customer typed.” The tool is internal. The field is not. A ticket body is free text authored by whoever opened the ticket, which in a support workflow means a customer, and in a public project means anyone with an account. The trust level of the field is the trust level of its author, and the tool’s trust level tells you nothing about that.

The artifact he proposes is a relabelling table with four columns: system, field, who writes it, trust level. Fill it in for every free-text field an agent reads; numbers, enums and structured identifiers are out of scope in his version. The exercise is mechanical and it is revealing, because the third column is where the assumption breaks. Jira ticket title: written by the reporter. Jira ticket body: written by the reporter. Jira comments: written by anyone with comment permission. Jira status: written by the workflow engine. Suddenly one “trusted internal tool” decomposes into fields with four different authors and at least two trust levels.

Do the same for the CRM, the ticketing system, the wiki, the shared drive, the email inbox, the database tables that ingest from web forms. Any field a person outside the security boundary can write is untrusted input to the agent, whatever system stores it.

That table is also a data-provenance inventory, which is a document your compliance function will eventually ask for under a different name. Build it once, use it twice.

Precedent as a runtime signal

Now the control. The signal Hirschberg proposes is what he calls a precedent gap: a tool call, or an argument to a tool call (a destination, a path, a table), that this particular agent has never used before. Judged against a baseline built from that one agent’s history in that one cluster.

Look at what this observes that the two screens do not. The action screen asks “is this call permitted?” The baseline asks “has this agent ever made this call before?” Those are different questions with different answers. An HTTP POST to an external URL may be permitted, because the agent legitimately posts to three internal webhooks. A POST to a fourth URL, one that appears in no prior call from this agent, is permitted and unprecedented at the same time. The permission check passes. The precedent check fires.

The signal fits the attack’s shape. An injected instruction typically drives the agent somewhere it does not usually go: a new destination for exfiltration, a new path for reading a secret, a new table for a write. The attacker chooses the destination. The attacker does not get to choose the agent’s history. The baseline is the one thing in the loop the injected text cannot rewrite. Hirschberg names the exception himself: an attacker who knows a baseline exists can work inside it, and he calls that the hardest case.

The choice of scope matters and Hirschberg is specific about it: one agent, one cluster, because a baseline that narrow cannot be downloaded, studied offline, or practiced against before the attempt. My reading of why it also sharpens the signal: a baseline built across all agents is too permissive, because some agent somewhere has called almost everything. A baseline built across all clusters blends production and staging behaviour. The narrower the population, the sharper the signal.

He also proposes an audit-to-enforce progression, and this is the part to take seriously if you have ever tried to ship a behavioural control. In audit, deviations are recorded against the baseline and nothing is blocked; his rule is to run in audit until the deviation rate settles, then enforce. What I would add: watch what the agent does that it has never done before, because some of it will be legitimate growth, a new integration, a new customer’s data path, and those cases belong in the baseline before enforcement starts. Enforcement on day one against an unmeasured baseline is how behavioural controls get switched off.

The vendor caveat

ARMO sells a product in this space, and the post is vendor content. I am citing the framing. A per-agent behavioural baseline is a control you can build with a log pipeline and a query. The concept stands independently of who packaged it. Evaluate any product on its own evidence.

Do this now

Three steps, in this order, this week.

Build the relabelling table. For one production agent, list every free-text field it reads. Four columns: system, field, who writes it, trust level. Label by author. Any field a customer, a vendor, a public user, or an unauthenticated form can write is untrusted, and the agent needs to be told so in its own instructions.

Pull the agent’s call history and look for firsts. Take the last month of tool calls from one agent. For each call, ask whether the tool and its key arguments (destination, path, table) appear anywhere earlier in that agent’s history. Every first is either a legitimate change you can explain or a candidate incident. If you cannot produce this list from your current logging, that is the first finding.

Add precedent to the audit dimension before you add it to enforcement. Log unprecedented calls as a distinct event class. Review them until you can explain each one. Then decide what to alert on and, later, what to block.

The two screens you already have are worth keeping. They catch the crude attacks. What they cannot see is the shape formed by a series of individually acceptable moves, and the agent’s own history is the one reference that shape stands out against.


This analysis synthesizes Prompt Injection Through Tool Output (ARMO, Ben Hirschberg, September 2026).

Victorino Group helps engineering teams build the provenance inventory and the per-agent baseline that turn agent tool calls into an auditable sequence. Let’s talk.

All articles on The Thinking Wire are written with the assistance of Anthropic's Opus LLM. Each piece goes through multi-agent research to verify facts and surface contradictions, followed by human review and approval before publication. If you find any inaccurate information or wish to contact our editorial team, please reach out at editorial@victorinollc.com . About The Thinking Wire →

If this resonates, let's talk

We help companies implement AI without losing control.

Schedule a Conversation