Skip to content

CVE-2026-55255: Verified Reproduction

CVE-2026-55255: Langflow’s /api/v1/responses endpoint contains an IDOR that lets any authenticated user execute another user’s flow by supplying the victim’s flow UUID.

CVE-2026-55255 is verified against langflow (pip) · pip. Affected versions: < 1.9.1. Fixed in >=1.9.1. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00268.

REPRO-2026-00268 langflow (pip) · pip Jul 8, 2026 CVE entry .txt
Severity
CRITICAL
CVSS
9.9
Confidence
HIGH
Reproduced in
28m 29s
Tool calls
241
Spend
$4.67
01 · Overview

What Is CVE-2026-55255?

CVE-2026-55255 is a critical (CVSS 9.9) Insecure Direct Object Reference (CWE-639) in Langflow's /api/v1/responses endpoint that lets any authenticated user execute another user's flow by supplying the victim's flow UUID. Pruva reproduced it (reproduction REPRO-2026-00268).

02 · Severity & CVSS

CVE-2026-55255 Severity & CVSS Score

CVE-2026-55255 is rated critical severity, with a CVSS base score of 9.9 out of 10.

CRITICAL threat level
9.9 / 10 CVSS base
Weakness CWE-639 — Authorization Bypass Through User-Controlled Key

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

Affected langflow (pip) Versions

langflow (pip) · pip versions < 1.9.1 are affected.

How to Reproduce CVE-2026-55255

$ pruva-verify REPRO-2026-00268
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00268/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-55255

Authorization bypass — reproduced
  • reached the target end-to-end
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

victim flow UUID supplied as the `model` value in POST /api/v1/responses with the attacker's x-api-key

Attack chain
  1. POST /api/v1/responses
  2. openai_responses.create_response
  3. get_flow_by_id_or_endpoint_name(request.model, str(api_key_user.id))
  4. UUID branch calls session.get(Flow, flow_id) without checking flow.user_id
How the agent worked 564 events · 241 tool calls · 28 min
28 minDuration
241Tool calls
151Reasoning steps
564Events
3Dead-ends
Agent activity over 28 min
Support
13
Repro
294
Judge
35
Variant
218
0:0028:29

Root Cause and Exploit Chain for CVE-2026-55255

Versions: Langflow < 1.9.1 (advisory) / < 1.9.2 (NVD). The

Langflow's OpenAI-compatible POST /api/v1/responses endpoint contains an Insecure Direct Object Reference (IDOR). The helper get_flow_by_id_or_endpoint_name resolves a flow by UUID using session.get(Flow, flow_id) without comparing flow.user_id to the caller's identity. Because /api/v1/responses passes the authenticated user's id as the second argument but the UUID branch ignores it, any authenticated user can execute any other user's flow by supplying the victim's flow UUID as the request model value. The fix (commit b0afe3d2d6, PR #12832) adds an ownership check on both the UUID and endpoint-name branches and returns the flow as not-found when the caller is not the owner.

  • Package/component affected: langflow-base 0.9.0 (langflow 1.9.0), helper langflow.helpers.flow.get_flow_by_id_or_endpoint_name, reached via langflow.api.v1.openai_responses.create_response (POST /api/v1/responses).
  • Affected versions: Langflow < 1.9.1 (advisory) / < 1.9.2 (NVD). The vulnerable source analyzed here is commit f52d0f0072 (langflow 1.9.0 / langflow-base 0.9.0), the parent of the fix commit b0afe3d2d6.
  • Risk level and consequences: High. An authenticated (non-superuser) attacker can execute arbitrary flows owned by other tenants by UUID, exposing those flows' outputs (cross-tenant data disclosure), consuming the victims' resources, and affecting integrity. This is a cross-tenant authorization bypass.

Impact Parity

  • Disclosed/claimed maximum impact: Authenticated cross-tenant flow execution / IDOR (authorization bypass) on /api/v1/responses.
  • Reproduced impact from this run: An authenticated low-privileged attacker executed a victim-owned flow through the real POST /api/v1/responses endpoint by supplying the victim's flow UUID as model, receiving a completed response whose output contained the flow's executed result. The same request against the fixed build (commit b0afe3d2d6) was rejected with error.code = "flow_not_found".
  • Parity: full — the claimed IDOR (cross-tenant flow execution) was reproduced end-to-end through the real HTTP API on a running Langflow 1.9.0 server, with a fixed-commit negative control.
  • Not demonstrated: No privilege escalation to superuser, no code execution beyond running an existing flow, and no RCE. The proof demonstrates the authorization bypass (flow execution + output disclosure), which is the claimed impact.

Root Cause

get_flow_by_id_or_endpoint_name(flow_id_or_name, user_id) has two branches:

# vulnerable (commit f52d0f0072, langflow-base 0.9.0)
try:
    flow_id = UUID(flow_id_or_name)
    flow = await session.get(Flow, flow_id)     # <-- no user_id check
