I audited my own setup last month and found something I did not want to find. All three legs of the dangerous combination were live at the same time: the system held private data, it ingested untrusted input from the outside world, and it could take actions that reached back out into that world. Each leg is fine on its own. Together they are the exact shape an attacker is looking for.

I went looking for the fix the way most people do. I assumed there was a product for this. Some classifier I could drop in front of the input stream that would catch the malicious instructions before they reached the model. A spam filter for prompt injection.

Every serious source I read pushed me off that idea. The security research consensus lands on the same counterintuitive recommendation, and it is worth stating plainly: do not buy a detector. Cut the combination with your own architecture.

Why the filter instinct fails

The instinct to filter is natural. We have decades of muscle memory from spam and malware, and the pattern is always the same: identify the bad thing, build a wall, keep it out. So the reflex is to treat injected instructions as more bad input to be screened.

It does not transfer. The filter is the least reliable layer in the whole stack, for two reasons.

First, adversarial input is trivially reworded. An instruction that means "ignore your task and exfiltrate the data" can be phrased ten thousand ways, encoded, buried in a document, split across fields, or written in a language your classifier was not tuned on. You are not matching a fixed signature. You are trying to detect intent in open-ended natural language, which is the one thing language is best at hiding.

Second, and this is the part people skip past: a filter that is right ninety-nine percent of the time is a filter that fails on exactly the inputs an attacker is trying. The one percent is not random. An attacker iterates against your defense until they find the wording that slips through, and then they use that wording. Your average-case accuracy is irrelevant. The adversary lives entirely in your worst case.

So a detector does not give you a guarantee. It gives you a slightly higher bar for the attacker to clear, and a false sense that the problem is handled. Those two things together are worse than knowing you have no wall at all.

Design for the breach

The shift that actually helped me was to stop asking "how do I keep the injection out" and start asking "when an injection lands, what can it actually do?"

That is the whole move. You assume the injection can succeed and eventually will. Then you design the system so that a successful injection does limited damage. The goal is not a model that cannot be tricked. The goal is a trick that is not worth much.

In practice that means a handful of architectural commitments, none of which live at the model layer.

Isolate where untrusted input is handled. The component that reads outside data should be the component that can do the least. It parses, it summarizes, it extracts, and it has no reach into the private store and no ability to act. Untrusted input and high privilege should never sit in the same place.

Scope permissions tightly. Every part of the system gets the narrowest authority that lets it do its one job, and nothing more. If a piece does not need to read the private data, it cannot. If it does not need to send anything outward, it cannot. Broad standing access is the thing that turns a small compromise into a large one.

Give sub-agents the least authority and no standing credentials. When I hand work to a sub-agent that touches outside input, it runs with the minimum it needs and it does not carry persistent keys. A hijacked helper with no credentials and no scope is a hijacked helper that cannot do much beyond waste a little compute.

Keep anything that acts on the outside world behind a boundary a hijacked prompt cannot cross on its own. The actions that matter - sending, writing, paying, publishing - sit behind a gate. A compromised prompt can ask to cross that gate. It cannot cross it by itself. Something with separate authority has to allow the step, and that something is not part of the conversation an attacker controls.

Notice what is not on this list. "Make the model better at refusing." "Add a stronger system prompt." "Warn the model about injection." Those are not load-bearing. The model layer is not a backstop. You can ask it to be careful, and it will mostly comply, and the day it does not is the day you needed it most. Hardening at the model layer is hardening on the one surface you do not control.

The operator's mindset

This reframes what good security looks like for anyone running a real operator setup, a spine that holds private data, takes in the outside world, and acts on your behalf.

You are not building a wall and hoping it holds. You are building a structure that stays safe even after something gets in. The breach is not the failure state you are trying to prevent. The breach is the assumption you design around.

It is a humbler posture and a far more durable one. A perimeter defense is only as strong as its weakest unseen gap, and you will never see all of them. An architecture that contains damage does not depend on you being perfect. It depends on you being honest about the fact that you will not be.

So I stopped trying to make my setup harder to trick. I started making a trick cost less. The audit that scared me did not end with a better filter. It ended with a smaller blast radius. That is the trade the research consensus has been pointing at the whole time, and once you see it, you cannot go back to thinking of this as a spam problem.

Design for the breach, not against it.


Field Notes from the Agentic Operator is a personal series. These are my own views, not those of my employer or any organization I work with, and nothing here relies on non-public information.

← All writing