# REPRO-2026-00342: Langflow contains an unauthenticated remote code execution vulnerability in the validate endpoint that can lead to arbitrary Python code execution as root. ## Summary Status: published Severity: critical CVSS: 9.8 / 10 CWE: CWE-94 (Improper Control of Generation of Code ('Code Injection')) Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00342 CVE: CVE-2026-0768 ## Package Name: langflow-ai/langflow Ecosystem: PyPI Affected: Endpoint fully unauthenticated in langflow <= 1.2.x; exec(code_obj) of user code persists through >= 1.8.0-rc. NO COMPLETE FIX EXISTS. Fixed: unknown ## Root Cause # RCA Report — CVE-2026-0768 (Langflow unauthenticated RCE via /api/v1/validate/code) ## Summary Langflow (<= 1.2.x, and partially thereafter) exposes `POST /api/v1/validate/code` without authentication. The endpoint calls `validate_code()` in `src/backend/base/langflow/utils/validate.py`, which `ast.parse()`s the attacker-supplied `code` field and then **`exec()`s each top-level `FunctionDef` node** after compiling it. Python evaluates default-argument expressions and decorators **at function-definition time**, so a function body that never runs still executes arbitrary expressions embedded in its default arguments or decorators. The endpoint's exception handler returns the resulting error text in the HTTP response (`detail.function.errors[0]`), giving a built-in exfiltration channel for command output — a fully non-blind, unauthenticated remote code execution. ## Impact - **Package/component:** `langflow` (`langflow.utils.validate.validate_code`, reached from the `/api/v1/validate/code` FastAPI route). - **Affected versions:** Unauthenticated on langflow <= 1.2.x (verified on the official image `v1.1.1`). Version 1.3.0 (commit `faac4db`, PR #6911) added `get_current_active_user` auth to the route only — `exec(code_obj)` of user code survives, and `LANGFLOW_AUTO_LOGIN=true` (the default) auto-authenticates requests, so default 1.3.0+ deployments remain effectively unauthenticated. Any authenticated user retains RCE on 1.3.0+. No complete fix exists as of the disclosed range (survives through >= 1.8.0-rc per advisory). - **Risk level:** Critical (CVSS 9.8, CWE-94, ZDI-26-034 / ZDI-CAN-27322). Sibling CVE-2025-3248 (same endpoint, same root cause) is in CISA KEV. ## Impact Parity - **Disclosed/claimed maximum impact:** Unauthenticated arbitrary Python code execution in the Langflow server process (advisory says "as root"; in the official container image the process runs as `uid=1000(user) gid=0(root)`, i.e. full container compromise). - **Reproduced impact from this run:** **Full parity — unauthenticated remote command execution.** Two independent fresh Langflow 1.1.1 containers executed attacker-selected shell commands (`id`) via the default-argument `exec()` vector; output `uid=1000(user) gid=0(root) groups=0(root)` was exfiltrated in the HTTP response JSON at `detail.function.errors[0]`; each instance also wrote a unique marker file inside the container filesystem via the executed command (`marker_output` evidence). A decorator-based vector (`@exec(...)`) triggered the identical sink, proving payload-shape agnosticism. - **Parity:** `full`. - **Not demonstrated:** Nothing of the claimed impact is missing. (Note: the advisory's "as root" phrasing maps to `gid=0(root)` container execution observed here; the server process itself runs as uid 1000 in the official image.) ## Root Cause 1. The route `POST /api/v1/validate/code` (langflow <= 1.2.x) has no authentication dependency. 2. `validate_code()` runs `ast.parse(code)`, then for each top-level `FunctionDef` node: `code_obj = compile(ast.Module(body=[node], type_ignores=[]), '', 'exec'); exec(code_obj)` — deliberately executing user code to "validate" it. 3. Python evaluates **default-argument expressions and decorator expressions when the `def` statement executes**, not when the function is called. An attacker embeds `exec('raise Exception(__import__("subprocess").check_output("id", shell=True))')` in a default argument; it runs during `exec(code_obj)` inside the server process. 4. The raised `Exception` text (containing the command output) is captured by the endpoint's error handling and returned to the attacker in `detail.function.errors[0]` — a response-side exfiltration channel making the RCE non-blind. 5. **Partial fix:** langflow 1.3.0 (commit `faac4db`, PR #6911) added `get_current_active_user` to the route. Because `LANGFLOW_AUTO_LOGIN=true` is the default, requests are auto-authenticated and the same payload still executes on default 1.3.0 deployments (verified in this run). With `LANGFLOW_AUTO_LOGIN=false`, the same request is rejected with HTTP 403 `An API key must be passed as query or header` and no code executes. ## Reproduction Steps 1. Script: `bundle/repro/reproduction_steps.sh` (self-contained; run with `bash bundle/repro/reproduction_steps.sh`). Two consecutive successful runs confirmed idempotency (exit 0 both times). 2. What it does: - Pulls digest-pinned official images: vulnerable `langflowai/langflow@sha256:b56d4cfe...` (v1.1.1) and fixed `langflowai/langflow@sha256:8c124064...` (1.3.0). - Starts four fresh containers: two vulnerable (`LANGFLOW_AUTO_LOGIN=true`), one fixed with `LANGFLOW_AUTO_LOGIN=false` (auth enforced), one fixed with defaults (auto-login). - Waits for `GET /health` == `{"status":"ok"}` on each. - **Vulnerable attempts (x2):** sends the exact contract payload `{"code": "def exploit(cd=exec('raise Exception(__import__(\"subprocess\").check_output(\"id\", shell=True))')): pass"}`; asserts HTTP 200 and `uid=` in `detail.function.errors[0]`. - **Marker-backed execution (x2 fresh instances):** payload writes a unique marker file to `/tmp/pruva_marker.txt` inside each container via the executed command; asserts marker bytes match and `uid=` is exfiltrated. - **Decorator variant:** `@exec(...)` payload; asserts command output in `function.errors[0]`. - **Fixed negative control (x2):** same payloads against auth-enforced 1.3.0; asserts 401/403, no `uid=`, and no marker file created in the container. - **Partial-fix documentation (control B):** same payload against default 1.3.0 (auto-login); observes it still executes (expected partial-fix behavior, not a reproduction failure). - Writes `bundle/repro/runtime_manifest.json` with target identity (image digests) and SHA-256 of all proof artifacts; cleans up containers. 3. Expected evidence: HTTP 200 responses containing `b'uid=1000(user) gid=0(root) groups=0(root)\n'` at `detail.function.errors[0]`, marker files with exact attacker-chosen bytes, and 403 rejection on the auth-enforced fixed build. ## Evidence All artifacts under `bundle/` (paths relative to bundle root), SHA-256 map in `bundle/repro/runtime_manifest.json`: - `logs/reproduction_steps.log` — full run transcript (diagnostic). - `logs/repro/image_identity.txt` — immutable image digests/arch used. - `logs/repro/containers.txt` — live container listing. - `logs/repro/attempts/vuln_attempt_{1,2}_{request,response}.txt` — primary payload and responses: `{"imports":{"errors":[]},"function":{"errors":["b'uid=1000(user) gid=0(root) groups=0(root)\n'"]}}` - `logs/repro/attempts/vuln_marker_{a,b}_{request,response}.txt`, `vuln_{a,b}_marker.txt` — marker-backed command execution on two fresh instances (markers `PRUVA-CMDEXEC-1788272103-6040-A` / `-B`, exact bytes verified). - `logs/repro/attempts/vuln_variant_decorator_{request,response}.txt` — decorator vector output `b'1000\n'` (`id -u`). - `logs/repro/attempts/fixed_attempt_{1,2}_{request,response}.txt`, `negative_control_observation.json` — 403 `{"detail":"An API key must be passed as query or header"}`, marker absent. - `logs/repro/attempts/fixed_auto_attempt_1_{request,response}.txt` — 1.3.0 default deployment still executes (partial fix). **Environment:** Docker (overlay2) on linux/x86_64; official images `langflowai/langflow` v1.1.1 (digest `sha256:b56d4cfe18284e9fb2f1ec2d1bc9a29107a8c893397543e4937a55cda0136cd3`) and 1.3.0 (digest `sha256:8c124064a4410ceff7a7ffbee3aec393e3b9fb2e3e43a163b537074143a38ca5`); no sanitizers or instrumentation; plain HTTP via curl. **Exploit knowledge records (current run):** primitive `9199a628-4fa2-4c50-9517-54e12e959e07` (control_flow_influence — full attacker control of executed Python via the validate_code exec sink) and derived capability `c216aee7-41df-4f5f-b157-9fc7518764cf` (command_execution, generic_runtime attestation, two marker-backed process samples + negative control). ⚠️ Record `2a7dbd1c-4e51-4243-87b6-0e8bfb97fe28` in `bundle/repro/exploit_knowledge.json` was an accidental placeholder submission ("address_disclosure", title "Placeholder") and is **invalid — no address disclosure was observed in this run**; it must be ignored by downstream stages and is not part of any composition. ## Recommendations / Next Steps - **Fix:** stop `exec()`ing user-supplied code during validation. Validate with `ast.parse` + a strict allowlist walk over the AST (rejecting `exec`/`eval`/`__import__`/dunder attribute access anywhere in the tree — defaults, decorators, lambdas, comprehensions — not just function bodies), or run validation in a sandboxed subprocess with no network/fs and resource limits. Auth alone is not a fix. - **Upgrade guidance:** no complete fix exists in the disclosed range; 1.3.0+ only adds route auth, defeated by default `LANGFLOW_AUTO_LOGIN=true`. Set `LANGFLOW_AUTO_LOGIN=false` + API-key auth to at least force authentication, and restrict `/api/v1/validate/code` at the reverse proxy/WAF. - **Detection:** requests to `/api/v1/validate/code` whose body contains default-arg/decorator `exec(`/`eval(`/`__import__(`/`subprocess` patterns; EDR: Langflow python worker spawning shell children. ## Additional Notes - **Idempotency:** the script removes prior containers, reclaims ports 27860–27863, rewrites attempt files, and passed two consecutive clean runs (exit 0, all checks green each time). - **Limitations:** host ports are fixed (27860–27863) and must be free; the script checks and fails fast if occupied. First execution on a cold Docker cache pulls ~2 images (several minutes); subsequent runs reuse them. The 1.3.0 "fixed" image is a partial-fix control (route auth added), matching the advisory's affected/fix state — the exec sink itself remains exploitable post-auth, which is exactly what control B documents and what the vuln_variant stage can explore further. - Marker evidence files were extracted with `docker exec cat` (not `docker cp`) to preserve worker file ownership in the bundle. ## Reproduction Details Reproduced: 2026-09-02T03:58:13.831Z Duration: 3788 seconds Tool calls: 227 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00342 pruva-verify CVE-2026-0768 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00342&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00342/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-0768 - Source: https://cve.org/CVERecord?id=CVE-2026-0768 ## Artifacts - bundle/repro/rca_report.md (analysis, 10073 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 16107 bytes) - bundle/logs/repro/attempts/fixed_attempt_1_request.txt (other, 254 bytes) - bundle/logs/repro/attempts/fixed_attempt_1_response.txt (other, 67 bytes) - bundle/logs/repro/attempts/fixed_attempt_2_request.txt (other, 326 bytes) - bundle/logs/repro/attempts/fixed_auto_attempt_1_request.txt (other, 254 bytes) - bundle/logs/repro/attempts/fixed_auto_attempt_1_response.txt (other, 109 bytes) - bundle/logs/repro/attempts/vuln_marker_b_request.txt (other, 319 bytes) - bundle/logs/repro/attempts/vuln_variant_decorator_request.txt (other, 251 bytes) - bundle/repro/runtime_manifest.json (other, 5090 bytes) - bundle/repro/validation_verdict.json (other, 1649 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00342 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00342/artifacts/bundle/repro/reproduction_steps.sh - Web: https://www.pruva.dev/reproductions/REPRO-2026-00342 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://www.pruva.dev