# Langflow has Authenticated Cross-Project File Disclosure via Unscoped MCP Resource Handlers

Langflow's project-scoped MCP resources/read trusts the attacker-supplied flow_id in the file URI, letting any authenticated user read other users' uploaded flow files.

- Payout: Research
- Severity: high
- Program: Langflow
- Found by: R1ZZG0D, andifilhohub
- Disclosed: 2026-10-07
- CVE: CVE-2026-105699
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-4hmc-cfm3-w43c
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-4hmc-cfm3-w43c
- Tags: area:web domain:api domain:web impact:data-leak kind:research platform:ghsa sev:high tech:python technique:source-review vuln:access-control vuln:idor vuln:path-traversal

**Target:** MCP streamable HTTP endpoint /api/v1/mcp/project/{project_id}/streamable and file storage layer
**Root cause:** verify_project_auth_conditional() only authorizes the project_id in the MCP transport URL; handle_read_resource() parses flow_id and filename from the attacker-controlled URI and calls storage_service.get_file() with no ownership or project-membership check.
**Steps:**
1. Create victim and attacker users via `POST /api/v1/users/`, log in via `POST /api/v1/login` to get tokens.
2. Victim creates project and flow, uploads file: `POST /api/v1/files/upload/$VICTIM_FLOW_ID` with `-F "file=@/tmp/secret.txt"`.
3. Build victim URI `$BASE/api/v1/files/download/$VICTIM_FLOW_ID/$VICTIM_FILE`; confirm direct GET as attacker returns 404.
4. Attacker creates API key via `POST /api/v1/api_key/` and connects to own project MCP endpoint `$BASE/api/v1/mcp/project/$ATTACKER_PROJECT_ID/streamable` with `x-api-key`.
5. Call `session.read_resource(victim_uri)` over MCP; server returns base64-encoded victim file contents.
**How found:** Code review of the MCP transport: noticed authorization was enforced at connection time on project_id but the resource URI was forwarded unvalidated to handle_read_resource(), which splits the URI into flow_id/filename and hits a non-authorization-aware storage layer.
**Exploitation:** Attacker needs only an account and any project they own. They open a streamable MCP session to their own project endpoint, initialize, then issue resources/read with a URI pointing at the victim's flow_id and filename. handle_read_resource() forwards the parsed flow_id/filename straight to storage_service.get_file(), bypassing the 404 the normal download route returns. Victim flow IDs and filenames are additionally enumerable via global handle_list_resources()/handle_list_tools() when project_id is None.
**Escalation:** Global MCP helpers (handle_list_resources/handle_list_tools with project_id=None) list all flows and file names across users, turning blind IDOR into easy enumeration of targets.
**Insight:** When a protocol has a connection-scoped auth check (project_id in URL) plus a separate resource-identifier parameter, test whether the second parameter is re-authorized; MCP resources/read, GraphQL node(id:), and REST sub-resource paths are common places where the connection check is mistaken for object-level authz.

