GHSA-gh98-4phw-6fmw: SQL Injection
Summary
Before Langflow 1.10.1, two deprecated but still-routed endpoints in the chat API did not check that the authenticated caller owns the flow referenced in the URL path:
- POST /api/v1/build/{flowid}/vertices (retrieveverticesorder) - POST /api/v1/build/{flowid}/vertices/{vertexid} (buildvertex)
Any authenticated user who knows (or obtains) another user's flowid could load that user's private flow graph, enumerate its vertex IDs, and build (execute) individual vertices of it, receiving the vertex results in the response.
Both routes are declared with deprecated=True, includeinschema=False, so they do not appear in the OpenAPI docs, but they remained registered on the router. The Langflow UI no longer calls them (it uses POST /api/v1/build/{flowid}/flow), so they are only reachable by direct HTTP requests.
The issue is fixed in Langflow 1.10.1 (langflow-base 0.10.1) by #13153.
Details
In affected versions, both handlers go straight to the graph loader:
- retrieveverticesorder (src/backend/base/langflow/api/v1/chat.py, lines 81-159 in 1.9.1) only required authentication via dependencies=[Depends(getcurrentactiveuser)]. The user was never injected into the handler, and the flow was loaded with buildgraphfromdb(flowid=flowid, ...). - buildvertex (chat.py, lines 320-491 in 1.9.1) receives currentuser, but only uses it for variable resolution (userid=str(currentuser.id)). It is never used to scope the flow lookup. The graph is taken from the chat-service cache keyed by flowid, or rebuilt with buildgraphfromdb on a cache miss. - buildgraphfromdbnocache (src/backend/base/langflow/api/utils/flowutils.py) does a bare primary-key lookup, session.get(Flow, flowid), with no owner filter.
The supported replacement, POST /api/v1/build/{flowid}/flow, already restricted the lookup to the owner or a PUBLIC flow (added in #12305 for GHSA-qj98-rhf8-v93f). The deprecated siblings were not covered by that fix, nor by the readflow fix for GHSA-8c4j-f57c-35cf.
Additional side effects in affected versions:
- retrieveverticesorder stores the graph in the chat-service cache under the victim's flowid (chatservice.setcache(str(flowid), graph)). When the request includes a data body, the cached graph is attacker-supplied. - buildvertex schedules logvertexbuild for the victim's flowid, so attacker-triggered builds are recorded in the victim flow's vertex build history.
Note on older versions: up to and including 1.7.1, these two routes had no authentication dependency at all. Authentication was added in 1.7.2 by #10977, but no ownership check was added. This advisory covers the missing ownership check, which is present in every release from 1.0.0 (when the routes were introduced) through 1.10.0.
Attack scenario
1. The attacker authenticates as any valid user. 2. The attacker obtains a victim flowid (shared URLs, exported flow files, logs, screenshots, etc.). 3. POST /api/v1/build/{victimflowid}/vertices returns 200 OK with {"ids": [...], "runid": "...", "verticestorun": [...]} listing the victim flow's vertex IDs (component type plus suffix, e.g. ChatInput-abc12). 4. The attacker calls POST /api/v1/build/{victimflowid}/vertices/{vertexid} for a returned ID. The victim's graph is loaded and the vertex is built. Its results, outputs and artifacts are returned to the attacker.
Impact
- Confidentiality (Low): Discloses the structure of private flows (vertex IDs and component types) and the outputs of vertices the attacker chooses to build. That includes values hardcoded in node configuration, such as prompt text or fixed inputs. - Global variables and credentials in the variable store are not exposed, because variable resolution uses the caller's userid. - The victim's stored flow definition is not returned wholesale. - Integrity (Low): The attacker can trigger execution of the victim's nodes, including any side effects baked into their configuration (fixed endpoints, webhooks, hardcoded API keys). Build records are written under the victim's flowid. The attacker cannot modify the stored flow. - Availability: None.
The attacker must be authenticated and must know the target flow UUID. Flow UUIDs are random v4 values, so blind enumeration is not practical.
Proof of concept
Observed on langflow==1.9.1 using the project's test client and in-memory SQLite fixtures (see the attached poc.zip):
| Request | Expected (hardened) | Observed on 1.9.1 | |---|---|---| | Attacker → POST /api/v1/build/{victimflowid}/vertices | 404 Not Found | 200 OK with vertex order payload | | Attacker → POST /api/v1/build/{victimflowid}/vertices/<bogus-id> | 404 Not Found | 500 {"detail":"Vertex <id> not found"}: the victim graph was loaded before the vertex lookup failed | | Owner (control) → same endpoints | non-404 | non-404 |
On 1.10.1 and later, the attacker requests return 404 Not Found. This is covered by regression tests in src/backend/tests/unit/testendpoints.py:
- testgetverticesreturns404forotherusersprivateflow - testgetverticeswithsupplieddatareturns404forotherusersprivateflow - testbuildvertexreturns404forotherusersprivateflow
Patches
Fixed in Langflow 1.10.1 (langflow-base 0.10.1) by #13153 (merge commit fb3d6ec90b). Both handlers now:
1. inject currentuser (for retrieveverticesorder, this replaces the dependencies-only declaration), 2. load the flow with an owner-scoped query and return 404 when it is not found, so a flow you don't own looks the same as a missing flow, and 3. call ensureflowpermission(currentuser, FlowAction.EXECUTE, ...) before building or caching any graph.
python retrieveverticesorder / buildvertex (1.10.1) stmt = ( select(Flow) .where(Flow.id == flowid) .where((Flow.userid == currentuser.id) | (Flow.accesstype == AccessTypeEnum.PUBLIC)) ) flow = (await session.exec(stmt)).first() if not flow: raise HTTPException(statuscode=404, detail=f"Flow with id {flowid} not found") await ensureflowpermission( currentuser, FlowAction.EXECUTE, flowid=flowid, flowuserid=flow.userid, workspaceid=flow.workspaceid, folderid=flow.folderid, )
Related hardening in later releases:
- #14342 (1.11.2) applies the same guard to the sibling GET /api/v1/build/{flowid}/{vertexid}/stream route. - #14497 (1.12.0) makes all three deprecated vertex routes owner-only, dropping the PUBLIC exception, because their graph cache is keyed by flow UUID rather than by the executing user.
Users should upgrade to 1.10.1 or later. The latest release is recommended.
Workarounds
If upgrading is not immediately possible, block POST /api/v1/build//vertices and POST /api/v1/build//vertices/ (and GET /api/v1/build///stream) at a reverse proxy. The Langflow UI does not use these routes.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/langflow-baseto a version that resolves this vulnerability.Fixed in 0.10.1 - Upgrade
Upgrade
pip/langflowto a version that resolves this vulnerability.Fixed in 1.10.1 - Upgrade
Upgrade
Langflowto a version that resolves this vulnerability.Fixed in 1.10.1Patch #13153 - Compensating control
At a reverse proxy, block POST requests to /api/v1/build/*/vertices and /api/v1/build/*/vertices/*, and GET requests to /api/v1/build/*/*/stream.
Event History
Frequently Asked Questions
Who can exploit this issue?
Any authenticated user can exploit it if they know or obtain another user's flow ID. No user interaction is required.
What access can an attacker gain through the affected routes?
An attacker can load another user's private flow graph, enumerate its vertex IDs, and execute individual vertices. The results of executed vertices are returned in the HTTP response.
Are the affected endpoints exposed through the Langflow UI or API documentation?
The Langflow UI does not call these routes, and they are excluded from the OpenAPI schema. They remain reachable through direct HTTP requests because they are still registered on the router.
What version contains the fix?
The issue is fixed in Langflow 1.10.1 and langflow-base 0.10.1.