Skip to content
LangflowGHSA-9c59-2mvc-vfr8

Langflow: IDOR/BOLA in Monitor API , Missing Ownership Enforcement on 7 Endpoints

High8.8CVE-2026-33760 · Published Jun 16, 2026 · updated Jul 20, 2026

### Summary Langflow's `/api/v1/monitor` router exposes 7 endpoints that perform read, write, and delete operations on user-owned resources , messages, sessions, build artifacts, and LLM transaction logs , without verifying that the authenticated requester owns the targeted resource. Any authenticated user can read, modify, rename, or permanently delete another user's data by supplying the target's resource ID or `flow_id`. This is a classic IDOR/BOLA vulnerability. Notably, the same source file (`monitor.py`) contains one correctly-implemented endpoint that uses an ownership check, demonstrating the correct pattern was known but inconsistently applied. ### Details **Source file: `src/backend/base/langflow/api/v1/monitor.py`** The correct pattern (used only in `GET /monitor/messages`, lines 77–80): ```python stmt = select(MessageTable) stmt = stmt.join(Flow, MessageTable.flow_id == Flow.id) stmt = stmt.where(Flow.user_id == current_user.id) # ownership enforced ``` All 7 vulnerable endpoints are missing this guard: **1. `GET /api/v1/monitor/builds` (lines 27–33)** , reads build data for any `flow_id`: ```python @router.get("/builds", dependencies=[Depends(get_current_acti...

GitHub advisory

Affected versions

PackageAffectedFixed in
langflow
PyPI
< 1.9.01.9.0
Details and references

### Summary Langflow's `/api/v1/monitor` router exposes 7 endpoints that perform read, write, and delete operations on user-owned resources , messages, sessions, build artifacts, and LLM transaction logs , without verifying that the authenticated requester owns the targeted resource. Any authenticated user can read, modify, rename, or permanently delete another user's data by supplying the target's resource ID or `flow_id`. This is a classic IDOR/BOLA vulnerability. Notably, the same source file (`monitor.py`) contains one correctly-implemented endpoint that uses an ownership check, demonstrating the correct pattern was known but inconsistently applied. ### Details **Source file: `src/backend/base/langflow/api/v1/monitor.py`** The correct pattern (used only in `GET /monitor/messages`, lines 77–80): ```python stmt = select(MessageTable) stmt = stmt.join(Flow, MessageTable.flow_id == Flow.id) stmt = stmt.where(Flow.user_id == current_user.id) # ownership enforced ``` All 7 vulnerable endpoints are missing this guard: **1. `GET /api/v1/monitor/builds` (lines 27–33)** , reads build data for any `flow_id`: ```python @router.get("/builds", dependencies=[Depends(get_current_active_user)]) async def get_vertex_builds(flow_id: Annotated[UUID, Query()], session: DbSession): vertex_builds = await get_vertex_builds_by_flow_id(session, flow_id) # no ownership check return VertexBuildMapModel.from_list_of_dicts(vertex_builds) ``` **2. `DELETE /api/v1/monitor/messages` (lines 102–107)** , deletes any message by UUID: ```python @router.delete("/messages", status_code=204, dependencies=[Depends(get_current_active_user)]) async def delete_messages(message_ids: list[UUID], session: DbSession): await session.exec(delete(MessageTable).where(MessageTable.id.in_(message_ids))) # message_ids accepted verbatim, no ownership check ``` **3. `PUT /api/v1/monitor/messages/{message_id}` (lines 110–134)** , overwrites any message: ```python db_message = await session.get(MessageTable, message_id) # no check: db_message.flow_id → Flow.user_id == current_user.id db_message.sqlmodel_update(message_dict) ``` **4. `PATCH /api/v1/monitor/messages/session/{old_session_id}` (lines 137–171)** , renames any session: ```python stmt = select(MessageTable).where(MessageTable.session_id == old_session_id) # no JOIN to Flow, no WHERE Flow.user_id == current_user.id ``` **5. `DELETE /api/v1/monitor/messages/session/{session_id}` (lines 174–188)** , bulk-deletes any session: ```python await session.exec( delete(MessageTable).where(col(MessageTable.session_id) == session_id) # no ownership filter ) ``` **6. `GET /api/v1/monitor/transactions` (lines 191–211)** , reads LLM prompt/response logs for any `flow_id`: ```python stmt = select(TransactionTable).where(TransactionTable.flow_id == flow_id) # no JOIN to Flow, no WHERE Flow.user_id == current_user.id ``` **7. `DELETE /api/v1/monitor/builds`** , deletes build records for any `flow_id`: Shares the same root cause as endpoint #1 (`GET /builds`): `flow_id` is accepted as a bare query parameter and passed to the deletion path without a `WHERE Flow.user_id == current_user.id` ownership check, so any authenticated user can destroy another user's build artifacts. ### PoC Tested on Langflow v1.7.3 (`langflowai/langflow:1.7.3`) with two accounts: `langflow` (victim) and `attacker_test` (attacker). ```bash # Setup: authenticate both users TOKEN=$(curl -s -X POST http://localhost:7860/api/v1/login \ -d "username=langflow&password=langflow" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") ATTKR=$(curl -s -X POST http://localhost:7860/api/v1/login \ -d "username=attacker_test&password=Attacker123" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") # Victim creates a flow (attacker only needs to know the flow_id , obtainable via brute force or enumeration) FLOW_ID=$(curl -s -X POST http://localhost:7860/api/v1/flows/ \

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Severity from
GitHub (reviewed advisory)
Weakness
CWE-639
Also known as
CVE-2026-33760, PYSEC-2026-242

More Langflow advisories

All Langflow

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.