Letting users trust their own MCP clients, without opening a phishing hole
Letting users trust their own MCP clients, without opening a phishing hole (streamvessel.com)
要約
この記事は、MCP(Model Context Protocol)クライアントの認証におけるセキュリティリスクと、Stream Vesselが実装した新しいアプローチについて解説しています。従来のOAuth認証では、クライアント登録時のリダイレクトURIの検証が不十分なため、フィッシング攻撃やアカウント乗っ取りのリスクがありましたが、Stream Vesselでは、ユーザーがサインイン時に個別のリダイレクトURIを信頼するかどうかを決定する仕組みを導入しました。これにより、オペレーターの許可リストに加え、ユーザーごとの信頼リストがセキュリティを強化します。
全文翻訳
Letting users trust their own MCP clients, without opening a phishing hole
2026年11月4日 · Alexander Loginov · 10 分で読める
問題
独自のエージェント(Slackボット、VPS上のサービス、週末に書いたMCPクライアントなど)を実行しているとします。そのエージェントに、Stream Vesselでは独自のS3バケットにあるファイルへのアクセスを許可したいと考えます。エージェントはMCPサーバー経由で読み書き削除を行います。エージェントはMCP標準のOAuthサインインを使用します。つまり、自身を登録し、ブラウザを開き、ユーザーが「承認」をクリックするとトークンが得られます。
しかし、最初のステップで失敗します。登録時にエージェントは、承認後にブラウザが戻ってくるアドレスであり、エージェントがトークンと交換する一時的な「認可コード」を運ぶ「リダイレクトURI」を指定します。あなたのURIが https://agent.yourhost.example/callback だとしても、サーバーからの応答は invalid_redirect_uri です。サーバーはオペレーターの「許可リスト」にあるアドレスしか受け付けません。リストへの追加には、オペレーターに設定ファイルを変更してもらう必要があります。
サーバーは任意の Садрес を受け入れるわけにはいきません。そのリストは、郵便局が家の鍵を届ける Садрес のようなものです。それがなければ、見知らぬ人が偽の「Claude」を登録し、鍵を自分の Садрес に送らせ、リンク一つであなたのストレージをフィッシングする可能性があります。したがって、サーバーはあなたに対して閉鎖的であるか、すべての人にとって安全でないかのどちらかです。
どちらの失敗も一般的です。閉鎖的な側では、人々は次のような状況に陥ります。
「リダイレクトURIが許可されていません。このURLは、登録済みMCPクライアントに対するAtlassianのホワイトリストドメインの一部ではありません。」— ユーザー(Cursorユーザーを含む)がAtlassian開発者コミュニティで報告したエラー。
「…各ホストアプリケーションは、ユーザーの代わりに認証を行うために、登録済みのGitHub App(またはOAuth App)を持っている必要があります。」— GitHub MCPサーバー、ポリシーとガバナンス;それ以外の場合は、パーソナルアクセストークンにフォールバックします。
開放的な側では、2026年5月の調査で、119のOAuth対応MCPサーバーをテストした結果、95.8%にあたる114のサーバーで登録エンドポイントが
「匿名の要求者から任意のredirect_uri値を受け入れる」
ことがわかりました。
攻撃:リンク一つ、クリック一つで、他人のバケットを乗っ取る
サーバーが任意の登録と任意の Сリダイレクトアドレスを受け入れると仮定します。そこにアカウントを持たない攻撃者は、4つのステップでユーザーのアクセスを乗っ取ることができます。
- リダイレクトURIを
https://evil.example/cbとする「Claude」という名前のクライアントを登録します。オープン登録は、設計上、匿名です。 - サインインしているユーザーに、「ストレージをClaudeに再接続してください」という、通常の見た目の
/oauth/authorizeリンクを送信します。 - ユーザーは、実際のセッションで、実際のドメイン上の、あなたの実際の同意画面を見て、「承認」をクリックします。
- コードが
evil.exampleに届きます。攻撃者はそれをトークンと交換し、ユーザーになります。
PKCEは役に立ちません。フローを開始したクライアントだけがそれを完了できます。クライアント名は自己申告であり、何も証明しません。同意画面はリダイレクトホストを表示しますが、claude-connect.app は一見すると問題なさそうです。これは、Microsoft 365ユーザーに送信される偽アプリ承認プロンプトと同じトリックです。実際のページと実際の「承認」ですが、ここではプラットフォームが登録したアプリを使用し、ここではサーバーが決して受け入れるべきではなかったアドレスを使用します。
ユーザーは承認しますが、誤った情報に基づいています。リダイレクト許可リストは、承認がユーザーが意図した意味を持つようにするためのものです。名前が何であれ、鍵はそのリストが示す Садрес にしか Садрес できません。
これは仮説ではありません。2026年1月にObsidian Securityは、SquareのMCPサーバーに対してこの連鎖を報告しました。自己登録は任意の Сリダイレクトアドレスを受け入れ、ワンクリックでマーチャントのプロフィール、トランザクション、銀行詳細が漏洩しました。研究者たちの言葉を借りれば、「以前にSquare MCPサーバーを承認したことのあるユーザーは誰でも脆弱でした」。Squareの修正は、新規登録クライアントにのみ適用されると彼らは指摘しています。
他の人も、会社のシングルサインオンの前に置かれたMCPプロキシで同じ連鎖を発見しました。これはクリックごとに24時間のアクセストークンと14日間のリフレッシュトークンに相当し、バグバウンティプログラムでも見つかりました。そこでは、ソーシャルエンジニアリングとして片付けられることもありました。SickSecの回答:被害者は信頼されたドメインから離れることはありません。
より静かな問題も伴います。リダイレクトアドレスが受け入れられた後にチェックに失敗した場合、エラーはそのアドレスに送信されます。したがって、任意の Садрес を受け入れるサーバーは、それ自体のドメイン上でオープンリダイレクターになります。悪意のある認可リンクは、あなたのホスト名の下で訪問者を任意のサイトにリダイレクトします。RFC 9700はまさにこのケースを説明しています。
仕様が述べていること、そしてサーバーが実際に行っていること
MCP仕様はリスクを明記していますが、ポリシーは各サーバーに委ねられています。そのセキュリティ上の考慮事項では、認可サーバーは正確なリダイレクトURIを検証しなければならず(MUST)、信頼するURIにのみ自動的にリダイレクトすべきであり(SHOULD)、あるいはユーザーに通知して決定を委ねることもできる(MAY)と述べています。最後の2つはRFC 9700の文章をそのまま引用したものです。「信頼」が何を意味するかは未定義です。
登録も変化しています。2026-07-28 クライアント登録ページでは、動的クライアント登録(人間が関与せずAPI経由でクライアントが自身を登録する)を非推奨とし、クライアントIDメタデータドキュメント(CIMD)を推奨しています。CIMDでは、クライアントIDは、リダイレクトURIのリストを含むドキュメントへのHTTPS URLとなります。CIMDはクライアントを誰が公開したかを示しますが、このユーザーがそれを接続することを意図したかどうかは示しません。
OAuthワーキンググループもこの問題に直面してきました。2025年12月のIETF OAuthメーリングリストのスレッドで、Warren Paradは、認証されていない登録はMCP認可サーバーをオープンリダイレクターに変えてしまうと主張しました。Aaron Pareckiの引用された返信は、まさにその設定から始まるRFC 9700 §4.11.2を指しています。
実際には、サーバーは2つの極端のどちらかに位置しています。
| アプローチ | 例 | 接続できるのは誰か | フィッシングへの露出 |
|---|---|---|---|
| オペレーターが設定した固定許可リスト | pgEdge Postgres MCPサーバー:自己登録はオンですが、登録されたURIはリスト上にある必要があります(デフォルトでClaudeのコールバックと2つのループバックURI)。 | リストに記載されたクライアントのみ;それ以外は設定変更が必要 | 低い |
| クライアントが登録したものすべて | 2026年5月の調査でテストされた119サーバーのうち114サーバー | 任意のクライアント | 高い:上記の攻撃 |
ループバックとは localhost または 127.0.0.1 を意味します。これはユーザー自身のマシン上のアプリが、ブラウザが戻ってくるのを待っている状態です。
最初の極端なアプローチは、実際のユーザーにコストをかけます。クライアントはあなたのためにそれを修正しません。Claude Codeのissue(2026年6月)は、正確な一致リダイレクト許可リストを持つ組織がClaude Codeからサインインできないことを報告しています。なぜなら、その http://localhost:<port>/callback のホストとパスは固定されているからです。これは2026年7月に「計画なし」としてクローズされました。サーバーは移動する必要があります。
これは新しいアイデアではない
ユーザーが自分のアプリのコールバックを証明できるようにすることは古い考え方です。GitHubでは、開発者がOAuthアプリを作成し、そのコールバックURLを入力します。MCP仕様の事前登録オプションでは、ユーザーが手動でクライアントを登録するサーバーページについて説明しています。また、組織は信頼できるリダイレクトドメインをAtlassianのMCPサーバーに追加できます。これらのいずれも、匿名で自身を登録するクライアントには適合しません。最初の2つは別の場所で作成されたクライアントIDを必要とし、Atlassianは組織全体のために決定します。そのため、私たちは自己登録を維持し、各ユーザーがサインイン時に、サーバー全体リストの横で、自分のアカウントのために決定できるようにしました。
私たちの設計:信頼はユーザーごと、サインイン時に決定
オペレーターの許可リストとローカルクライアント用のループバックは、変更せず、すべてのアカウントに公開したままにします。その上に、各ユーザーは自分自身のアカウントのために、少数の正確なHTTPSリダイレクトURIを信頼できます。私たちのシステムでは最大10個までです。Stream Vesselでは、これはWebアプリのMCPページにある「信頼されたアプリのアドレス」です。
[画像: Stream VesselのMCPページにある「信頼されたアプリのアドレス」カード。2つの信頼されたアドレスが表示され、追加フィールドがある]
リストはMCPページにあり、ユーザー自身のブラウザセッションのみが変更できます。
重要なのは、チェックが「いつ」実行されるかです。登録は誰かがサインインする前に行われるため、どのユーザーのリストを参照すべきかを知ることはできません。そのため、登録は任意のHTTPS URIを受け入れ、決定は認可ページまで待たれます。そこではユーザーが認識されます。
[画像: 認可の決定。許可リストまたはループバックの場合:同意、その後コードがアプリに送信される。それ以外の場合、サインアウト済み:サインイン、その後リクエストが繰り返される。サインイン済みで、このユーザー自身のリストにある場合:同意、あなたによって信頼済みとマークされる。リストにない場合:私たちのホスト上の拒否ページ。ここでユーザーはホストを入力して信頼し、再試行できる]
コードは、すべてのアカウントまたはこのユーザーが信頼しているアドレスにのみ到達します。
すべての分岐の下には1つのルールがあります。コードもエラーも、すべてのアカウントまたはこのユーザーが信頼しているアドレス以外にはリダイレクトされません。サインイン前にはどのユーザーのリストも適用されないため、OAuthライブラリ自体の検証エラー(不正なPKCEチャレンジ、不明なスコープ)でさえサインインを待ち、拒否は私たちのホスト上のページとなります。これにより、フィッシングの穴とともにオープンリダイレクトの穴も閉じられ、拒否ボタンもカバーされます。n8nのCVE-2026-42230(2026年4月)は、同意を拒否したユーザーを攻撃者の登録済みアドレスに直接リダイレクトしました。
[画像: 拒否ページ:アプリのホストと完全なアドレスがテキストで表示され、サインインしたアカウント、警告、そして信頼を有効にして続行するためにホストが入力される]
拒否ページ。何もアプリには送信されませんでした。信頼するにはホストを入力する必要があります。
7つの回避策、すべて閉鎖
これらのルールのほとんどは、最初のバージョンを破ろうとした試みから生まれました。
- ユーザー自身のブラウザセッションのみがリストを変更できます。アプリやAPIトークンは変更できないため、盗まれたトークンがユーザーのコードが行ける場所を広げることはありません。
- 拒否ページからの追加はホストの入力が必要です。ページにはホスト、完全なアドレスがプレーンテキスト(リンクではない)で表示され、どの Садрес に追加されるか、そして警告が表示されます。「このリンクをフィッシングされた場合、ページを閉じてください。」「信頼して続行」はホストが入力されるまで無効なままなので、フィッシングメールからのワンクリックでは不十分です。
- 同意画面には、このサインインのリダイレクト先と、許可された理由が表示されます。クライアントは一度に複数のアドレスを登録できます。たとえば、独自の Садрес の横に
claude.aiをリストできます。画面には、このサインインで戻ってくる Садрес とその根拠のみが表示されます。根拠は「許可リスト上」「ループバック」「あなた自身が2026年11月3日にこのアドレスを信頼しました」のいずれかです。仕様ではメタデータドキュメントクライアントのホストを表示することが要求されていますが、2026年5月の調査ではテストされたサーバーの60.5%がそうしていませんでした。根拠こそが意味を持たせるものです。 - すべての追加はメールで通知され、アクティビティログがオンの間は監査されます。メールはホストから始まり、いつ、どの Садрес に追加されたかを伝えます。完全なアドレスがリンクになるのを防ぎます。これは、フィッシングされたユーザーが決して得られないであろうシグナルです。
- アドレスの削除は、サインイン中でもアプリを切り離します。そのアドレスを通じて行われたすべての許可(ユーザーが与えた承認)を取り消し、承認されたがまだ交換されていないコードを破棄します。コードの有効期間は約1分です。これがないと、飛行中のコードは削除後に許可となります。
- ブラウザとサーバーで異なる読み方をするホストは拒否されます。URIパーサーは
good.example%2eevil.exampleを1つのリテラルホストとして保持するかもしれませんが、ブラウザはgood.example.evil.exampleに移動します。1つのホスト名を指定して別のホストにユーザーを送信するページは、入力ホストチェックを回避するため、登録時およびリストに追加する際に拒否されます。 - 正確な一致のみ。ワイルドカード、プレフィックスマッチ、ループバックスタイルのポートの柔軟性はありません。
[画像: 信頼されたアドレスの同意画面:このサインインで戻るホスト名、ユーザーが2026年11月3日に自分で信頼した理由が表示され、デフォルトで読み取り専用が選択されている]
信頼されたアドレス経由の同意:このサインインのホスト、許可された理由、デフォルトで読み取り専用が選択されている。
停止しないもの、そして私たちが却下したもの
決意のあるソーシャルエンジニアを止めることはできません。攻撃者のホストを入力して承認するように説得されたユーザーは、どのティアを選択してもフィッシングされます。私たちはそれを防ぐのではなく、制約を設けました。入力されたホスト、警告、メール、削除時の即時切断、そして読み取り専用から始まり、ユーザーがアドレスを信頼したことを示す同意画面です。
当初、ユーザーが信頼したアドレスを書き込みアクセスのみに制限しましたが、フィッシングされた許可がデータを変更できても削除できないようにしました。キャップを外しました。独自の Садрес を接続するユーザーは完全なアクセスを求めており、キャップはバックストップであって防御ではありませんでした。
上記の2つの極端以外に、私たちは以下を検討しました。
| オプション | なぜ単独ではダメなのか |
|---|---|
| CIMDドメインバインディング(リダイレクトホストはクライアントIDのホストと一致する必要がある) | クライアントを誰が公開したかは証明するが、このユーザーが接続することを意図したかは証明しない;悪意のあるドメインでも要求できる |
| 「未検証クライアント」ティア(より低い上限付き) | フィッシングリンクからのワンクリックであることに変わりはないが、影響範囲は小さい |
| ユーザーごとのリスト(出荷済み) | 追加するにはユーザー自身のセッションが必要;アプリごとに一度ホストを入力するコストがかかる |
独自のMCPサーバーのためのチェックリスト
- サインインしたユーザーが認識されている状態で、登録時ではなく、認可時にコードがどこへ行けるかを決定します。
- リダイレクトする前に、エラーを含め、すべての応答をそのルールに対してチェックし、自身のホスト上のページに拒否します。トークン交換時だけでなく、認可時に行います。Cloudflareの
workers-oauth-providerは、後者のみをチェックしたために、2025年5月にCVE-2025-4143をリリースしました。 - 同意画面に、このリクエストのリダイレクトホストを表示し、許可された理由を伝えます。
- リストを広げることを、サインインフローの外、または入力確認の後で行われるようにします。
- アドレスが追加されるたびに、ユーザーにメールで通知します。
- 削除が許可とインフライトコードを取り消すようにします。
- ブラウザとサーバーで読み方が異なるホストのURIを拒否します。
試してみたいですか?独自の Садрес で、Stream VesselのMCPサーバー https://mcp.streamvessel.com を追加してサインインしてください。あなたの Садрес がリストにない場合、上記の拒否ページに誘導され、そこでアドレスを信頼して続行できます。
ソース
- RFC 9700, Best Current Practice for OAuth 2.0 Security, §4.11.2
- MCP authorization security considerations (draft)
- MCP client registration, 2026-07-28
- Zhou et al., A First Measurement Study on Authentication Security in Real-World Remote MCP Servers, May 2026
- Obsidian Security: When MCP Meets OAuth, Jan 2026
- Full account takeover on an MCP OAuth proxy (hackt.us)
- SickSec: Hunting account takeovers in MCP OAuth servers, Jan 2026
- n8n GHSA-f6x8-65q6-j9m9 / CVE-2026-42230
- GHSA-4pc9-x2fx-p7vj / CVE-2025-4143, workers-oauth-provider
- GitHub MCP server: policies and governance
- GitHub: Creating an OAuth app
- Atlassian: available MCP server domains
- Atlassian developer community: redirect URI allowlisting for the remote MCP server
- IETF OAuth WG: MCP DCR redirect URLs as open redirectors, Dec 2025
- pgEdge Postgres MCP server: OAuth
- Claude Code issue #66511: configurable OAuth redirect URI