Channelsセキュリティ設計 — 受信メッセージのゲートと組織制御(Claude Code)
Claude Code Channelsはゲートしないとプロンプトインジェクションの入口になります。送信者検証の実装パターンと、組織ポリシーによる二重の防御をまとめました。
Channelsの受信メッセージはなぜゲートが要るか
Channelsは、外部のイベントをClaudeのコンテキストへそのまま流し込む仕組みです。仕組み自体の解説はClaude Code Channelsとはにまとめました。裏を返せば、そのエンドポイントに届くテキストを誰かが自由に送れるなら、Claudeへの指示を書けてしまうということです。ゲートしていないチャネルはプロンプトインジェクションの経路になります。
一方向のアラート転送でも二方向のチャットブリッジでも事情は同じです。チャットプラットフォームや公開エンドポイントを監視するチャネルは、mcp.notification()を呼ぶ前に実効性のある送信者チェックを必ず持つ必要があります。ここでいう実効性のあるチェックとは、次の節で説明する送信者IDでの照合を指し、部屋やチャットのIDで代用したものは含みません。
通知の送り側には、成否が返ってこないという性質もあります。await mcp.notification()が終わるのは、メッセージをトランスポートへ書き込んだ時点です。Claudeが処理した時点ではありません。セッションがそのサーバーをチャネルとして読み込んでいない場合や、組織ポリシーで止められている場合は、イベントが黙って捨てられ、サーバーへエラーも返りません。
送信者とルームの取り違えという典型的な穴
送信者検証を実装するとき、最も起きやすい間違いは送信者の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つの公式プラグインは、2通りの方法で送信者確認の初期状態を作っています。各アプリの個別セットアップ手順はClaude Code Channels対応アプリと研究プレビューの制約にまとめました。
許可リストの作り方は2系統
Telegram・Discord
ボットに任意のメッセージ(DiscordはDM)を送ると、ペアリングコードが返ります。Claude Code側でコードを承認すると、送信者IDが許可リストに入ります。「まず自分だけがボットと話せる状態を作り、信頼できる相手を1人ずつ足す」設計です。
iMessage
起動時にMessagesデータベースから自分のアドレスを検出し、そこからの自分宛メッセージは自動で通します。他の連絡先は/imessage:access allowで手動追加します。
ペアリング方式には、コード承認のあとにもう1コマンドあります。Telegramの手順ではpairで自分のアカウントを登録してから、次のコマンドで「自分のアカウントだけが送れる」状態に絞ります。Discordも/discord:accessで同じ流れです。
/telegram:access pair <code>
/telegram:access policy allowlistメッセージが届くまでの4つの関門
送信者ゲートはメッセージ単位の防御です。その手前に、セッションと組織の3段階が並びます。
Channelsのメッセージが通る4段階
- 1
--channelsで名指しする(セッション単位)
.mcp.jsonにサーバーを書いただけでは何も届きません。起動のたびに--channelsで明示したサーバーだけがチャネルとして登録されます。 - 2
channelsEnabledが有効(組織)
マスタースイッチです。Team・Enterpriseでは、Ownerロールの管理者が有効化するまでChannels全体がブロックされます。設定場所はclaude.aiの管理設定か、管理対象設定ファイルです。
- 3
allowedChannelPlugins(組織)
Anthropicが管理する承認済み一覧を、組織独自の一覧に置き換えます。社内マーケットプレイスのプラグインだけを許可する運用もできます。管理対象設定ファイルで設定し、
channelsEnabled: trueが前提です。 - 4
送信者の許可リスト(チャネルの実装)
前の3段階は「誰が使えるか」を決め、この段階だけが「何が届くか」を決めます。
channelsEnabledが無効・未設定なら、サーバーは接続しツールも普段どおり動くのに、チャネルのメッセージだけが届きません。起動時には、管理者に有効化を頼むよう促す警告が出ます。
allowedChannelPluginsを空の配列にしても、--dangerously-load-development-channelsはそのブロックを迂回できます。開発フラグごと塞ぎたい場合は、空配列ではなくchannelsEnabled自体を未設定のままにします。
{
"channelsEnabled": true,
"allowedChannelPlugins": [
{ "marketplace": "claude-plugins-official", "plugin": "telegram" },
{ "marketplace": "acme-corp-plugins", "plugin": "internal-alerts" }
]
}Pro・Maxプランで組織に属さないユーザーは、組織ポリシーの2段階を最初から通らず、セッションごとの--channelsだけが残ります。
既定値は認証方式で変わる点にも注意が要ります。Console APIキーで認証している組織は既定で許可されます。ただし管理対象設定を配布している組織では、channelsEnabledが設定されるまでブロックされます。同じ組織でも、認証経路と管理対象設定の有無で初期状態が変わります。
ゲート設計でつまずきやすい3つの点
開発フラグが迂回するのは承認済み一覧だけ
--dangerously-load-development-channelsが迂回するのは承認済みプラグインの許可リストだけです。channelsEnabledが未設定・falseの組織ポリシーは、このフラグを付けても有効なままです。チャネルが登録されないときは、管理者側の設定を疑います。
もう1つの癖は、迂回がエントリ単位で効くことです。--channelsと併用しても、--channels側のエントリには迂回が及びません。承認済み一覧に載っていない自作チャネルは、開発フラグに渡し続けることになります。起動のたびに全画面の警告ダイアログが出て、「I am using this for local development」を選んで進みます。
MCP_PROTOCOL_NEGOTIATIONで、チャネルが登録されないことがある
v2のMCPクライアントランタイムでは、プロトコルリビジョン2026-07-28をネゴシエートするチャネルサーバーをClaude Codeは登録しません。このリビジョンはチャネルのメッセージを運べないためです。
ネゴシエーションが起きる条件は2つあります。1つはMCP_PROTOCOL_NEGOTIATIONをautoにした場合です。もう1つは、v2.1.285以降のフラグ取得セッションです。stdioサーバーへの問い合わせは、Anthropicが段階的に既定化を進めています。自作のstdioチャネルが--channelsに渡しても反応しないときは、autoを外すだけでは足りない可能性があります。legacyを指定すると、すべてのサーバーが従来のハンドシェイクのまま接続します。autoとlegacy以外の値は、デバッグログに警告を出して無視されます。この環境変数にはv2.1.221以降が必要です。
送信者ゲートを実装しても、.envのトークン漏洩がゲートより手前の穴になる
TelegramやDiscordのボットトークンは~/.claude/channels/<plugin>/.envに保存されます。環境変数TELEGRAM_BOT_TOKEN・DISCORD_BOT_TOKENで渡す方法もあります。送信者の許可リストがどれだけ厳格でも、このトークン自体が流出すればボットの制御を丸ごと奪われます。ゲートの実装だけでなく、トークンの保管場所へのアクセス権も同じ脅威モデルで扱います。
配送が実際に成功したかをサーバー側で確かめたいときは、通知に成否が返らない前提で設計します。サーバー側で状態を持ち、reply toolでその状態をセッションに報告させる構成です。
自作チャネルを組織内に配布するときの経路
送信者ゲートを実装したチャネルを自分だけでなくチームに配りたい場合、プラグイン化してマーケットプレイスに公開する経路があります。ただし自社マーケットプレイスに公開しただけでは研究プレビューの承認済みリストには載りません。承認済みリストはAnthropicが研究プレビュー中に管理しているもので、社内マーケットプレイスへの公開とは別の配布経路です。
Team・Enterprise組織であれば、管理者がallowedChannelPluginsにその内製プラグインを明示的に加えることで、開発フラグなしで組織内のメンバーに使わせられます。送信者ゲートの実装と、配布経路をどちらの許可リストに載せるかは別の意思決定であり、どちらか一方だけを固めても不十分です。
すでにMCPサーバーの許可リストを運用しているチームなら、allowedChannelPluginsも同じ運用ルールに乗せられます。Managed MCPの許可リスト・拒否リストも参照してください。
権限中継を足すかどうかの判断材料
送信者の許可リストが効いているチャネルは、reply toolによる返信まではそのまま拡張できます。しかし権限確認プロンプトの中継を足すかどうかは、もう一段別の判断になります。
権限中継を使わない場合、離席中にClaudeが権限確認に当たるとセッションは止まり、端末での応答を待ちます。中継を宣言したチャネルがあれば、同じ確認が端末とチャットの両方に出ます。先に届いた答えが採用され、もう一方は閉じられます。
権限中継で届くもの・届かないもの
中継される
Bash・Write・Editなどのツール実行の承認確認です。許可・拒否は、リクエストIDが開いている確認と一致したときだけ適用されます。中継されない
プロジェクトの信頼ダイアログと、MCPサーバーの同意ダイアログです。この2つはローカルの端末にしか出ません。
加工されて届く
v2.1.211以降は、確認の説明文と引数のプレビューから、方向制御文字・不可視文字・引用符や山括弧に似た文字を無害化します。v2.1.234以降はAPIキーなど、プレフィックスで判別できる認証情報も
[REDACTED]に置き換えます。空白の連続は1つの半角スペースにまとめられます。長いコマンドは3,500コードポイントまで丸ごと中継され、超えた場合は先頭と末尾を残し、間を省略マーカーで置き換えます。コマンドの末尾も承認者に届きます。マスクはシェル構文・パス・URLの文字を含む範囲には及びません。値を直列化できないときは
(value unserializable)と出ます。加工されずに届く
プレフィックスのない秘密情報は、マスクされずに中継先へ届きます。秘密鍵ブロックのように空白をまたぐ秘密情報も、チャネル仕様ではマスク対象外です。
一方、changelogには、大きすぎる秘密鍵ブロックは強いマスクで伏せるとあります。2つの記述が並ぶため、秘密鍵ブロックはどちらの挙動もあり得ると見ておきます。
中継先にはコマンド全体が出る場合があるため、説明文と引数のプレビューは、クライアント群を自分で管理していない限り信頼できない入力として扱います。
権限中継の許可リストは、送信者ゲートと同じリストを使う設計です。つまり「チャットに返信できる相手」と「Bashやファイル編集を承認できる相手」が同一集合になります。メッセージのゲートだけを見て「この相手はチャットに書き込めるだけ」と考えていると、実は同じ相手にツール実行の可否まで委ねていた、という食い違いが起こり得ます。
v2.1.234以降のClaude Codeは、そのセッションでチャネルとして登録したサーバーにだけ権限確認を送ります。つまり中継も、--channelsによる明示とチャネルの組織制御に守られています。さらにサーバー側が権限のcapabilityを宣言している必要があります。claude/channel/permissionにfalseを渡す書き方は、v2.1.234より前のClaude Codeでは「宣言した」と扱われるため、古いクライアントが混在する環境では省略するほうが安全です。
グループチャットの全員を信頼する設計にする場合は、権限のcapabilityを宣言しないという選択肢があります。メッセージは全員から受けつつ、ツール実行の承認だけは端末側に残せます。
実機のメモ
Channelsの2つのフラグは、研究プレビュー中はclaude --helpに載らない扱いです。v2.1.289で試すと、次のとおり該当行が1つも出ません。
claude --version
claude --help | grep -i channel出力は2.1.289 (Claude Code)の1行だけで、2つ目のコマンドは何も表示しません。ヘルプに載らないことと、フラグが使えないことは別です。有効かどうかはchannelsEnabledと起動時の通知で判断します。
まとめ
自作チャネルで書き手が責任を持つのは送信者ゲートの1層で、ここをchat.idとfrom.idの取り違えで壊すと、組織側の関門を固めても通ってしまいます。権限中継まで伸ばすときは、許可リストが「会話の相手」ではなく「セッションを操作できる相手」の基準で作られているかを先に見直します。