flawopen.com/AI Security/semantic-kernel-inmemory-vector-filter-cve-2026-26030
Semantic Kernel CVE-2026-26030: Filter Code Injection
Prompt-controlled vector filters bypassed Semantic Kernel’s AST checks and reached Python eval. Upgrade the Python SDK to 1.39.4 or later.[1], [4]
Imagine a librarian who accepts a search rule written on a card, checks that it uses a few familiar words, then asks a computer to run the card as a tiny program. An attacker who can influence the card can exploit gaps in that check. CVE-2026-26030 let a model-influenced filter cross from data lookup into code execution inside the application process.[4]
Core Concepts & Subsystem Terms
InMemoryVectorStore- Semantic Kernel’s in-process vector store. Its search operation can apply a filter to records before returning matches.[4]
Filter expression- A predicate that decides whether a stored record is eligible for a vector-search result. In the affected Python path, string filters were parsed as Python expressions and evaluated.[4]
Abstract Syntax Tree (AST)- A structured representation of parsed code. An AST validator can allow only the expression forms a filter needs, but an incomplete allowlist can leave paths to Python object introspection.[2], [4]
Prompt injection- Untrusted text influences an AI model to take an unintended action. Here, the security impact depends on the application exposing the vulnerable filter path to model-controlled input.[4]
Framework mappings
- CWE reference for the weakness described in this guide: CWE-94
Step-by-Step Attack Flow
Untrusted content influences a model-selected filter
The documented attack requires a prompt-injection path that influences agent input and a Search Plugin backed by the default InMemoryVectorStore filter. A model-generated filter must not be treated as trusted code merely because it was returned through a typed tool call.[4]
The filter becomes Python code
The pre-fix path parsed a filter as an AST and evaluated it with builtins removed, but its checks did not sufficiently constrain object traversal. The 1.39.4 implementation retains ast.Subscript for record-field access; it combines the node-type allowlist with a dangerous-attribute blocklist, lambda-parameter-only bare names, and an allowlist for calls. The upstream source does not reject subscripts by themselves, and eval with empty builtins is not a sandbox.[2], [4]
Execution crosses into the application process
When the bypassed expression is evaluated during vector search, attacker-influenced logic can execute in the Python process hosting the agent. The resulting access is bounded by that process’s operating-system permissions, environment, credentials, and network reach.[4]
Patch the parser boundary and investigate exposure
Upgrade semantic-kernel to 1.39.4 or later. The upstream patch adds dangerous-attribute checks and restricts bare names to the lambda parameter; Microsoft documents an AST-node allowlist, function allowlist, attribute blocklist, and name restriction. Review the affected deployment window for process, network, and credential-use anomalies.[1], [2], [3], [4]
Source Code: Flaw vs. Secure Implementation
The vulnerable pane is a representative pattern, not verbatim product source: a model-controlled value is interpolated into Python syntax before eval. The hardened pane focuses on the upstream 1.39.4 checks. Its configured AST allowlist intentionally includes ast.Subscript for record-field access; the patch combines that node allowlist with blocked dangerous attributes, lambda-only bare names, and allowed function or method calls. Empty builtins alone do not make eval a sandbox. Upgrade the package; do not copy this excerpt as a standalone validator.[2], [6], [4]
# Representative pre-fix pattern: model-controlled text changes the expression.
filter_text = f"lambda x: x.city == '{model_selected_city}'"
tree = ast.parse(filter_text, mode="eval")
code = compile(tree, filename="<filter>", mode="eval")
filter_fn = eval(code, {"__builtins__": {}}, {}) # nosec
def validate_filter(self, tree):
# Focused upstream 1.39.4 checks; exception messages are shortened.
# Top-level filters must be lambda expressions.
if not (isinstance(tree, ast.Expression) and isinstance(tree.body, ast.Lambda)):
raise VectorStoreOperationException("Filter must be a lambda expression")
lambda_param_names = {arg.arg for arg in tree.body.args.args}
for node in ast.walk(tree):
if type(node) not in self.allowed_filter_ast_nodes:
raise VectorStoreOperationException("AST node type is not allowed")
if isinstance(node, ast.Attribute) and node.attr in self.blocked_filter_attributes:
raise VectorStoreOperationException("Dangerous attribute is not allowed")
if isinstance(node, ast.Name) and node.id not in lambda_param_names:
raise VectorStoreOperationException("Only lambda parameter names are allowed")
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
func_name = node.func.id
elif isinstance(node.func, ast.Attribute):
func_name = node.func.attr
else:
func_name = None
if func_name and func_name not in self.allowed_filter_functions:
raise VectorStoreOperationException("Function is not allowed")
code = compile(tree, filename="<filter>", mode="eval")
func = eval(code, {"__builtins__": {}}, {}) # nosec
Engineering & System Hardening Checklist
- ✓Find every deployed Python environment that includes semantic-kernel and record the resolved version, including containers, notebooks, workers, and CI images.[4]
- ✓Upgrade affected installations to semantic-kernel 1.39.4 or later, regenerate lockfiles, and rebuild immutable images from the patched dependency set.[1], [3]
- ✓Until upgrade is complete, disable the InMemoryVectorStore filter path in production; the vendor advisory recommends avoiding this store in production as a workaround.[1]
- ✓For every PR that adds or changes a vector-search filter, add tests proving user text, retrieved text, and model-supplied arguments cannot change the filter’s parsed structure; reject unsupported forms.[4]
- ✓Add a regression test that rejects AST attribute access to blocked names and bare names outside the lambda parameter, without including executable exploit payloads.[2]
- ✓Represent model-selected filters as typed data with an explicit field and operator allowlist; add tests that reject expression strings and unknown fields or operators.
- ✓Run agent processes with minimal filesystem, network, and credential access so a future code-execution flaw has a bounded blast radius.[4]
- ✓Add an agent-host detection regression test that emits representative child-process and outbound-connection events and asserts the deployed telemetry rule alerts on both.[4]