Summary
Field Value CVE ID CVE-2026-17633 CVSS 8.5 (HIGH) CWE CWE-94 (Improper Control of Generation of Code) Affected Langflow OSS 1.0.0 – 1.10.3 Preconditions Any authenticated user +LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true
Vulnerable endpoint
POST /api/v1/custom_component
Langflow is an open-source low-code platform for building LLM applications and agent workflows visually. One of its features, Custom Components, lets users define a component’s behavior directly in Python. That feature is the attack surface for this vulnerability.
IBM’s security advisory (published August 5, 2026) disclosed a cluster of issues in Langflow OSS 1.0.0–1.10.3. CVE-2026–17633 is the authenticated RCE reachable through /api/v1/custom_component.
Root Cause - Source-Level Analysis
1.1 The vulnerable endpoint
From langflow/api/v1/endpoints.py (around line 1271):
@router.post("/custom_component", status_code=HTTPStatus.OK, include_in_schema=False)
async def custom_component(
raw_code: CustomComponentRequest,
user: CurrentActiveUser,
request: Request,
) -> CustomComponentResponse:
…
# The only gate: "is the custom-component feature enabled at all?"
if not settings.allow_custom_components and not code_hash_matches_any_template(raw_code.code, all_known):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, …)
# scan_code_security() is never called here
component = Component(_code=effective_code)
built_frontend_node, component_instance = build_custom_component_template(component, user_id=user.id)
Enter fullscreen mode Exit fullscreen mode
The important detail isn’t that there’s “no validation” - it’s that the validation checks the wrong thing. allow_custom_components answers “is this user allowed to create custom components,” not “is this code’s content safe.” In production, LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true is a common setting, and once it’s on, this check passes trivially and the code flows through with zero content inspection.
Langflow does ship a separate AST-based scanner, scan_code_security() (covered in section 5), but this endpoint’s execution path never calls it.
1.2 The actual bug lives in prepare_global_scope()
custom_component() calls build_custom_component_template(), which flows into create_class() in lfx/custom/validate.py. That function calls prepare_global_scope(), and the submitted code is exec()‘d shortly after.
def prepare_global_scope(module):
exec_globals = globals().copy()
…
for node in module.body:
if isinstance(node, ast.Import | ast.ImportFrom):
imports.append(node)
elif isinstance(node, ast.ClassDef | ast.FunctionDef | ast.Assign | ast.AnnAssign):
definitions.append(node)
…
if definitions:
compiled_code = compile(combined_module, "<string>", "exec")
exec(compiled_code, exec_globals) # ← exec() happens here
Enter fullscreen mode Exit fullscreen mode
This function walks the submitted code’s AST and only collects import statements and class/def/assignment nodes into definitions. That’s the trap.
- A top-level statement like
os.system(…)is, at the AST level, anast.Exprnode. -
ast.Exprisn’t in theisinstanceallow-list above → it’s silently dropped. - Code placed inside a class body, however, is part of that
ClassDefnode - so when the class is defined (i.e., whenexec()runs), that code executes right along with it.
# ❌ Module level - classified as ast.Expr, silently dropped by prepare_global_scope()
import os
os.system("id > /tmp/pwned.txt")
class PocComponent(Component):
…
# ✅ Inside the class body - part of ClassDef, runs when exec() defines the class
class PocComponent(Component):
os.system("id > /tmp/pwned.txt") # ← executes at class-definition time
…
Enter fullscreen mode Exit fullscreen mode
So the entire trick an attacker needs is: put the payload inside the class body, not at module level. No encoding tricks, no filter bypass gymnastics - just a simple, and simply devastating, design flaw.
Exploit Chain
The diagram below traces the full path from request to command execution.
In short:
Authenticated user (any privilege level)
│
▼
POST /api/v1/custom_component { "code": "<malicious Python class>" }
│
▼
build_custom_component_template() → create_class()
│
▼
prepare_global_scope() - only ClassDef nodes get collected into definitions/exec target
│
▼
compile_class_code() → exec(compiled_class, exec_globals)
│
▼
Class body executes at definition time → RCE achieved
Enter fullscreen mode Exit fullscreen mode
No LLM involvement, no scanner to evade. One HTTP request is enough.
Why the Scanner Didn’t Catch This
Langflow ships a separate AST-based scanner, scan_code_security(), in langflow/agentic/helpers/code_security.py. But it’s wired only into the Agentic Assistant path (validating LLM-generated component code) - it’s never called from /api/v1/custom_component at all.
So CVE-2026–17633 isn’t really a scanner bypass; it’s exploiting a code path the scanner was never attached to in the first place. The scanner’s own detection logic has a separate weakness (missing vars() coverage leading to a false is_safe: True verdict, tracked as CVE-2026–17632), which surfaced from analyzing the same codebase but is a distinct issue.
Patch and Mitigation
- Upgrade to Langflow 1.10.4+, which adds content validation to the
custom_componentendpoint - Keep
LANGFLOW_ALLOW_CUSTOM_COMPONENTSset tofalsein production unless the feature is genuinely needed - If custom components must be enabled, restrict which accounts can reach that feature
- Audit the group permissions of the container’s runtime user (
gid=0membership) to reduce lateral-movement / container-escape surface

