Microsoft Semantic Kernel In-Memory Vector Store Filter eval() Sandbox Bypass RCE (CVE-2026-26030)
by InertFluid · 2026-07-05
- Severity
- Critical
- CVE
- CVE-2026-26030
- Category
- misc
- Affected product
- Microsoft Semantic Kernel (Python), InMemoryCollection vector store connector
- Affected versions
- < 1.39.4 (fixed in 1.39.4)
- Disclosed
- 2026-07-05
- Patch status
- patched
Tags
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-06 |
| Author / Researcher | InertFluid |
| CVE / Advisory | CVE-2026-26030 |
| Category | misc |
| Severity | Critical |
| CVSS Score | Not specified in source |
| Status | Weaponized |
| Tags | semantic-kernel, python, eval-injection, sandbox-bypass, prompt-injection, llm-agent, rce, ast-allowlist-bypass, vector-store |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | Microsoft Semantic Kernel (Python), InMemoryCollection vector store connector |
| Versions Affected | < 1.39.4 (fixed in 1.39.4) |
| Language / Platform | Python 3.13, LLM agent framework (Semantic Kernel + optional Groq-hosted LLM demo) |
| Authentication Required | No (exploited indirectly via prompt injection into an LLM agent) |
| Network Access Required | No for the headless lab PoC (local); Yes for the live agent demo (LLM API + local web UI) |
Summary
CVE-2026-26030 is a sandbox-bypass remote code execution vulnerability in Semantic Kernel’s in-memory vector store search filter evaluation. Agents that expose a search/query tool backed by InMemoryCollection let the LLM emit a filter expression string (e.g. lambda x: x.team == 'platform'), which is compiled and evaluated by _parse_and_validate_filter() in connectors/in_memory.py. That function empties __builtins__ and applies an AST node allowlist, intending to prevent arbitrary code execution — but two gaps break the sandbox: unrestricted ast.Attribute access allows dunder-attribute traversal (().__class__.__base__.__subclasses__), and the ast.Call name-check only inspects func when it’s a Name or Attribute node, silently skipping the check when func is a Subscript (which is itself allowlisted). Chaining [obj.method][0](args)-style calls lets an attacker walk to BuiltinImporter, load_module('os'), and os.system(), achieving code execution whenever a filter string an LLM was steered into emitting (via direct user prompt or injected retrieved content) reaches this code path. The repository is a full, runnable lab: isolated venvs for the vulnerable (1.39.3) and patched (1.39.4) versions, a headless exploit script, a validator-only probe, and an optional live FastAPI + LLM agent demo that opens a harmless “ransom note” file to visually confirm RCE end-to-end.
Vulnerability Details
Root Cause
_parse_and_validate_filter() combines an AST node allowlist with an emptied __builtins__ dict before calling compile()/eval() on the filter string, but the allowlist does not restrict ast.Attribute access and only validates callee names for Name/Attribute call targets — leaving Subscript-wrapped calls and dunder attribute chains (__class__, __base__, __subclasses__) completely unchecked.
Attack Vector
- An LLM agent exposes a tool (e.g.
search_runbooks(team)) that queries anInMemoryCollectionusing a filter string the model constructs from conversation context. - An attacker influences that filter string via a direct prompt or via injected text in retrieved/tool content (prompt injection), steering the model into emitting a malicious filter such as
lambda x: [[[().__class__.__base__.__subclasses__][0]()[N].load_module][0]('os').system][0]('<command>'). - The filter string reaches
InMemoryCollection._get_filtered_records(), which calls_parse_and_validate_filter(); the allowlist passes the payload because every call site is wrapped in an allowlistedSubscript. eval()executes the compiled lambda, which walksobject.__subclasses__()toBuiltinImporter, importsos, and callsos.system(cmd)— arbitrary command execution as the process’s user, invisibly to the agent, which still reports normal “search results” to the LLM/user.
Impact
Unauthenticated (from the attacker’s perspective — driven purely through prompt injection) arbitrary OS command execution in any process embedding a Semantic Kernel agent with an InMemoryCollection-backed search tool and attacker-influenceable filter input.
Environment / Lab Setup
Target: semantic-kernel == 1.39.3 (vulnerable) vs. 1.39.4 (patched), Python 3.13 venvs
Attacker: bash, python3.13, pip; optional: Groq API key (free tier) for the live LLM agent demo
Proof of Concept
PoC Script
See
exploit.py,probe.py,find_sink.py,setup.sh,run.sh, anddemo-ui/(app.py,run_filter.py,run.sh,index.html) in this folder.
| |
setup.sh builds two Python 3.13 virtualenvs pinned to the vulnerable and patched Semantic Kernel releases. run.sh executes exploit.py against both: on the vulnerable venv it upserts sample records into an InMemoryCollection, runs a benign filter, then the malicious dunder-traversal filter, and confirms RCE CONFIRMED when the marker file /tmp/pwned_by_filter is created by os.system; on the patched venv the same payload is rejected with a __subclasses__ ... is not allowed error. probe.py isolates just the validator/eval call for minimal reproduction. The optional demo-ui/ FastAPI app wires the same vulnerable filter path into a real Groq-hosted LLM agent chat UI, where a prompt-injected message triggers the tool call and pops a harmless ransom-note file to demonstrate real-world agent exploitation.
Detection & Indicators of Compromise
Signs of compromise:
- Search/filter tool inputs or logged LLM tool-call arguments containing dunder-attribute chains or nested subscript-call patterns
- Agent process spawning shell commands with no corresponding legitimate business logic
- Marker/artifact files (or worse, real payloads) appearing in the agent host’s filesystem shortly after a tool invocation
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade to semantic-kernel >= 1.39.4, which adds a dangerous-attribute blocklist (blocking __subclasses__ and similar) on top of the existing AST allowlist |
| Interim mitigation | Do not let LLM-generated or user/content-influenced strings reach eval()/compile() at all; if unavoidable, use a closed grammar instead of a node allowlist with open attribute access, and isolate the evaluating worker (seccomp, no network, unprivileged user) |
References
Notes
Mirrored from https://github.com/InertFluid/sk-cve-2026-26030-lab on 2026-07-05.
| |