system: OPERATIONAL
← back to all hacks
AGENTS CRITICAL

Prompts as shells: when prompt injection becomes RCE in agent frameworks

Two CVEs disclosed in Microsoft Semantic Kernel on May 7, 2026 (CVE-2026-25592, CVE-2026-26030) show how a single injected prompt can pivot from text to remote code execution on the agent's host.

2026-05-22 // 7 min affects: semantic-kernel-python, semantic-kernel-dotnet, agent-frameworks

What happened

On May 7, 2026, the Microsoft Security Response Center disclosed two critical vulnerabilities in the Semantic Kernel agent framework: CVE-2026-25592 (CVSS 10.0) in the .NET SDK and CVE-2026-26030 (CVSS 9.8) in the Python SDK. Both turn a textual prompt injection — the model receiving attacker-controlled instructions through untrusted content — into remote code execution on the host running the agent. Patches landed the same day in semantic-kernel Python 1.39.4 and .NET 1.71.0.

The companion research post, When prompts become shells, frames a broader pattern: agent frameworks expose tools, sandboxes and helper functions to the model, and any helper that touches a code-execution sink (eval, exec, deserialization, file write, shell) becomes reachable by a single well-crafted prompt.

How it works

Agents work by giving the LLM a registry of callable tools. The model decides which to invoke and supplies arguments. Two distinct bugs, same shape:

CVE-2026-26030 — Python eval() in the in-memory vector store. The default InMemoryVectorStore built filter expressions by passing attacker-influenced strings into a Python lambda compiled with eval(). An LLM tricked through an injected prompt could ask the store to filter records using a payload that executes arbitrary Python.

# Conceptual sketch — DO NOT run.
# A field controlled by untrusted retrieved content reaches eval().
filter_expr = f"lambda r: r.score > {model_supplied_value}"
predicate = eval(filter_expr)  # [REDACTED]: payload runs in host process

CVE-2026-25592 — [KernelFunction] exposed DownloadFileAsync in the .NET sandbox. The SessionsPythonPlugin runs model-generated code inside an Azure Container Apps dynamic session. An internal helper, DownloadFileAsync, was accidentally annotated [KernelFunction] and registered as a callable tool with no path validation. A prompt could ask the agent to “download” a remote file to an arbitrary local path, writing attacker-controlled content outside the sandbox and onto the host.

In both cases the entry point is text. No memory corruption, no browser exploit. The model simply interprets the request, picks a tool, and hands parameters to a code-execution sink.

Why this matters

This is not a Semantic Kernel-specific story. The same pattern has surfaced repeatedly in 2026 across agent frameworks (PraisonAI, Flowise, LMDeploy, mcp-remote and others — see the Adversa AI tracker). The structural cause is the same everywhere:

  1. The framework exposes a tool registry to the LLM.
  2. At least one tool reaches an unsafe sink (eval, exec, pickle deserialization, path write, shell).
  3. The agent processes untrusted text — retrieved documents, web pages, tool results — alongside its own instructions.
  4. There is no enforced boundary between data and instructions.

This is exactly the lethal trifecta described by Simon Willison in June 2025 — private data access, untrusted-content exposure, and external action — and the Rule of Two proposed by Meta’s AI Security team on October 31, 2025: an agent should hold at most two of those three properties per session. When all three coexist, deterministic safety collapses, and a textual prompt becomes a shell.

Defenses

Mitigations are architectural, not heuristic — prompt filtering alone is insufficient.

  1. Patch first. Upgrade Semantic Kernel to Python >=1.39.4 and .NET >=1.71.0. Audit any agent framework you operate against the Adversa monthly tracker.
  2. Eliminate eval/exec on model-supplied strings. Replace with structured parsers (AST allowlists, typed filter DSLs). Microsoft’s fix layered four checks: AST node-type allowlist, function-call allowlist, dangerous-attribute blocklist, name-node restriction.
  3. Audit your tool registry. Every [KernelFunction] / @tool / OpenAPI-exposed endpoint is reachable by the model. Treat the registry as a public API: minimal surface, validated parameters, no helper functions exposed by accident.
  4. Sandbox the executor, not the prompt. Run any model-driven code in a container or microVM with no network, no host filesystem write, no credentials beyond the task.
  5. Apply the Rule of Two. For each agent session, decide which two of {untrusted input, sensitive data, external action} you accept — and enforce the cut at the architecture level.
  6. Log tool calls with full arguments. Make post-hoc detection possible; raw model output is not enough.

Status

FrameworkCVECVSSDisclosurePatched in
Semantic Kernel (.NET)CVE-2026-2559210.02026-05-071.71.0
Semantic Kernel (Python)CVE-2026-260309.82026-05-071.39.4
PraisonAICVE-2026-443389.x2026-04latest
Flowise MCP adapterCVE-2026-409339.x2026-04latest

If you ship an agent in production, the question is no longer whether prompt injection will reach your tool layer — it is which sink it lands on. Cut the sinks.

Sources