except ValueError:
    endpoint_name = flow_id_or_name
    stmt = select(Flow).where(Flow.endpoint_name == endpoint_name)
    if user_id:                                  # <-- only scoped when truthy
        uuid_user_id = UUID(user_id) if isinstance(user_id, str) else user_id
        stmt = stmt.where(Flow.user_id == uuid_user_id)
    flow = (await session.exec(stmt)).first()

The UUID branch calls session.get(Flow, flow_id) and returns the flow unconditionally — the user_id argument is never consulted. The endpoint-name branch only filters by user_id when a truthy value is passed.

POST /api/v1/responses (openai_responses.create_response) authenticates the caller via api_key_security (the x-api-key header) and then resolves the flow with the authenticated user's id:

flow = await get_flow_by_id_or_endpoint_name(request.model, str(api_key_user.id))

request.model is attacker-controlled. When it parses as a UUID, the vulnerable UUID branch ignores str(api_key_user.id) and returns any flow with that UUID, regardless of ownership. The flow is then executed by run_flow_for_openai_responses, so the attacker runs the victim's flow and reads its output.

The fix (commit b0afe3d2d6, PR #12832, "fix(security): close IDOR in get_flow_by_id_or_endpoint_name (LE-639)") normalizes user_id once and enforces it on both branches:

flow = await session.get(Flow, flow_id)
if flow is not None and uuid_user_id is not None and flow.user_id != uuid_user_id:
    flow = None      # cross-user lookup -> treated as not found (404)

so the shared if flow is None: raise HTTPException(404) fires for cross-tenant lookups and /api/v1/responses returns error.code = "flow_not_found".

Fix commit: b0afe3d2d6 (PR #12832), file src/backend/base/langflow/helpers/flow.py.

Reproduction Steps

  1. Script: bundle/repro/reproduction_steps.sh (self-contained, idempotent).
  2. What it does:
    • Builds the vulnerable Langflow 1.9.0 stack (langflow-base 0.9.0) from the repo source at commit f52d0f0072 (b0afe3d2d6^). PyPI never published langflow 1.9.0/1.9.1/1.9.2 (the 1.x series starts at 1.9.3 which already contains the fix), so the vulnerable stack must be built from source. The sandbox ships only Python 3.14 but langflow 1.9.0 requires <3.14, so a standalone CPython 3.12 is provisioned via uv.
    • Starts a real Langflow server (multi-user mode, SQLite) on 127.0.0.1:7860.
    • Registers a victim and an attacker user via the public POST /api/v1/users/ signup endpoint (both is_superuser: false).
    • Logs both in, mints API keys via POST /api/v1/api_key/.
    • The victim creates a flow (ChatInput→ChatOutput echo, no LLM) and captures the victim flow UUID (VICTIM_FLOW_ID).
    • Ownership proof: GET /api/v1/flows/{id} returns 200 for the victim and 404 for the attacker, confirming the flow is victim-owned and the attacker has no legitimate access.
    • IDOR (vulnerable): the attacker calls POST /api/v1/responses with x-api-key: <attacker> and {"model": "<VICTIM_FLOW_ID>", "input": "<probe>", "stream": false}.
    • Swaps in the fixed helpers/flow.py from commit b0afe3d2d6, restarts the server on the same SQLite DB (same flow/users), and repeats the attacker call.
  3. Expected evidence of reproduction:
    • Vulnerable build: HTTP 200, object: "response", status: "completed", model equals the victim flow UUID, and output[].content[].text contains the attacker's probe string — the victim's flow was executed by the attacker.
    • Fixed build: {"error": {"code": "flow_not_found", "message": "Flow with id '<uuid>' not found"}} — the same request is blocked.
    • The only difference between the two phases is helpers/flow.py, proving the divergence is the missing ownership check.

Evidence

  • bundle/logs/reproduction_steps.log — full run log with version, marker checks, and both IDOR responses.
  • bundle/logs/langflow_server_vuln.log / bundle/logs/langflow_server_fixed.log — server startup/health logs for each phase.
  • bundle/repro/artifacts/victim_register.json / attacker_register.json — signup responses (is_superuser: false).
  • bundle/repro/artifacts/victim_flow_create.json — flow creation response containing VICTIM_FLOW_ID and user_id (victim).
  • bundle/repro/artifacts/ownership_victim_get.txt / ownership_attacker_get.txtGET /api/v1/flows/{id} status codes (200 vs 404).
  • bundle/repro/artifacts/idor_response_vuln.json — vulnerable IDOR response (status: completed, probe in output).
  • bundle/repro/artifacts/idor_response_fixed.json — fixed IDOR response (error.code: flow_not_found).
  • bundle/repro/artifacts/summary.json — structured classification (idor_confirmed: true).
  • bundle/repro/runtime_manifest.json — runtime evidence manifest (service_started/healthcheck_passed/target_path_reached = true).
  • bundle/repro/validation_verdict.json — structured verdict.

Environment: Langflow 1.9.0 (langflow-base 0.9.0) built from source commit f52d0f0072; fixed control commit b0afe3d2d6; Python 3.12 (uv standalone); SQLite; uvicorn backend-only; multi-user mode (LANGFLOW_AUTO_LOGIN=false, signup enabled).

Key excerpt (vulnerable IDOR response):

{
  "object": "response",
  "status": "completed",
  "error": null,
  "model": "<VICTIM_FLOW_UUID>",
  "output": [{ "type": "message", "status": "completed",
    "content": [{ "type": "output_text", "text": "IDOR_PROBE_VULN_..." }] }]
}

Key excerpt (fixed IDOR response):

{ "error": { "message": "Flow with id '<VICTIM_FLOW_UUID>' not found",
  "type": "invalid_request_error", "code": "flow_not_found" } }

Recommendations / Next Steps

  • Upgrade to Langflow >= 1.9.1 (contains the fix from PR #12832 / commit b0afe3d2d6).
  • Fix approach (already applied upstream): enforce user_id on both branches of get_flow_by_id_or_endpoint_name and return the flow as not-found on cross-user lookups, so the shared 404 path fires and flow existence is not disclosed to unauthorized callers.
  • Defense in depth: audit other callers of get_flow_by_id_or_endpoint_name (e.g. webhook/run endpoints) for the same ownership gap; add an integration test that asserts a cross-tenant UUID lookup returns 404 (the upstream regression tests in tests/unit/helpers/test_flow_helpers.py cover this).
  • Testing recommendations: add a multi-tenant API test that creates a flow as user A and asserts user B cannot execute it via /api/v1/responses by UUID.

Additional Notes

  • Idempotency: The script extracts the vulnerable source fresh each run (so the editable langflow-base install always starts from the vulnerable flow.py), reuses a cached venv at the durable project cache path when present, and uses a fresh SQLite DB each run. Consecutive runs produce the same vulnerable/completed vs fixed/flow_not_found divergence.
  • Note on request field: the advisory's example curl uses "input_value", but the actual OpenAIResponsesRequest schema field is "input". The reproduction uses the correct input field; the IDOR is independent of this field (it is triggered by model = victim flow UUID).
  • Note on HTTP status: both the vulnerable (flow executed) and the fixed (flow not found) responses are returned with HTTP 200 by the OpenAI-compatible endpoint; the discriminator is the response body (status: completed + output vs error.code: flow_not_found), which is a stronger signal than the status code.
  • Scope: The vulnerable wt-vuln/wt-fixed worktrees provided in the project cache already contained the fix commit, so the reproduction anchors the vulnerable checkout to b0afe3d2d6^ (f52d0f0072) per the fixed-commit checkout rule, and the fixed checkout to b0afe3d2d6.

CVE-2026-55255 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.

Event 1/40
0:000:32
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-55255 · REPRO-20
0:02
0:03
web search
0:05
web search
0:06
0:08
0:19
0:19
extract_facts
no facts extracted
0:20
0:20
0:20
supportrepro
0:22
0:22
0:23
0:23
0:24
0:24
0:24
0:25
0:25
0:25
0:28
0:28
0:28
$cd /data/pruva/project-cache/688af86e-dc4c-4d20-bb02-ccdf8da2c7d0 && echo "=== wt-vuln HEAD ===" && git -C wt-vuln log --oneline -1 && echo "=== wt-fixed HEAD ===" && git -C wt-fixed log --oneline -1 && echo "=== repo HEAD ===" && git -C repo log --oneline -1 && echo "=== branches ===" && git -C repo branch -a
0.2s
=== wt-vuln HEAD ===
b11bd1f261 chore: update lock files
=== wt-fixed HEAD ===
69982462bb fix: add SSRF protection to legacy RSS Reader and SearXNG components (#13395)
=== repo HEAD ===
315cc41b43 ci: surface nightly Group 3 stall via 90s pytest thread-timeout (#13938)
=== branches ===
* main
0:30
0:30
0:30
0:30
0:30
0:32
08 · How to Fix

How to Fix CVE-2026-55255

Upgrade langflow (pip) · pip to >=1.9.1 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-55255 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-55255

How does the CVE-2026-55255 IDOR attack work?

An authenticated, non-superuser attacker sends a request to POST /api/v1/responses with the model value set to another user's flow UUID. Because get_flow_by_id_or_endpoint_name looks up the flow purely by UUID without an ownership check, Langflow executes the victim's flow and returns its output to the attacker, exposing that user's flow logic and data across tenants.

Which Langflow versions are affected by CVE-2026-55255, and where is it fixed?

Langflow versions before 1.9.1 are affected. It is fixed in 1.9.1 and later, which adds an ownership check on both the UUID and endpoint-name resolution branches and returns the flow as not-found when the caller isn't the owner.

How severe is CVE-2026-55255?

It is rated critical severity with a CVSS score of 9.9. Any authenticated user can execute arbitrary flows belonging to other tenants, exposing their outputs and consuming their resources without needing superuser privileges.

How can I reproduce CVE-2026-55255?

Download the verified script from this page and run it in an isolated environment against Langflow before 1.9.1. It authenticates as one user, creates a flow, then authenticates as a second user and calls /api/v1/responses with the first user's flow UUID to show cross-tenant execution succeeding, then confirms the fixed version rejects it.
11 · References

References for CVE-2026-55255

Authoritative sources for CVE-2026-55255 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.