
In May 2026, Microsoft’s security team published the write-up that every agent builder should have taped to their monitor. Two critical vulnerabilities in Semantic Kernel — its own agent framework — let a model’s output reach a code interpreter and execute on the host. Both scored at the very top of the CVSS scale. If you run Semantic Kernel you have almost certainly patched by now; the fixes shipped in the same disclosure. So this is not a breaking-news alert. It is an autopsy, because the interesting thing about these bugs is not that they existed in one Microsoft library. It is that they are the same bug the whole industry keeps writing, and the design lesson underneath them applies to every agent stack, including yours.
I have argued before that the agent web’s real risk lives below the protocol, in the code that decides what a tool call is allowed to do. These two CVEs are that argument made concrete. Let me walk the anatomy, then give you a checklist you can run against your own framework this afternoon.
The two bugs
The Microsoft Security Response Center write-up disclosed two distinct flaws on 7 May 2026.
CVE-2026-26030 lived in the Python SDK, in versions before 1.39.4. The InMemoryVectorStore built its default search filter as a Python lambda and evaluated it with eval() — feeding in filter parameters that could be model-controlled and were not sanitised. Read that sentence again slowly: a vector-store filter, the most boring plumbing imaginable, was compiling attacker-influenceable strings into live Python. The GitHub advisory (GHSA-xjw9-4gw8-4rqx) rates it 9.8–9.9. A public reproduction lab (InertFluid/sk-cve-2026-26030-lab) exists, so this is not theoretical.
CVE-2026-25592 sat in the .NET SDK, before 1.71.0: an arbitrary file-write primitive through the SessionsPythonPlugin, scored 9.9. Arbitrary file write is rarely the end of the story — drop a file in the right place and it becomes code execution, persistence, or lateral movement soon enough. The patches are 1.39.4 for Python and 1.71.0 for .NET. Apply them if you somehow have not.
The attack chain: closing the quote
Here is the mechanism, because it is the part that generalises. An agent framework exposes tools to a model. The model, doing its job, produces the arguments for those tools — a search filter, a filename, a query. The framework takes those arguments and, somewhere deep in a convenience path, treats them as code rather than data. In the Semantic Kernel case, the filter parameter flowed into an eval().
Now add an adversary. The model’s output is not sovereign; it is shaped by whatever it read — a poisoned document, a hostile web page, a booby-trapped tool result. Microsoft’s framing is exact: the attacker “closes the quote and appends Python logic.” A parameter that was supposed to be the string category == "invoices" becomes category == "invoices" or __import__("os").system("..."). The benign data lookup carries an executable payload across the trust boundary, and because the framework compiles it, the host runs it. Prompt injection stops being a content problem — a chatbot saying something rude — and becomes a remote-code-execution problem: a shell on your machine.
The trust boundary is the whole story. Every serious agent breach assembles the same three ingredients in one place: access to private data, a channel for untrusted content, and the ability to act on the outside world. When tool parameters are also a code channel, those three collapse into a single call.
Why frameworks keep shipping this bug
You might ask how a team as capable as Microsoft’s ships an eval() on model-adjacent input. The answer is not incompetence; it is the gravity of convenience. Dynamic evaluation is genuinely the easiest way to turn a flexible filter expression into a callable. Lambdas-from-strings, template engines that execute, plugins that write and run files — these are all ergonomic wins that were designed in an era when the input came from a developer, not from a probabilistic text generator steered by whatever it just read on the internet.
That is the mental shift the whole field is still only half-way through. In a classic application, the code is trusted and the data is not. In an agent, the model’s output sits in an uncanny middle: it looks like an instruction from your own system, but its provenance is the open web. Treat it as trusted and you have handed the internet a function pointer. I dug into the reliability half of this problem in the agent reliability gap; this is its security twin. A model that is merely unreliable wastes your money. A model whose output reaches eval() loses you the host.
The design principles that would have stopped it
None of these bugs required a novel defence. They required old defences applied to a new source of input.
- Treat model output as untrusted input — always. Every tool argument the model produces is, for security purposes, an anonymous HTTP request from the public internet. You would never
eval()one of those. Hold model output to the same standard. - Never evaluate tool parameters as code. No
eval(), noexec(), no string-to-lambda, no shell interpolation on anything a model can influence. If you need a filter language, parse it into a restricted AST you control and reject everything else. Convenience that compiles attacker input is not convenience. - Sandbox capabilities, not just prompts. A code-interpreter or file-writing plugin should run with the least privilege that lets it do its job — a locked-down container, a scratch filesystem, no ambient network, no host mounts. Assume the payload will get through the guardrail, because adversarial testing finds a bypass within weeks, and make sure it lands somewhere it cannot hurt you.
- Put a real boundary between the model and the host. Tools should expose narrow, typed, allowlisted operations — not a general-purpose escape hatch that happens to be convenient for the demo.
A checklist for auditing your own stack
Semantic Kernel got named because Microsoft disclosed responsibly and loudly. That is to its credit, and it is also a warning: the same pattern is sitting unremarked in frameworks that have not been audited yet. Run this against yours.
- Grep for dynamic evaluation. Search the framework and your own tool code for
eval,exec,compile,subprocesswithshell=True, and template engines in execute mode. Trace every hit back to see whether a tool parameter can reach it. - Map the model-to-code path. For each tool, ask one question: can a value the model produced end up interpreted rather than handled as inert data? If you cannot answer confidently, that tool is unaudited.
- Check your code-interpreter and file plugins hardest. These are the ones designed to run things. Confirm the sandbox, the filesystem scope, and the network egress rules yourself — do not take the defaults on faith.
- Pin and watch your framework versions. These fixes shipped as point releases. Subscribe to the advisory feed for every agent dependency; the next CVE-2026-26030 is already in someone’s backlog.
- Log tool calls with their arguments. When something does get through, an authenticated, per-call audit trail is the difference between a two-hour investigation and a two-week one.
The uncomfortable truth is that this class of vulnerability is structural, not incidental. As long as we hand models the ability to act and build frameworks optimised for developer ergonomics, tool parameters will keep trying to become shells. The protocols are getting standardised and audited — I wrote about that in the piece on the agent web’s plumbing — but the framework code sitting one layer down is where the payload actually detonates. Patch Semantic Kernel, yes. Then go read your own eval() calls before someone else does.