flawopen.com/Teardowns/cve-2026-105699-langflow-mcp-cross-project-file-disclosure
CVE-2026-105699:Langflow MCPのファイル漏えい
LangflowはMCP接続を認証しても、要求されたファイルごとの認可を行っていませんでした。別ユーザーのフローファイルを読める問題は1.9.1で修正されています。[1], [2], [5]
CVSS ベクトル
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ホテルが鍵を確認した後、荷札を伝えるだけでどの部屋の荷物でも渡す状況を想像してください。鍵はホテルに入れる証明であり、他の宿泊客の荷物の所有者である証明ではありません。LangflowのMCP読み取りは、ファイルを開く前に所有者とプロジェクトを確認する必要があります。[1]
主要な概念と専門用語
Model Context Protocol(MCP)- AIアプリケーションがサーバーにツールやリソースを要求するためのプロトコルです。サーバーは要求された各オブジェクトを個別に認可する必要があります。[1]
プロジェクト限定MCPエンドポイント- 特定のプロジェクトに関連付けられたLangflowのMCP接続です。接続できても、すべてのプロジェクトのファイルにアクセスできるわけではありません。[1]
resources/read- URIで指定されたリソースをサーバーに返すよう求めるMCP操作です。[1]
フレームワーク対応
- このガイドで説明する弱点の CWE 参照: CWE-639
根本原因の分析 (Root Cause)
影響を受けるLangflow 1.6.8~1.9.0では、プロジェクト限定MCPトランスポートが接続URL内のプロジェクトIDへのアクセスを認証していました。その後、resources/readハンドラーはリソースURIを受け取り、フローIDとファイル名を解析してstorage_service.get_file()へ渡しましたが、そのフローが認証済みユーザーまたは現在のプロジェクトに属するかを確認していませんでした。ストレージ層は名前空間とファイル名で読み取るだけで、Langflowのユーザーやプロジェクトの権限を知りません。そのため、自分のプロジェクトMCPエンドポイントへアクセスできるアカウントユーザーは、別ユーザーのフロー名前空間にあるファイルを要求できました。別途報告されたグローバルなresources/listとtools/listの挙動は識別子を漏らして標的選択を容易にする可能性がありますが、読み取り自体には不要です。これは認証後に発生するユーザー間の機密性侵害であり、匿名アクセスやコード実行ではありません。CVEレコードはCVSS 4.0 7.1 High、CWE-639としています。Langflow 1.9.1ではhandle_read_resource()が認証済みユーザーを取得し、Flow.user_id == current_user.idでフローを検索します。プロジェクト限定トランスポートではさらにFlow.folder_id == project_idを必須にし、未認可の名前空間をストレージアクセス前に拒否します。パッチはパス区切り文字とtraversal文字列も拒否し、読み取り・書き込み・削除のストレージ経路でパス境界を確認し、グローバル一覧を現在のユーザーに限定します。メンテナーの回帰テストは他ユーザーのフローの読み取りを拒否します。[1], [2], [3], [5]
ステップ・バイ・ステップの攻撃フロー
ユーザーが自分のプロジェクトMCPエンドポイントに認証する
攻撃者にはLangflowアカウントと、少なくとも1つのプロジェクト限定MCP接続へのアクセスが必要です。この問題はログインを回避しません。[1]
クライアントがリソースURIを要求する
MCPのresources/read要求には、末尾のパス要素がフロー名前空間とファイルを示すURIが含まれます。[1]
ストレージが名前空間で読み取る
storage_service.get_file()は指定された名前空間のファイルを返しました。ストレージにはユーザー所有権のコンテキストがありません。[1]
ソースコード比較:脆弱 vs 堅牢化
これは報告された処理経路のPython抜粋であり、単独で動作するアプリケーションではありません。脆弱な処理はクライアントが指定した名前空間を十分な権限として扱っていました。1.9.1の修正では、まず認証済みユーザー、さらにプロジェクトMCP接続では現在のプロジェクトの範囲でフローを解決し、その後にストレージから読み取ります。パッチはファイル名も検証し、ストレージ層でパス境界を強制するため、他の読み取り・書き込み・削除経路から回避できません。ユーザーとプロジェクトを把握するサービス層で認可してください。ストレージアダプターだけでは所有者を判断できません。 抜粋の範囲:実際のハンドラーは、名前空間が認証済みユーザー本人のIDと一致し、サーバーがプロジェクトスコープでない場合に限り、ユーザーバケット内のファイルも許可します。この別分岐は抜粋では省略しており、任意の名前空間へのアクセスを意味しません。上流の回帰テストには、ユーザー間読み取りの拒否を検証する test_handle_read_resource_denies_other_users_flow が含まれます。[1], [3], [4], [7]
ソース抜粋: mcp_utils.py — 対象ソースの抜粋
# 影響を受けた読み取り経路の抜粋: 解析済み ID がそのままストレージに渡されていた。
content = await storage_service.get_file(
flow_id=flow_id,
file_name=filename,
)
# 1.9.1 の認可境界をソースに沿って簡略化した例。
current_user = current_user_ctx.get()
query = select(Flow).where(
Flow.id == namespace_id,
Flow.user_id == current_user.id,
)
if project_id is not None:
query = query.where(Flow.folder_id == project_id)
flow = (await session.exec(query)).first()
if flow is None:
raise ValueError("Resource not found or access denied")
content = await storage_service.get_file(
flow_id=namespace_id,
file_name=filename,
)
エンジニアリング&システム堅牢化チェックリスト
- ✓Langflowを1.9.1以降に更新し、ソースツリーだけでなく実際のパッケージとコンテナイメージを確認します。[5]
- ✓ストレージを呼び出す前に、認証済みユーザーとMCP接続のプロジェクトに基づいて各
resources/readURIを認可します。[1], [3] - ✓識別子をデータベースオブジェクトに解決し、ユーザー所有権とプロジェクト限定時の所属を必須にします。[1], [3]
- ✓クライアントから任意のストレージパスやダウンロードURIを受け取らず、サーバー生成の不透明なリソースIDを優先します。[1], [3]
- ✓デコード後のファイル名を検証し、区切り文字やtraversal要素を拒否して、すべての読み取り・書き込み・削除で正規化パスの包含を確認します。[1], [3]
- ✓
resources/listとtools/listを認証済みユーザーに限定します。識別子列挙は認可不備を拡大し得る別の情報漏えいです。[7] - ✓2ユーザーの回帰テストを追加し、一方のプロジェクトMCPが他方のフローファイルと別プロジェクトのフローを拒否することを確認します。[1]
- ✓影響を受ける環境のアクセスログを確認して露出を評価します。アドバイザリは脆弱性を示しますが、実環境での悪用を確認したものではありません。[7], [3]
- ✓上流の回帰テスト
test_handle_read_resource_denies_other_users_flowを維持し、現在のプロジェクト外にある本人所有フローも拒否されることを検証するプロジェクトスコープのケースを追加します。[7], [3]
参考資料
- Langflow maintainer advisory GHSA-4hmc-cfm3-w43c
- CVE Program record CVE-2026-105699
- Upstream fix commit b8fe970
- Upstream pull request #12818 and regression coverage
- Langflow 1.9.1 release
- MITRE CWE-639 — Authorization Bypass Through User-Controlled Key
- Langflow regression tests — MCP resource authorization