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.
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).
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 — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected langflow (pip) Versions
langflow (pip) · pip versions < 1.9.1 are affected.
How to Reproduce CVE-2026-55255
pruva-verify REPRO-2026-00268 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 Proof of Reproduction for CVE-2026-55255
- reached the target end-to-end
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
victim flow UUID supplied as the `model` value in POST /api/v1/responses with the attacker's x-api-key
- POST /api/v1/responses
- openai_responses.create_response
- get_flow_by_id_or_endpoint_name(request.model, str(api_key_user.id))
- UUID branch calls session.get(Flow, flow_id) without checking flow.user_id
How the agent worked
Root Cause and Exploit Chain for CVE-2026-55255
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-base0.9.0 (langflow1.9.0), helperlangflow.helpers.flow.get_flow_by_id_or_endpoint_name, reached vialangflow.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 commitf52d0f0072(langflow 1.9.0 / langflow-base 0.9.0), the parent of the fix commitb0afe3d2d6. - 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/responsesendpoint by supplying the victim's flow UUID asmodel, receiving a completed response whoseoutputcontained the flow's executed result. The same request against the fixed build (commitb0afe3d2d6) was rejected witherror.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
- Script:
bundle/repro/reproduction_steps.sh(self-contained, idempotent). - 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 viauv. - 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 (bothis_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}returns200for the victim and404for the attacker, confirming the flow is victim-owned and the attacker has no legitimate access. - IDOR (vulnerable): the attacker calls
POST /api/v1/responseswithx-api-key: <attacker>and{"model": "<VICTIM_FLOW_ID>", "input": "<probe>", "stream": false}. - Swaps in the fixed
helpers/flow.pyfrom commitb0afe3d2d6, restarts the server on the same SQLite DB (same flow/users), and repeats the attacker call.
- Builds the vulnerable Langflow 1.9.0 stack (langflow-base 0.9.0) from
the repo source at commit
- Expected evidence of reproduction:
- Vulnerable build: HTTP 200,
object: "response",status: "completed",modelequals the victim flow UUID, andoutput[].content[].textcontains 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.
- Vulnerable build: HTTP 200,
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 containingVICTIM_FLOW_IDanduser_id(victim).bundle/repro/artifacts/ownership_victim_get.txt/ownership_attacker_get.txt—GET /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_idon both branches ofget_flow_by_id_or_endpoint_nameand 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 intests/unit/helpers/test_flow_helpers.pycover 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/responsesby UUID.
Additional Notes
- Idempotency: The script extracts the vulnerable source fresh each run
(so the editable
langflow-baseinstall always starts from the vulnerableflow.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 actualOpenAIResponsesRequestschema field is"input". The reproduction uses the correctinputfield; the IDOR is independent of this field (it is triggered bymodel= 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+outputvserror.code: flow_not_found), which is a stronger signal than the status code. - Scope: The vulnerable
wt-vuln/wt-fixedworktrees provided in the project cache already contained the fix commit, so the reproduction anchors the vulnerable checkout tob0afe3d2d6^(f52d0f0072) per the fixed-commit checkout rule, and the fixed checkout tob0afe3d2d6.
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.
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=== 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
Artifacts and Evidence for CVE-2026-55255
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-55255
Upgrade langflow (pip) · pip to >=1.9.1 or later.
FAQ: CVE-2026-55255
How does the CVE-2026-55255 IDOR attack work?
Which Langflow versions are affected by CVE-2026-55255, and where is it fixed?
How severe is CVE-2026-55255?
How can I reproduce CVE-2026-55255?
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.