Table of Contents

Microsoft’s May 7 advisory1, titled “When prompts become shells,” discloses two CVSS 9.9 vulnerabilities in Semantic Kernel that convert model output into code execution. Neither bug is in the model. Both are in how the framework hands model-supplied values to system primitives without treating those values as untrusted.

The May 7 Advisory: Two CVEs, One Architectural Problem

CVE-2026-260302 and CVE-2026-255923 share the same root structure: an AI-controlled string exits the model context and enters an execution context with no intermediate trust check. The vector filter bug executes it directly via eval(). The file-download bug passes it to the filesystem via an annotated method the model is allowed to call. Microsoft’s framing is deliberate1, these are tool-design failures, not prompt-injection failures. Patching the prompt template would leave both vectors open.

The patched versions are semantic-kernel 1.39.4 for Python2 and 1.71.0 for .NET3. NVD initially listed 1.70.0 as the .NET fix; that was corrected.

CVE-2026-260302: How eval() in a Vector Filter Became a Shell

The InMemoryVectorStore in the Python SDK accepted filter predicates built from model output and evaluated them as Python expressions. The construction looked roughly like:

lambda x: x.city == '{value}'

where {value} came from the model. Pass in ' or __import__('os').system('cmd') or ' and the lambda becomes a shell invocation. Or bypass that entirely with class-hierarchy traversal: __class__.__bases__[0].__subclasses__() hands you the full Python object graph, from which arbitrary code execution is a few attribute lookups away.

According to Microsoft’s advisory1, CVSS 9.9 is the correct score. The attack surface is any deployment that uses the in-memory vector store with a model processing user-supplied queries, which describes most Semantic Kernel RAG implementations.

CVE-2026-255923: When [KernelFunction] Accidentally Grants Host File Write

The .NET bug is more architectural. The SessionsPythonPlugin included a DownloadFileAsync method decorated with [KernelFunction], which tells Semantic Kernel to register it as a tool the model can invoke by name. The method accepted a destination path with no validation.

The attack chain described in the advisory1 runs in two steps: prompt injection causes the model to call ExecuteCode, staging a payload inside the Pyodide sandbox. The model then calls DownloadFileAsync with a destination path pointing to the Windows Startup folder. On the next user sign-in, the payload runs. No traditional exploit required; the framework’s own tool-binding infrastructure delivers the payload.

Why Blocklists Lose to Python’s Object Model

The original filter implementation used a blocklist, denying specific dangerous names. This fails against Python for a reason that has been understood for years: the language’s object model is a graph, not a tree. Any path through __class__, __bases__, __subclasses__, or __globals__ can reach os, subprocess, or builtins from a context that looks, on the surface, like a restricted expression evaluator.

