Channelsセキュリティ設計 — 受信メッセージのゲートと組織制御(Claude Code)
Claude Code Channelsはゲートしないとプロンプトインジェクションの入口になります。送信者検証の実装パターンと、組織ポリシーによる二重の防御をまとめました。
Channelsの受信メッセージはなぜゲートが要るか
Channelsは、外部のイベントをClaudeのコンテキストへそのまま流し込む仕組みです。裏を返せば、そのエンドポイントに届くテキストを誰かが自由に送れるなら、Claudeへの指示を書けてしまうということです。ゲートしていないチャネルはプロンプトインジェクションの経路になります。
一方向のアラート転送でも二方向のチャットブリッジでも事情は同じです。チャットプラットフォームや公開エンドポイントを監視するチャネルは、mcp.notification()を呼ぶ前に実効性のある送信者チェックを必ず持つ必要があります。ここでいう実効性のあるチェックとは、次の節で説明する送信者IDでの照合を指し、部屋やチャットのIDで代用したものは含みません。
送信者とルームの取り違えという典型的な穴
送信者検証を実装するとき、最も起きやすい間違いは送信者のIDではなくルーム(チャット)のIDでゲートしてしまうことです。
const allowed = new Set(loadAllowlist()) // access.json等から読み込む
// メッセージハンドラー内、notification発行の直前に置く
if (!allowed.has(message.from.id)) { // 送信者のID。チャットのIDではない
return // 黙って捨てる
}
await mcp.notification({ /* ... */ })1対1のDMではmessage.from.idとmessage.chat.idはほぼ同じ値になるため、この違いは見えにくいものです。しかしグループチャットでは両者が別物になります。ルームのIDで許可リストを組んでしまうと、許可済みグループに参加している全員がセッションへメッセージを注入できるようになります。許可したつもりの相手は1人でも、実際に通ってしまうのはグループの人数分です。
allowed.has(id)のチェック自体は1行ですが、そのidがどちらの識別子を指しているかを取り違えると、ゲート自体が意味を持たなくなります。実装時は必ず送信者個人のIDを使っているかをテストで確認します。
公式チャネルは送信者確認をどう実装しているか
3つの公式プラグインは、それぞれ違う方法で送信者確認の初期状態を作っています。
| チャネル | ゲートの仕組み | 初期登録の方法 |
|---|---|---|
| Telegram | ゲートの仕組み送信者IDの許可リスト | 初期登録の方法ボットに任意のメッセージを送るとペアリングコードが返り、Claude Code側で承認すると許可リストに追加される |
| Discord | ゲートの仕組み送信者IDの許可リスト | 初期登録の方法Telegramと同じペアリングフロー(ボットにDMを送ってコードを承認) |
| iMessage | ゲートの仕組み自分のApple IDアドレスは自動許可、他者は手動追加 | 初期登録の方法起動時にMessagesデータベースから自分のアドレスを検出して素通しする。他の連絡先は/imessage:access allowで手動追加 |
TelegramとDiscordは「まず自分だけがボットと話せる状態を作り、そこから信頼できる相手を1人ずつ足す」という同じ設計です。iMessageは自分宛のセルフチャットが最初から素通しになる点が異なり、これは「自分のApple IDから自分のMacへのメッセージ」という経路自体が既に閉じているという前提に立っています。
自作チャネルを設計するときも、この2パターンのどちらかに寄せるのが安全です。ペアリングコード方式は汎用性が高く、セルフチャット検出方式はプラットフォームが送信者の身元をOSレベルで保証できる場合にだけ使えます。
セッションのオプトインと組織ポリシーの二重の壁
送信者ゲートはメッセージレベルの防御ですが、その手前にもう2段階の壁があります。
| 防御層 | 何を防ぐか | 設定場所 |
|---|---|---|
--channelsフラグ | 何を防ぐか.mcp.jsonにサーバーを書いただけでは何も届かない。セッションごとに明示的に有効化しないとメッセージは配信されない | 設定場所セッション起動コマンド |
channelsEnabled | 何を防ぐかTeam/Enterprise組織では既定でChannels自体がブロックされる。Ownerロールの管理者が有効化するまで、どのユーザーも--channelsを使えない | 設定場所claude.ai管理設定、または管理対象設定ファイル |
allowedChannelPlugins | 何を防ぐかAnthropicの承認済みプラグイン一覧を、組織独自の許可リストに置き換える。社内マーケットプレイス経由のプラグインだけを許可する、あるいは特定のプラグインだけ禁止するといった運用ができる | 設定場所管理対象設定ファイル(channelsEnabled: trueが前提) |
| 送信者の許可リスト | 何を防ぐか上記3つを通過したあとの、メッセージ単位の最終ゲート。ここまでの層は「誰が使えるか」で、この層だけが「何が届くか」を決める | 設定場所チャネルサーバーの実装(前節参照) |
.mcp.jsonにサーバーを書いてあることと、実際にメッセージが届くことはイコールではありません。--channelsで名指ししていないサーバーは接続してツールも動きますが、チャネルとしてのイベントだけが届きません。組織の設定が空の配列(allowedChannelPlugins: [])であっても注意が必要です。--dangerously-load-development-channelsはこのブロックを迂回できます。開発フラグごと完全に塞ぎたい場合は、allowedChannelPluginsを空にするのではなくchannelsEnabled自体を未設定のままにします。
Pro・Maxプランで組織に属さないユーザーは、この2段階の組織ポリシーチェックを最初から素通りし、セッションごとのオプトイン(--channels)だけが残ります。
既定値は認証方式で変わる点にも注意が要ります。claude.ai経由のTeam・Enterprise組織は、Ownerが明示的に有効化するまでChannelsそのものがブロックされた状態が既定です。一方、Console APIキーで認証している組織は既定でChannelsが許可されており、channelsEnabledを明示する必要があるのは管理対象設定を配布している場合だけです。同じ「組織アカウント」でも、認証経路によって初期状態が逆になっている点は見落としやすいポイントです。
ゲート設計でつまずきやすい3つの点
開発フラグは組織ポリシーまでは迂回しない
--dangerously-load-development-channelsが迂回するのは承認済みプラグインの許可リストだけです。channelsEnabledが未設定・falseの組織ポリシーは、このフラグを付けても引き続き有効なので、「blocked by org policy」と出た場合は管理者側の設定を疑います。
MCP_PROTOCOL_NEGOTIATION=autoでチャネルが登録されないことがある
v2のMCPクライアントランタイムでこの環境変数をautoに設定していると、プロトコルリビジョン2026-07-28をネゴシエートするチャネルサーバーをClaude Codeは登録しません。カスタムチャネルが--channelsに渡しても反応しない場合、まずこの環境変数の設定を確認します。
送信者ゲートを実装したつもりで、実は.envのトークン漏洩がゲートより手前の穴になっている
TelegramやDiscordのボットトークンは~/.claude/channels/<plugin>/.envに保存されます。送信者の許可リストがどれだけ厳格でも、このトークン自体が流出すればボットの制御を丸ごと奪われます。ゲートの実装だけでなく、トークンの保管場所へのアクセス権も同じ脅威モデルで扱います。
channelsEnabledが無効・未設定の組織でユーザーがチャネルサーバーを.mcp.jsonに追加した場合、サーバー自体は接続しツールも普段どおり動きますが、チャネルとしてのメッセージだけが届きません。セッション側には「blocked by org policy」という文字列が表示されますが、チャネルサーバー側にはエラーが返らず、イベントは黙って捨てられます。配送が実際に成功したかをサーバー側で確認したい場合は、通知が届かないことを前提にサーバー側で状態を持ち、reply toolでその状態をセッションに報告させる設計が必要です。
自作チャネルを組織内に配布するときの経路
送信者ゲートを実装したチャネルを自分だけでなくチームに配りたい場合、プラグイン化してマーケットプレイスに公開する経路があります。ただし自社マーケットプレイスに公開しただけでは研究プレビューの承認済みリストには載りません。承認済みリストはAnthropicが研究プレビュー中に管理しているもので、社内マーケットプレイスへの公開とは別の配布経路です。
Team・Enterprise組織であれば、管理者がallowedChannelPluginsにその内製プラグインを明示的に加えることで、開発フラグなしで組織内のメンバーに使わせられます。送信者ゲートの実装と、配布経路をどちらの許可リストに載せるかは別の意思決定であり、どちらか一方だけを固めても片手落ちになります。
逆にこの登録をせず個人の--dangerously-load-development-channelsだけで運用し続けると、起動のたびに全画面の警告ダイアログを確認する手間が要ります。フラグの性質上「信頼できないソースには使わない」という前提も毎回思い出す必要があります。組織単位で「使っていいものを配る」という設計自体は同じ考え方です。すでにMCPサーバーの許可リストを運用しているチームなら、allowedChannelPluginsも同じ運用ルールに乗せやすいはずです。Managed MCPの許可リスト・拒否リストも参照してください。
権限中継を足すかどうかの判断材料
送信者の許可リストが効いているチャネルは、reply toolによる返信まではそのまま安全に拡張できます。しかし権限確認プロンプトの中継を追加するかどうかは、もう一段別の判断が要ります。
権限中継の許可リストは、送信者ゲートと同じリストを使う設計です。つまり「チャットに返信できる相手」と「Bashやファイル編集を承認できる相手」が同一集合になります。メッセージのゲートだけを見て「この相手はチャットに書き込めるだけ」と考えていると、実は同じ相手にツール実行の可否まで委ねていた、という食い違いが起こり得ます。権限capabilityを宣言する前に、許可リストの中身が「返信を任せてよい相手」ではなく「セッションの操作を任せてよい相手」の基準で作られているかを見直す価値があります。
まとめ
Channelsの受信メッセージは3層で守られています。送信者IDでのゲート、セッションのオプトイン(--channels)、そして組織のchannelsEnabled/allowedChannelPluginsです。自作チャネルを書くときに書き手が責任を持つのは送信者ゲートの1層だけですが、そこをchat.idとfrom.idの取り違えで壊すと、上の2層がどれだけ厳格でも無意味になります。TelegramやDiscordのペアリングフローはこの1層を安全に立ち上げるための実例として参考になります。権限中継まで機能を伸ばす前には、許可リストの粒度が「会話」ではなく「セッションの操作権限」に見合っているかを確認します。
よくある質問
個人利用(Pro/Maxプラン)でも組織ポリシーの設定は必要ですか
不要です。組織に属さないPro・MaxユーザーはchannelsEnabledやallowedChannelPluginsのチェックを経由しません。セッションごとのオプトイン(--channels)だけでChannelsを使えます。
送信者の許可リストが空でもチャネルは起動しますか
起動します。MCPサーバー自体は許可リストの状態に関わらず接続し、ツールも通常どおり動きます。許可リストが空の場合に変わるのは「誰からのメッセージもmcp.notification()まで到達しない」という点だけです。
allowedChannelPluginsを設定すればAnthropicの承認済みリストは完全に無視されますか
はい。allowedChannelPluginsを設定すると、それが既定のAnthropic許可リストを丸ごと置き換えます。設定後に許可したいプラグインは、Anthropic公式のものであっても明示的にリストへ書き直す必要があります。
グループチャットの全員を許可したい場合はどう設計すべきですか
message.from.idを1人ずつ許可リストに追加する運用のままでは全員分の登録作業が要ります。グループ全体を信頼する設計にするなら、送信者ゲートを緩める代わりに権限中継のcapability自体を宣言しない、というように機能ごとに信頼範囲を分けるのが安全です。
自社マーケットプレイスに公開すれば承認済みリストに自動で載りますか
載りません。承認済みリストはAnthropicが研究プレビュー中に管理しているもので、自社マーケットプレイスへの公開はそれとは別の配布経路です。組織内のメンバーに開発フラグなしで使わせたい場合は、Team・Enterprise組織の管理者がallowedChannelPluginsにそのプラグインを明示的に追加する必要があります。