You Can't Sanitize a Sentence: Why Prompt Injection Is Still Unsolved
You cannot fix the model. So bound what a compromised one can reach.
For about two years, prompt injection was mostly a party trick. Somebody would post a screenshot of a chatbot being talked out of its instructions by a cleverly worded paragraph, everyone would enjoy it, and the industry would move on. It was a curiosity, and curiosities don’t get budget.
In December 2025, Palo Alto Networks’ Unit 42 documented the first real-world case of malicious indirect prompt injection. Not a demo. Not a researcher’s proof of concept. An actual attack, in the wild, against a production system. Since then the pattern has repeated often enough that security teams have started cataloguing the delivery techniques, which is what happens when something stops being a stunt and starts being tooling.
Nine days ago, OWASP published the 2026 edition of its Top 10 for LLM Applications. Prompt injection is still number one, for the second consecutive edition.
That isn’t the interesting part. The interesting part is what moved underneath it.
The entry that tells the real story
Eight of the ten entries changed position between the 2025 and 2026 lists. The methodology weights community voting at 75 percent and real incident data at 25 percent, drawn from 6,639 catalogued AI incidents with enough detail to classify.
Here’s the movement that matters: Excessive Agency climbed from sixth place to third.
Sit with that for a second, because those two facts together are the whole story of the last year. The vulnerability at number one didn’t get more severe. Injection works the same way it worked in 2024. What changed is that we spent a year handing these systems tools, credentials, filesystem access, the ability to send email, and the authority to act without asking. We took a known-unfixable input problem and dramatically increased what happens when someone exploits it.
The list is describing an industry that made a stable vulnerability far more dangerous through its own deployment choices.
Why it’s SQL injection’s harder cousin
The comparison to SQL injection is the one people reach for, and it’s correct right up until the part that matters.
Both are the same shape of bug: instructions and data travel down one channel, and the system can’t reliably tell which is which. An attacker writes something that looks like content and gets treated as a command.
But SQL injection has a fix, and it’s a good one. Parameterized queries separate the two channels structurally. The query goes in one slot, the data in another, and the database is told, in a way it cannot misread, which is which. The vulnerability doesn’t get filtered, it gets designed out. That’s why SQL injection is a solved problem that persists only through carelessness.
There is no parameterized query for English.
A large language model takes one undifferentiated stream of tokens. Your system prompt, the conversation, the document it’s summarizing, the web page it fetched: all of it arrives as text, in the same channel, with no structural marker the model is obligated to respect. You can ask it to treat some of that text as untrusted. You are asking the thing being attacked to enforce its own boundary, which is not a security control. It’s a request.
Why “just sanitize the input” isn’t a plan
The instinct from web security is to validate at the boundary, so people reach for a filter. Scan the incoming text, block the bad instructions, pass the rest through.
This fails for a reason that isn’t obvious until you say it plainly: there is no grammar of malicious input to validate against. SQL injection has syntax. There are characters and structures that have no business appearing in a last-name field, and you can reject them without ambiguity. Prompt injection has no such signature. The attack is fluent, ordinary language, and there is an unbounded number of ways to phrase any instruction. Politely. In a different language. Split across a document. Implied rather than stated.
Any filter you build to catch that is itself a classifier making a judgment call about natural language, which means it is exactly as fallible as the model it’s protecting, and attackable by the same techniques. You have not removed the problem. You have added a second model that can also be talked into things, and given yourself the comfortable feeling of having done something.
Filters raise the cost of an attack. They are worth having for that reason. They are not a boundary, and treating one as a boundary is how you end up surprised.
The version that should actually worry you
The demos everyone saw were direct injection: a user typing something manipulative into a chat box. That’s the least interesting case, because the attacker and the user are the same person, and mostly they’re just jailbreaking a toy.
Indirect injection is the real threat, and it’s the one Unit 42 caught in the wild. Here the malicious instructions don’t come from the person talking to the agent. They’re planted in content the agent reads while doing its job: a web page it fetches, an email it summarizes, a document in the shared drive, the description on a pull request, a calendar invite, a code comment, a README in a dependency.
The user asks for something completely reasonable. The agent goes and reads something hostile in the course of answering. The hostile thing contains instructions. The agent has no mechanism for deciding that the instructions embedded in the material it was asked to process carry less authority than the instructions from its operator, because to the model they are all just tokens that arrived in the same stream.
The consequence is a rule worth writing on the wall: every input surface is an instruction surface. The moment your agent can read something you don’t control, whoever controls that thing has a channel into your agent.
Chatbots say things. Agents do things.
This is where the OWASP movement stops being trivia and starts being the design constraint.
When a chatbot is successfully injected, the damage is output. It says something false, or embarrassing, or reveals its system prompt. Bad, sometimes expensively bad, but bounded by the fact that talking is all it can do.
When an agent is successfully injected, the damage is action. Same exploit, entirely different consequence, and the difference is the tools you attached. An agent that can read your email can be made to forward it. An agent with repository write access can be made to open a pull request. An agent with a shell can do whatever that shell can do. An agent with a payments API can move money.
None of that requires a new vulnerability. It’s the same injection, landing on a system that was given more reach. That is precisely what “Excessive Agency, up three places” is measuring, and it’s why the fix cannot live at the input.
The only thing that actually works
If you can’t fix the model and you can’t filter the input, you’re left with one honest option: assume the model will be compromised, and constrain what a compromised model can do.
That’s an uncomfortable design premise, and it’s the correct one. Every control that survives contact with reality follows from it.
Least privilege on tools, aggressively. The agent gets the narrowest set of capabilities the task genuinely requires, scoped as tightly as they can be scoped. Not the API key that can do everything because it was easier to provision. The question to ask of any agent isn’t “what does it need to do its job.” It’s “what could it do if a web page told it to.”
Deterministic gates that live outside the model. A check the model cannot reason with, negotiate against, or be persuaded to skip, because it isn’t a participant in the conversation. It’s code that inspects the output and decides. I’ve written before about putting a gate around an agent’s pull requests that runs outside the agent, and this is the security case for that architecture rather than the quality case. A guardrail inside the thing being attacked is not a guardrail.
A human on anything irreversible. Sending money, sending mail, deleting data, deploying to production, granting access. Not every action, or you’ve built something too tedious to use. The ones you cannot take back.
Separate the control plane from the data plane, and never let untrusted input near the control plane. This is the same structure I’ve argued for guardrail configs, pointed at a different attacker. The agent does its work in the data plane. The rules governing that work live somewhere the agent, and anything that has captured the agent, cannot unilaterally rewrite.
Notice what these have in common. Not one of them tries to make the model harder to trick. Every one of them accepts that it will be tricked and shrinks the blast radius. That’s the whole discipline: you are not securing the model, you are containing it.
The honest part
I’d rather end this the way the research actually ends it than with a reassuring paragraph.
Prompt injection is not solved. It is not “not solved yet, and here’s the roadmap.” In current architectures, where instructions and data share one channel and the model has no structural way to tell them apart, there is no known general fix, and the major labs have said so in print. Mitigations help. Detection helps. None of it is parameterized queries, and pretending otherwise is how teams talk themselves into deployments they’d never approve if they’d stated the assumption out loud.
So state it out loud. Assume the model can be turned against you by anything it reads. Then go look at what it’s allowed to touch.
The most useful question I know for an agentic system isn’t “how do we stop it from being manipulated.” You can’t, reliably, today. It’s the one that’s actually answerable: when it is manipulated, what exactly can it reach?
The chatbot that gets talked into something says a thing you’d rather it hadn’t. The agent that gets talked into something does a thing you can’t take back. The vulnerability between them is identical. The only variable is how much you handed it.