CVE-2026-26030 is not a prompt injection vulnerability. The attacker never manipulated a system prompt. They sent a city name. Semantic Kernel passed that city name into a Python lambda via string interpolation, and the model-supplied value became arbitrary code execution on the host. The agent did exactly what it was designed to do. The vulnerability was the assumption that what the agent generates can be trusted.
The attack surface developers never guard is not at the system input. It is at the output.
The Two-Hop Path: Where Trust Inverts
Traditional security protects the input boundary: WAFs, input validation, parameterized SQL. All of these controls exist at the point where user input enters the system. The agent-to-interpreter boundary receives none of them.
OWASP LLM05:2025 is direct: treat LLM responses as untrusted input, exactly as you treat user input. Most frameworks do not. Agent output flows to SQL engines, shell executors, and template renderers as trusted content by default.
The NCSC defines the consequence: when an LLM processes information from a party, its privileges drop to that of the party. Applications rarely apply this privilege drop at the output stage. The agent executes with application privileges, not those of the user whose content it processed.
The attack path has two hops. The first: user content to agent, the hop developers watch. The second: agent output to interpreter, the hop nobody watches. The second hop is where the injection lands.
SQL: Why Parameterized Queries Cannot Rescue NL2SQL
Parameterized queries prevent SQL injection when the attacker controls a value inside a pre-written query structure. In NL2SQL, the agent writes the query. The attacker does not inject SQL into an existing query: they cause the agent to generate a query that serves the attacker's intent.
ICSE 2025 documents the mechanism precisely. The attacker's payload is grammatically correct English. The HTTP request contains no SQL, no quote characters, no semicolons the developer tried to escape. The SQL appears only after inference, generated by the model from attacker instructions embedded in natural language.
Keysight BreakingPoint research confirms this empirically. An innocent business prompt causes the agent to generate a SELECT paired with a destructive statement. The WAF saw nothing. The SQL executed.
LangChain SQLDatabaseChain and similar NL2SQL integrations execute the full text of what the model returns. There is no structural element to parameterize: the agent generates the structure. The only effective defense is structural. The agent returns table name and filter values as JSON fields. The application inserts those values into a pre-written parameterized query. The agent never generates SQL syntax.
Shell and Code Interpreters: CVE-2026-26030 and the Lambda Injection
The Semantic Kernel Python SDK exposed a vector store filter that built a Python lambda via string interpolation: lambda x: x.city == '{model_output}'. An attacker controlling the model-returned value embedded Python class hierarchy traversal. The lambda became arbitrary code execution. CVE-2026-26030 was patched in v1.39.4 with an AST node-type allowlist and a function call allowlist. The patch validates generated code structure before execution, not prompt content before inference.
CVE-2026-25592 follows the same pattern at a different layer. DownloadFileAsync was decorated as a [KernelFunction] with no path validation. The model, attempting to complete its task, generated a file path pointing to the Windows Startup folder. The framework executed the download. The payload ran on next boot. Patched in v1.71.0.
subprocess(shell=True) is the Python equivalent: shell metacharacters in agent-generated filenames or arguments expand to arbitrary commands. The fix is shell=False with an argument list. No string interpolation, no shell expansion.
Template Engines: The Jinja2 Surface That Looks Like Data
Template engines evaluate expressions embedded in text. When agent output reaches a Jinja2, Handlebars, or Nunjucks renderer without sandboxing, the agent becomes a vector for server-side template injection with direct host access.
SpaCy-LLM SSTI (January 2025, GitHub issue #492): the framework called jinja2.Environment().from_string().render() on LLM output. The default Environment has no sandbox. A security researcher reported the issue as critical: complete server takeover via arbitrary command execution. Fixed in PR #491 by switching to SandboxedEnvironment with autoescape=True.
The email template attack pattern: an attacker embeds indirect injection in web content that the agent later processes. The injection causes the agent to include {{ config.SECRET_KEY }} in text that reaches the email template renderer. The template evaluates the expression. The secret appears in the sent email.
Most frameworks use Jinja2's default Environment because the content appears to be agent-generated, therefore trusted by assumption. SandboxedEnvironment is mandatory for any renderer that accepts agent output.
HTML Renderers and the Forensic Invisibility Problem
When agent-generated HTML reaches innerHTML or a server-side HTML renderer, the XSS payload arrives with no attacker fingerprint in logs or WAF records. The injection happened inside the model, not in the HTTP request. No script tag, no event handler attribute, no blocked keyword appears in ingress traffic.
Financial services incident (2024): an attacker used indirect injection to cause an agent to produce output carrying data to an attacker-controlled endpoint. The agent exfiltrated 45,000 customer records. The WAF had no signature match. Logs showed normal user queries and normal agent responses.
OWASP LLM05 documents the exfiltration pattern: prompt injection techniques cause LLMs to encode sensitive data and send it to attacker-controlled servers without output filtering. Egress-point filtering is the required control.
The forensic gap is the defining characteristic of agent output injection. Traditional injection leaves artifacts at the injection point. Agent output injection leaves no artifact until the downstream interpreter executes it.
The Fix Is at the Output Gate, Not the Input Gate
Hardening the system prompt does not harden the downstream SQL engine. Hardening the WAF does not harden the Jinja2 renderer. The only effective defense positions sanitization at the output boundary.
The Semantic Kernel patch for CVE-2026-26030 demonstrates the principle: AST node-type allowlist plus function call allowlist. The patch verifies generated code structure before passing it to the Python interpreter. It does not verify prompt content before inference.
Three patterns that work:
Structured output with allowlisted values: the agent returns {"table": "users", "filter": "city"}. The application inserts those values into a pre-written parameterized query. The agent never generates SQL syntax. There is no structure to inject.
AST validation before code execution: the Semantic Kernel patch approach. Generated code is checked against an allowlist of node types before passing to the Python interpreter. Any attribute or method call outside the allowlist is rejected.
Interpreter hardening: subprocess with shell=False and an argument list; Jinja2 SandboxedEnvironment; innerHTML replaced with textContent for agent-generated content; NCSC privilege-drop principle applied at the output stage.
MAGO Intel (intel.mago.team) tests agent deployments against the OWASP LLM05 taxonomy. It identifies output surfaces where agent-generated content reaches SQL engines, template renderers, or shell executors without structural validation. The test patterns draw from CVE-2026-26030 and the SpaCy-LLM incident.
Every injection vulnerability in agent frameworks since 2025 has the same anatomy: a developer trusted something the model generated. The model generated exactly what the attacker wanted. The defense is not a better model or a better prompt. It is an output gate that applies zero-trust to agent output with the same rigor the input gate applies to user input.
Top comments (0)