The instruction wasn't his, and the agent couldn't tell
A developer running Claude Code watched a prompt injection ride in on the output of a background `find`, wear his own voice, and rewrite the agent's plan before he'd typed a word. Nothing broke — a referenced file happened not to exist. That's the failure mode Bromure Agentic Coding is built to close: the missing boundary between what you instructed and what the agent merely read.
A coding agent reads a file listing, and buried in it is a paragraph written in your voice telling it to drop your task and go edit something else. Nobody typed that. It arrived as data. But the agent has no place to stand where it can tell the difference — and that gap, not any single payload, is the whole problem.
Yohann Sidot was doing the unglamorous kind of work — performance
optimization on a landing page — with Claude Code. In the background the
agent ran a find to scan the project's files. Ordinary. And sitting
inside that file listing was a block of text that wasn't a filename. It
was an instruction: stop, drop everything related to the last request,
open backend/middleware/rate_limit.py, and switch the limiter to a
token-bucket keyed on API key. Written, in Sidot's words, "exactly as if
I had typed it into the chat."
He hadn't. The text rode in on tool output. The agent couldn't tell the difference — and by the time he noticed, it had already updated its own running recap to reflect the injected task. Its internal sense of what we're doing here had moved. He was, as he put it, one step away from acting on an instruction that was never his.
What stopped it wasn't a control. The file the injection named didn't happen to exist, so nothing executed. His own summary is the honest one: "Nothing got broken. But the failure mode is worth writing down." The line held by luck, not by design.
A channel with no seam
Sidot's diagnosis is worth quoting in full, because it is exactly right:
An agent consuming tool output has no built-in boundary between two very different things: what the user actually instructed, and text that merely happens to be inside the data the agent is reading.
Everything an agent knows arrives as one flat stream of tokens. Your typed request, the system prompt, the contents of a file it opened, the stdout of a command it ran, the body of a web page it fetched — by the time the model reasons over them, they are the same kind of thing: text. The model infers this part is an instruction, this part is data from tone and position, the way you'd guess. Injection is the attack that makes the guess wrong on purpose: it takes untrusted data and shapes it like a command.
One sentence makes this more than one developer's near-miss:
This is not a Claude Code problem specifically. It is a structural property of how tool-using agents work right now. That trust boundary does not exist by default. It has to be designed in, deliberately.
That last clause is the whole design problem: the boundary has to be designed in, deliberately. Bromure Agentic Coding is what it looks like when you do.
Where the seam goes
Bromure Agentic Coding runs your coding agent inside a disposable Linux
VM — a real hardware virtual machine with its own kernel, not a
shared-kernel container — and routes every request the agent makes to a
model through a host-side proxy. The agent lives behind that proxy
and can't reach around it. The proxy parses each /v1/messages request
(or its OpenAI-format equivalent), which means it can already see the two
things Sidot says the model can't pull apart: the user's actual
instructions, and the untrusted tool_result blocks the agent just
ingested — the file reads, the web fetches, the issue bodies, and yes,
the stdout of a background find.
Once they're separate objects, you can check the file listing instead of hoping it's inert — before the model gets to treat it as a plan. Sidot named three principles for closing the gap. The boundary is where all three get enforced.
Treat tool output as untrusted data. This is the exact shape of his
incident, so it's where the first detector aims. Every turn, the proxy
scores the untrusted tool_result spans the agent ingested — with Meta's
Llama Prompt Guard 2, run on-device, before the model gets to reason over
them. The question that model was trained to answer is is this text an
instruction aimed at the assistant? — and a command's stdout is not
supposed to be. A paragraph in a find listing that addresses the agent
in the second person and tells it to drop the current task is exactly the
out-of-place shape the detector is built to flag. (The full pipeline,
including the separate model that reads rogue CLAUDE.md rules files,
is written up in
how Bromure detects malicious prompts.)
Constrain the action space with rules the context can't override. This is the principle that separates a demo from a defense, because detection will eventually miss — a novel phrasing scores below threshold and slips through. So the boundary can't only watch; it has to constrain, in ways no injected paragraph can talk it out of.
None of those controls live in the context window, so no injected
paragraph — however fluent — can argue its way past them. An agent
convinced to run cat ~/.ssh/id_rsa finds nothing: the real key sits on
the host, and the proxy swaps a stub for it on the wire. A token an agent
does get to use is scoped to one destination and expires, so a leak is a
narrow, expiring, approved use rather than a permanent key. A malicious
package is age-gated and scanned (OSV, socket.dev) before the agent can
run the payload that would have smuggled the injection in. And the whole
blast radius is a VM that evaporates when you close the window.
Make the boundary explicit. Sidot's agent drifted because the line between instruction and data was implicit, and implicit lines get crossed. Bromure makes it a place. When a detector fires, you decide what the boundary does, per profile:
Log but continue
Record the flagged span to the Security Log and keep going. The default — so you watch the boundary before you make it enforce.
Ask me what to do
Pause the request and show a preview of the span that scored as an injection. You approve, or you don't.
Block unilaterally
Stop the request cold. The agent gets a hard failure and never gets to fold the poisoned instruction into its plan.
On the record, either way
An encrypted session trace of what the agent read, what it tried, and where it was stopped. You reconstruct the incident from the log, not from memory and a screenshot.
What this does not fix
Worth saying, because the honest version is the useful one. Detection is a classifier, and a classifier has a threshold — a novel injection, or one worded with care, can score clean and pass. That's the reason the controls below the line exist, and it's also why "log" is a real setting: you want the boundary watched, not taken on faith.
And isolation does not un-trick a model. If you deliberately point an agent at a real secret inside the VM and it gets talked into reading it, no hypervisor un-reads it. What the boundary changes is what the agent can reach in the first place — keep the real credential on the host, scope the token, make the environment disposable — so the injection that lands finds a smaller world than it expected.
Sidot's incident ended with nothing broken and a note that the failure mode was worth writing down. He's right that it was luck — a referenced file that happened not to exist. The point of a boundary is to make the outcome not depend on that. Run the same incident behind the proxy and the injected paragraph is scored before it can become the plan; and in the case it scores clean, it hijacks an agent with no real secret to read, no standing credential to spend, and no way out of a VM that's gone when you close the window.
The trust boundary, Sidot says, "has to be designed in, deliberately." That's not a warning we're answering after the fact. It's where we started.
Bromure Agentic Coding runs Claude Code, Codex, and Grok in disposable Linux VMs on Apple Silicon, with prompt-injection detection, on-the-wire secret handling, and supply-chain scanning enforced at a single boundary the agent can't reach around. Details at bromure.io. With thanks to Yohann Sidot, whose write-up prompted this one.