Skip to main content

Semantic Kernel's Prompt-Injection RCE: The Agent-Tool Audit Every Deploy/Rollback MCP Server Needs

9 min readDora NodaDora Noda
Share

A single document retrieved into a RAG pipeline was enough to launch calc.exe on the machine running the agent. No browser exploit, no memory corruption, no phishing email — just a prompt, and a framework that trusted its own decorators too much.

That's the demo Microsoft's own security team used to disclose two Semantic Kernel vulnerabilities on May 7, 2026: CVE-2026-26030 (CVSS 9.8, Python SDK) and CVE-2026-25592 (CVSS 9.9–10.0, .NET SDK). Different code paths, same root failure — an internal method got exposed to the model as a callable tool, and a model-controlled argument reached a dangerous operation with no validation in between.

If you're running (or building) an MCP server that lets an agent deploy, roll back, or read logs for a production service, the interesting part isn't the CVE numbers. It's that the same failure pattern — decorator says "callable," nobody checked whether the argument that reaches it is safe — applies directly to a deploy(service, ref) or rollback(service, revision) tool. Compressed into one audit you can run today against your own tool definitions:

  1. Is this tool actually meant to be agent-facing, or is it an internal helper that a blanket decorator/export swept in by accident?
  2. Does any parameter reach a dangerous sinkeval, a shell command, a filesystem path, a SQL string — without going through an allowlist or a constrained schema?
  3. Is a destructive or irreversible action gated by a confirmation step enforced on the server, not just a client-side hint the agent could route around?
  4. Does the tool run with the platform's ambient permissions, or a scoped, time-bound credential that limits blast radius if the call itself is malicious?

The rest of this post walks through how the two CVEs actually broke, why they're one vulnerability class wearing two costumes, and what running that checklist looks like against a real deploy/rollback tool.

What Actually Broke

Semantic Kernel is Microsoft's open-source framework for building AI agents that call functions — "skills," in its terminology — decorated with [KernelFunction]. That decorator is what turns an ordinary method into something the LLM can see in its tool schema and invoke on its own. Both CVEs trace back to that same seam, hit from opposite directions.

CVE-2026-26030 lives in the Python SDK's InMemoryVectorStore. When an agent searches a vector store with a metadata filter, Semantic Kernel builds that filter as a Python lambda expression and runs it through eval() at query time. The problem is what's allowed to reach that eval() call: fields from records already sitting in the vector store — meaning anything an attacker can get indexed into a RAG corpus once (a poisoned support ticket, a planted document, a crafted commit message) can carry a payload that executes the moment a later query triggers filtering on it. No prompt injection into the conversation is even required — the injection lives in the data the agent was told to trust.

CVE-2026-25592 lives in the .NET SDK's SessionsPythonPlugin, the component that lets an agent run Python inside an Azure Container Apps sandbox. Two of its methods, DownloadFileAsync and UploadFileAsync, were tagged [KernelFunction] — exposing their full parameter schema, including the destination path, directly to the model. Nothing validated or sandboxed that path. A prompt-injected agent could call DownloadFileAsync with a localFilePath pointed at the Windows Startup folder, drop a payload there with no user confirmation, and get code execution on the next reboot — walking straight out of the sandbox the plugin was supposed to contain it in.

CVEComponentRoot causeCVSSFixed in
CVE-2026-26030Python SDK, InMemoryVectorStoreAttacker-controlled vector-store fields reach eval()9.81.39.4
CVE-2026-25592.NET SDK, SessionsPythonPluginInternal file-transfer helper accidentally tagged [KernelFunction]; destination path unvalidated9.9–10.01.71.0

Microsoft's security team was explicit that this isn't a Semantic-Kernel-specific problem: "readers should expect analogous flaws in LangChain, CrewAI, AutoGen and other agent frameworks." The decorator pattern that makes these frameworks convenient — annotate a method, the model can call it — is exactly the pattern that let both bugs ship.

The Shared Vulnerability Class

Strip away the framework-specific details and both CVEs reduce to the same two-part failure, and either half alone is enough to be exploitable:

Exposure without intent. A decorator like [KernelFunction] is a one-line opt-in with no separate review step. DownloadFileAsync wasn't designed as an agent tool — it was an internal helper the plugin needed for its own bookkeeping, and it got the same annotation as the methods that actually were meant to be model-facing. Nothing in the framework distinguishes "I meant to expose this" from "this got swept up because it lives in the same class." That's OWASP's Excessive Agency risk (LLM06) in its purest form: the agent had a capability nobody deliberately granted it.