Blocklists require enumerating every dangerous attribute across every Python version, every installed package’s monkey-patching, and every future addition to the standard library. Microsoft’s fix (PR #13505)1 replaced the blocklist with an AST node-type allowlist: only specific node types are permitted, function calls and bare names outside the allowlist are rejected, and dangerous attribute names are blocked at the AST level before evaluation reaches eval(). Allowlists are smaller, auditable, and don’t need to anticipate future bypass routes.

The Fix: Allowlists, Not Sandboxes

Both fixes share a principle: reduce the exposed surface rather than add defensive layers around a dangerous one.

For CVE-2026-260302, the filter evaluator now validates against an AST allowlist before any expression reaches eval(). The set of permitted node types is explicit and narrow.

For CVE-2026-255923, the .NET fix1 removed [KernelFunction] from DownloadFileAsync entirely and added ValidateLocalPathForDownload(), which calls Path.GetFullPath() and checks the result against a directory allowlist. The method is no longer model-callable by default; re-exposing it requires an explicit decision.

Beyond Semantic Kernel: Every Agent Framework Needs a Tool Audit

Microsoft’s advisory1 names LangChain, CrewAI, and AutoGen as frameworks sharing the same design pattern: LLM output flows into plugin parameters, which flow into system calls. The advisory treats analogous vulnerabilities in those frameworks as likely rather than hypothetical.

The mechanism is identical regardless of framework: a developer annotates a method to make it model-callable, and the model is trusted to supply safe parameter values. That trust is the vulnerability. Whether the annotation is [KernelFunction], @tool, a LangChain Tool wrapper, or an MCP tools/call registration, if the method accepts a path, an expression, a shell argument, or a URL, and the model controls that value, the method is a remote primitive.

Analysis published May 124 frames this as a structural property of the entire class of agent frameworks rather than a Semantic Kernel-specific oversight. Given that the vulnerability pattern reproduces mechanically from the design, that framing is hard to argue with.

What to Patch and How to Hunt

Patch targets: Python SDK 1.39.4 or later for CVE-2026-260302; .NET SDK 1.71.0 or later for CVE-2026-255923. If you’re on .NET 1.70.0 and assumed you were covered, you are not.

For the broader audit, the search is mechanical: grep your codebase for whatever annotation your framework uses to register model-callable tools, then review each result for parameters that accept paths, expressions, or externally resolvable identifiers. The question for each method is not “could the prompt inject something dangerous?” but “if the model calls this method with adversarial parameters, what can it touch?” Those are different questions, and only the second one surfaces bugs like these.

The Semantic Kernel advisory1 is the clearest statement a major framework vendor has published about where the trust boundary actually sits. Prompt sanitisation is a component; it is not a perimeter. The perimeter is the set of tools you expose to the model, and most frameworks currently expose that surface without an audit trail.

Frequently Asked Questions

Do the same vulnerability patterns apply to Claude Code and Flowise?

Yes. The research analysis identifies Claude Code and Flowise as sharing the identical LLM-output-to-plugin-parameter-to-system-call pipeline. Any framework that auto-binds natural-language tool calls to annotated methods without auditing the exposed surface carries the same structural risk, regardless of vendor.

What’s the stopgap if we can’t upgrade SDK versions immediately?

For CVE-2026-25592, manually removing the [KernelFunction] attribute from DownloadFileAsync in your local SessionsPythonPlugin copy neutralizes the model-callable file-write primitive without a full SDK upgrade. For the Python vector filter, disabling InMemoryVectorStore filter predicates sourced from model output—falling back to application-side filtering—closes the eval() path until 1.39.4 can be deployed.

What attack surfaces does the AST allowlist fix leave uncovered?

The allowlist scopes only the vector-store filter evaluator. Other registered kernel functions that accept model-controlled strings—SQL generators, HTTP clients, shell executors—remain fully exposed if they lack their own parameter validation. Each annotated method requires an independent allowlist or surface-reduction pass; the filter fix is point-solution, not framework-wide.

Will upgrading to Python SDK 1.39.4 break existing filter predicates?

Likely yes. Filters that relied on Python features now excluded from the allowlist—arbitrary function calls, bare names, complex attribute chains—will fail at AST validation time. The allowlist is intentionally narrow with no backward-compatibility escape hatch, so teams should audit existing InMemoryVectorStore predicates against the permitted node types before upgrading.

How does the CVSS 9.9 rating compare to typical framework-level CVEs?

CVSS 9.9 is near the maximum and unusually high for a library-level vulnerability—most framework CVEs land in the 7.0–8.5 range. The rating reflects that no authentication or privilege escalation is required; the attacker only needs to inject a prompt that the model processes, and the framework itself supplies the execution primitive.

Footnotes

  1. Microsoft Security Blog: When prompts become shells 2 3 4 5 6 7 8 9

  2. NVD: CVE-2026-26030 2 3 4 5

  3. NVD: CVE-2026-25592 2 3 4 5 6

  4. ExpertLinked: Prompt injection RCE agent framework trust collapse

Sources

  1. Microsoft Security Blog: When prompts become shellsvendoraccessed 2026-05-17
  2. NVD: CVE-2026-26030primaryaccessed 2026-05-17
  3. NVD: CVE-2026-25592primaryaccessed 2026-05-17
  4. ExpertLinked: Prompt injection RCE agent framework trust collapseanalysisaccessed 2026-05-17

Enjoyed this article?

Stay updated with our latest insights on AI and technology.