In the early 2000s, SQL injection was everywhere. Every web app built a query by smashing user input into a string, and attackers typed ' OR '1'='1 into login forms and walked right in. It was embarrassing, it was pervasive, and it took the industry the better part of a decade to dig out — mostly through parameterized queries and frameworks that made the safe thing the default.
Prompt injection is the same movie with a different cast.
The shape of the vulnerability is identical: untrusted input gets mixed into a trusted instruction stream, and the system can't tell which is which. Only now the "database" is a language model, the "query" is a prompt, and the "input" is whatever the user — or a webpage, or a document, or another agent — decided to feed it.
We've seen the demos. The résumé screener that gets told to ignore all other candidates. The support bot that gets jailbroken into handing out refund codes. The agent with access to your email that reads a phishing message and helpfully follows its instructions. Every one of these is the same bug wearing a new hat: the model treats data as instructions because nobody built a wall between them.
And here's the part that should worry you: we're repeating the timeline. SQL injection was a known, documented, exploited vulnerability for years while the industry shipped framework after framework that concatenated strings by default. Today we're shipping LLM features that concatenate prompts by default, and the security guidance is still mostly vibes.
So what does defense in depth actually look like for LLM apps? Not a single silver bullet — SQL injection didn't die because of one product, it died because of layers.
First: treat model output as untrusted. The model's answer is data, not a decision. If your agent can send email, move money, or change access, the model's output should be a *proposal* that something else validates — not a command that executes directly.
Second: least-privilege tool access. Your customer support bot does not need write access to the billing database. Your document summarizer does not need your inbox. Every tool you hand the model is an attack surface, and the model will use them exactly as creatively as an attacker tells it to.
Third: separate the channels. System instructions, user input, and retrieved content should be structurally distinct, and the model should be trained and configured to treat them that way. This is the parameterized-query equivalent — the thing that makes the safe path the default path. It's not perfect, and researchers keep finding bypasses, but it's the foundation everything else stacks on.
Fourth: filter the outputs, not just the inputs. Input filtering is a losing game — attackers are more creative than your blocklist. But you can validate what the model *produces* before it acts: does this response match the expected schema? Is this action within the allowed set? Is a human in the loop for anything consequential?
That last one deserves emphasis. The industry spent years trying to make the model itself the firewall — better alignment, better refusal training, better guardrails baked into the weights. That's like asking the database to sanitize its own queries. It helps, but it's not a security boundary. The boundary lives in your architecture, not in the model's mood.
The people building LLM features aren't careless. Most of them are moving fast on a platform that's genuinely new, and the security playbook is still being written. I get it. But the playbook has a first chapter, and we've already read it once.
We know how this story ends. The question is whether we spend another decade learning it.