Untrusted input reaching a dangerous sink. Even a deliberately-exposed tool is safe only if what reaches it is constrained. InMemoryVectorStore's filter-building code was meant to be flexible — that's why it used eval() instead of a fixed set of comparison operators — but "flexible" and "reachable by attacker-controlled data" turned out to be the same design decision. A tool that takes a file path, a shell argument, or a query expression from the model needs that argument constrained at the schema level, not validated after the fact by hoping the LLM behaves.

Neither CVE needed both failures simultaneously to be a 9+ CVSS bug. DownloadFileAsync was exposed and unvalidated — but the exposure alone (an internal helper reachable at all) was the finding worth patching, independent of how bad the path traversal was. That's the part worth internalizing: you don't get to treat "our tool list is small" as a mitigating factor, because both vulnerable functions here shipped inside a small, well-known, first-party plugin — not a sprawling third-party tool surface nobody had eyes on.

The Audit: Applying It to a Deploy/Rollback Tool

Here's what the four-point checklist from the top looks like against a concrete example — an MCP tool a self-hosted PaaS might expose so an agent can trigger a deploy:

json
{
  "name": "deploy_service",
  "description": "Deploy a service to a given git ref",
  "inputSchema": {
    "type": "object",
    "properties": {
      "service": { "type": "string" },
      "ref": { "type": "string" },
      "extra_args": { "type": "string" }
    },
    "required": ["service", "ref"]
  }
}

Run the checklist against it:

  1. Agent-facing on purpose? Yes — this is exactly the kind of tool a deploy-from-chat platform intends to expose. Unlike DownloadFileAsync, it isn't an accidental sweep. But that only clears bar one; the definition still has to survive bars two through four.
  2. Dangerous sink reachable? extra_args is the problem. A free-form string parameter that gets appended to a shell command or build invocation is structurally identical to the path Semantic Kernel handed to DownloadFileAsync — the model controls a string, and that string reaches something that executes. The fix is the same in both cases: replace the free-form field with a closed enum of supported flags, or drop it entirely if nothing legitimate needs it.
  3. Confirmation enforced server-side? A deploy is reversible (roll back), but a rollback or destroy_service tool sitting next to it isn't, or is expensive to undo. If the only thing standing between a model decision and an irreversible action is an MCP destructiveHint: true annotation, that's not a control — annotations are hints for client UX, and the MCP spec itself says clients shouldn't trust them from anything but a fully trusted server. The gate has to be a real server-side check (a second signed confirmation, a human-in-the-loop approval queue) that holds even if the client ignores the hint.
  4. Ambient or scoped credentials? If deploy_service executes with the same broad service-account permissions the platform's own control plane uses, a successful injection against this one tool inherits everything that account can touch. Scoping the credential the tool runs under — to just this service, just this action, with a short-lived token — is what turns "the model called a tool it shouldn't have" into a contained incident instead of a host compromise.

Point 3 is worth dwelling on because it's the one most likely to already be half-solved and quietly assumed to be fully solved. MCP's tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) exist precisely to flag exactly this kind of risky call — but the spec's own guidance is that they're informational, not load-bearing, once the server issuing them can't be fully trusted. An agent-operable PaaS's own deploy/rollback tools are first-party and presumably trustworthy, which is the easy case; the harder one is any tool whose output later re-enters the same agent's context (logs, a third-party status check) and could itself be attacker-influenced input steering the next call. The confirmation gate needs to hold regardless of which side of that trust boundary the instruction came from.

Why "The Tool List Is Small" Isn't a Defense

It's tempting to read all four checklist items and conclude that a platform with five hand-written MCP tools is safe by construction — there's no sprawling surface to audit, so what's the risk? The CVEs argue directly against that comfort. Semantic Kernel's vulnerable functions didn't live in some obscure community plugin with thin review; SessionsPythonPlugin and InMemoryVectorStore are first-party, documented, widely-used components inside one of the most visible agent frameworks Microsoft ships. A small, well-known, carefully-maintained tool surface still shipped a CVSS 10.0 sandbox escape, because size was never the variable that mattered — what a tool's arguments are allowed to reach, and what has to happen before an irreversible one fires, are.

A five-tool MCP server is easier to audit than a fifty-tool one, which is a real advantage — but "easier to audit" only pays off if the audit actually happens. The failure mode in both CVEs wasn't "too many tools to review," it was "nobody ran this specific check against this specific tool before shipping it." That's a process gap the checklist above is meant to close, not a scale problem that goes away because the list is short.


Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with deploy/rollback exposed to agents as scoped, confirmation-gated MCP tools rather than decorator-swept internal methods. Star the repo on GitHub or run the audit above against your own agent's tool surface today.

Related articles

Give your agents a chain backend

Autonomous agents hit RPC endpoints very differently than people do. See what bex router handles on their behalf.

Read the agents guide