7 min readJohnny UnarJohnny Unar

Prompt Injection Is a Code Execution Primitive Now

Microsoft's CVE-2026-25592 showed a single injected prompt driving host-level RCE. Your threat model still treats it like a content problem. That's the bug.

the demo nobody wanted to see

In May, Microsoft's security research team published a walkthrough of CVE-2026-25592, a flaw in how a Semantic Kernel agent handled tool invocation, and the thing that made engineers I know actually stop and read it twice was how boring the exploit chain was. No heap grooming, no ROP gadgets, no crafted PDF with a malformed font table. Just a chunk of text, embedded in a document the agent was asked to summarize, that said something to the effect of please run the diagnostics tool with the following arguments, and the agent, doing exactly the job it was built to do, parsed the natural language, matched it to a registered function, and shelled out to the host. Host-level RCE. From a summarization request. The injected instructions never touched a buffer they shouldn't have, never overflowed anything, never violated a single memory-safety guarantee that Rust or a fuzzer would have caught, because there was nothing to catch. The agent was working as designed. That's the whole point and it's the part the industry keeps refusing to internalize. When your architecture treats natural language as both the control plane and the data plane at the same time, and one of your tools can run commands, you have built a system where any attacker who can get text in front of the model can steer execution. We've been calling that prompt injection like it's a spam-filter problem. It's an unauthenticated code path.

why content moderation framing keeps failing you

The reason teams keep shipping exploitable agent pipelines is that the dominant mental model for prompt injection came from the wrong department. It came from trust and safety, from the people whose job was stopping the model from saying slurs or generating instructions for making nerve gas, and in that world the mitigation is a classifier that scores input and output and blocks the bad stuff. That framing is fine for a chatbot that can only emit tokens. It falls apart the moment the model can call a function that has side effects on the world, because now the question isn't whether the output is offensive, it's whether the model was tricked into invoking a capability the requesting user was never authorized to trigger. A guardrail that scans for jailbreak phrasing is trying to solve a decidability problem, and it will lose, because the space of ways to phrase run this command in natural language is not enumerable and never will be. We've reviewed maybe a dozen agentic systems in the last year at steezr, most of them for startups moving fast on the RAG-plus-tools pattern, and in almost every case the team had bolted an input classifier onto the front and considered the problem addressed. Then we'd point at the retrieval step, where the agent pulls a support ticket or a scraped web page or a Notion doc written by someone outside the company, feeds it into the same context window as the system prompt, and hands the model a tool that can hit an internal API. The classifier is watching the wrong door. The attacker isn't typing into your chat box. They filed a Zendesk ticket six weeks ago and waited.

the actual boundary is capability, not text

If you want a threat model that survives contact with a real attacker, stop thinking about text and start thinking about capabilities and who is allowed to invoke them. Every tool you register with an agent is a function, and every function has a blast radius, and the correct question during design review is: assuming the model can be made to call this function with arbitrary arguments, what's the worst thing that happens? Not will the guardrail stop it. Assume the guardrail is off. Assume the attacker owns the model's intent, because in a system that mixes untrusted retrieved content with your system prompt, they effectively do. A tool that reads a public status page has a blast radius of basically nothing. A tool that runs shell commands, or executes SQL without a parameterized allow-list, or sends email from your domain, or calls Stripe, has a blast radius that should terrify you, and those tools need authorization that lives outside the model entirely. The pattern we push clients toward is treating the model as an untrusted planner that proposes actions, and putting a deterministic policy layer between the proposed action and its execution, one that checks the actual user's actual permissions against the actual operation, in code, with no LLM in the loop. If the agent proposes DELETE FROM orders and the user is a read-only support account, that request dies at a Go middleware function that never once asks a language model for its opinion. The model can want whatever it wants. It doesn't get to bypass authz just because it phrased the request politely.

how we architect this in practice

Concretely, here's the shape that has held up for us. The agent runs with no ambient authority of its own, it carries the requesting user's token, and every tool call is a request that gets evaluated against that token by a service the model can't influence. We split tools into tiers by side effect. Read-only, non-sensitive tools can be called freely. Anything with a write side effect, or anything touching money, PII, or infrastructure, goes through an explicit authorization check and, for the genuinely dangerous stuff, a human confirmation step that shows the exact operation in plain terms before it fires. We keep retrieved untrusted content in a structurally separate part of the context, and we do not let it inherit the trust level of the system prompt just because the tokenizer flattens everything into one sequence, which means the prompt template itself has to be written knowing that anything coming from retrieval is hostile input. On the Semantic Kernel and comparable frameworks, that means not auto-registering every plugin function into the planner's callable set and being deliberate about what the planner can even see. We also log every proposed tool call, not just executed ones, because the rejected proposals are your intrusion signal, the same way blocked auth attempts are. If an agent tied to a support account suddenly starts proposing calls to a user-deletion tool, that's not a model quality issue, that's someone probing your system, and you want that in your SIEM. None of this is exotic. It's the principle of least privilege applied to a component that happens to speak English, and the only reason it feels novel is that we spent two years pretending the English part changed the rules.

what to do monday morning

Pull up whatever agent you've got in production or heading there, and make a list of every tool it can call. For each one, write down the blast radius under the assumption that an attacker fully controls the arguments, and be honest, because run a shell command doesn't get less dangerous because you named the function safe_execute. Then find every place untrusted data enters the context window, and there are always more than the team thinks, retrieval, tool outputs that get fed back in, user-uploaded files, scraped pages, previous conversation turns from other users if you're sloppy with sessions. Draw the line between the two. Any dangerous tool reachable from any untrusted input source is a live vulnerability today, guardrails or not, and it needs an out-of-band authorization check before you sleep well. If you're building agentic systems and you want a second set of eyes from people who've broken a few of these on purpose, that's a chunk of what we do at steezr, but you don't need us to start, you need to stop treating the model as a trust boundary. It isn't one. It never was. CVE-2026-25592 wasn't a bug in Semantic Kernel so much as a bug in how the whole industry decided to think about what an agent is, and the frameworks are going to keep shipping planners that flatten trust because that's what makes the demos impressive. The correct threat model is old and unglamorous and it works, and the sooner your architecture treats a language model like the confused deputy it is, the sooner prompt injection goes back to being an annoyance instead of a way onto your box.

Johnny Unar

Written by

Johnny Unar

Want to work with us?

Microsoft's CVE-2026-25592 showed a single injected prompt driving host-level RCE. Your threat model still treats it like a content problem. That's the bug.