Claude Media
devcontainerのinit-firewall.sh — Claude Code参照構成で外向き通信を絞る

devcontainerのinit-firewall.sh — Claude Code参照構成で外向き通信を絞る

参照devcontainerのinit-firewall.shは外向き通信を許可先だけに絞ります。NET_ADMINとNET_RAWが要る理由、許可先の中身、外して自前制御に任せる選択までを順に見ます。

参照devcontainerの init-firewall.sh は、コンテナの外向き通信を「スクリプトが許可した宛先」だけに絞る仕組みです。走らせるために devcontainer.json の runArgs へ NET_ADMIN と NET_RAW を足しています。

このファイアウォールも2つのcapabilityも、Claude Code自体の動作には要りません。外して自前のネットワーク制御に任せる構成も、Claude Codeのドキュメントが認めています。

init-firewall.shは何をするスクリプトか

anthropics/claude-code リポジトリの .devcontainer/ に置かれた、iptablesとipsetによる許可リスト方式のスクリプトです。ドキュメントは「参照コンテナは動く例であって、保守されるベースイメージではない」と位置づけています。

参照構成のファイルは3つです。devcontainer.json がcapabilityと起動フックを、Dockerfile がiptablesとipsetの導入を、init-firewall.sh が通信の制限を受け持ちます。

手順

init-firewall.shの6段階

  1. 1

    NATルールを退避

    Docker内部DNS(127.0.0.11)向けのNATルールを控えておきます。

  2. 2

    ルールを空にする

    iptablesのルールとipsetを消します。

  3. 3

    先に開けておく通信

    DNS・SSH・localhostの通信を許可します。

  4. 4

    許可先をipsetに入れる

    GitHubのIP範囲とドメイン一覧を解決して、ipset allowed-domains に登録します。

  5. 5

    既定でDROPにする

    INPUT / FORWARD / OUTPUTの既定ポリシーをDROPにし、ipsetに含まれる宛先だけACCEPTします。

  6. 6

    自己検証

    curl で example.com に届かないこと、api.github.com に届くことを確かめます。

最後の自己検証が外れると、スクリプトは exit 1 で終わります。

なぜNET_ADMINとNET_RAWを足すのか

コンテナの中でiptablesを書き換えるには、通常の権限では足りません。ドキュメントは「コンテナ内でファイアウォールを動かすには追加の権限が要る」として、参照構成が runArgs で NET_ADMIN と NET_RAW を足していると説明しています。

参照構成の devcontainer.json では、該当部分は次の形です。

"runArgs": [
  "--cap-add=NET_ADMIN",
  "--cap-add=NET_RAW"
],
"remoteUser": "node",
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
"waitFor": "postStartCommand"

起動のたびに postStartCommand がスクリプトをrootで実行し、waitFor により完了するまで待ちます。作業ユーザーは非rootの node です。Dockerfile は /etc/sudoers.d/node-firewall に「node はこのスクリプトだけをパスワードなしでroot実行できる」という1行を書いています。iptablesとipsetのインストールも Dockerfile 側の担当です。

つまり権限の付与は、ファイアウォールを張るための限定的な例外として設計されています。逆に言えば、ファイアウォールを使わないならcapabilityを足す理由も消えます。

許可先には何が入っているか

スクリプトが通すのは、GitHubのIP範囲と、リストにあるドメインのAレコードです。

種別内容
GitHub内容api.github.com/meta のweb・api・gitの範囲を集約して登録
ドメイン内容registry.npmjs.org / api.anthropic.com / sentry.io / statsig.com / marketplace.visualstudio.com / vscode.blob.core.windows.net / update.code.visualstudio.com
ホスト内容デフォルトルートから割り出した /24 のネットワーク

読むときの注意点が3つあります。

  • ドメインは起動時に1回だけIPへ解決され、ipsetに固定されます。その後のDNSの変化は追いません
  • 解決に失敗したドメインが1つでもあると、スクリプトは exit 1 で止まります
  • スクリプトに ip6tables の記述はなく、登録するのもAレコード(IPv4)だけです

IPv6については、デュアルスタックのDockerネットワークでIPv6の通信がファイアウォールをすり抜けるという報告と、ip6tables で塞ぐ修正案がプルリクエスト #81423に出ています。IPv6が有効な環境では、curl -6 https://example.com が届かないことも確かめておく価値があります。

ホストのネットワークは、デフォルトルートのゲートウェイから末尾を落として /24 に固定されます。スクリプトの置換式を手元のシェルで走らせると、次の結果になります。

echo "172.17.0.1" | sed "s/\.[0-9]*$/.0\/24/"
# 172.17.0.0/24
echo "10.0.5.1" | sed "s/\.[0-9]*$/.0\/24/"
# 10.0.5.0/24

ゲートウェイを含むネットワークが /16 でも、通るのは同じ第3オクテットの /24 だけです。同じDockerネットワーク上の別コンテナへ通信が必要な構成では、この範囲に収まるかを見ます。

くらべる

閉じるものと開いたまま残るもの

既定でDROP

閉じる

許可リストに無い宛先へのHTTPSなど、上のipsetとホストのネットワークに入らない外向き通信です。

宛先を問わない

開いたまま

外向きUDP 53(DNS)、外向きTCP 22(SSH)、localhostです。「外向き通信が完全に閉じる」構成ではありません。

許可ドメインの調べ方とスクリプトへの足し方

許可先を決める一次情報は、ドキュメントの「Network access requirements」表です。たとえば次のホストが載っています。

ホスト用途
api.anthropic.com用途APIリクエスト、機能フラグ、テレメトリ
claude.ai用途claude.aiアカウントの認証
platform.claude.com用途Consoleアカウントの認証とOAuthトークンの更新
registry.npmjs.org用途プラグインの導入、npm経由のMCPサーバー、npmでのClaude Code導入
github.com用途プラグインマーケットプレイスのclone

表にはこのほか mcp-proxy.anthropic.com や downloads.claude.ai なども並びます。全体は原本で確認してください。

参照スクリプトのドメイン一覧と、この表を並べると差が2種類あります。1つは、表にある claude.ai と platform.claude.com がスクリプトの一覧に入っていないことです。表の説明では、claude.aiアカウントのOAuthトークンの交換・更新・失効も platform.claude.com に向かい、ConsoleとClaude.aiのどちらのサインインでも必要です。

もう1つは逆向きで、スクリプトにある sentry.io と statsig.com が現行の表に載っておらず、表にあるDatadogの2ホストはスクリプトに無いことです。自分の構成で必要なホストは表から拾い、init-firewall.sh の for domain in に足します。

for domain in \
    "registry.npmjs.org" \
    "api.anthropic.com" \
    "platform.claude.com" \
    "claude.ai"; do

これは例示です。実際に何を足すかは、認証方式(Claudeアカウント / APIキー / クラウドプロバイダー)と使う機能で変わります。Bedrockなどのクラウドプロバイダーを使う場合、モデル通信と認証は api.anthropic.com ではなくプロバイダー側へ向かいます。プロバイダーのエンドポイントも許可に足します。WebFetchを使うなら、プロバイダーに関係なくドメインの安全チェックが api.anthropic.com に向かうため、これも許可に残します。

MCPサーバーを使う場合は、リモートサーバーのドメインも許可リストに入れます。ドキュメントも同じ注意を書いています。

テレメトリ先を許可せずに済ませる

表にはDatadogの2ホスト(運用テレメトリとエラーレポート)があり、どちらも任意の宛先です。許可リストに載せたくないときは、containerEnv で送信そのものを止めます。

"containerEnv": {
  "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
}

この変数は両方のホストへの送信を止めます。ただし機能フラグの評価も止まるため、Remote Controlなど機能フラグに頼る機能はコンテナ内で使えなくなります。個別に切るなら DISABLE_TELEMETRY と DISABLE_ERROR_REPORTING があります。詳しくはDISABLE_NONESSENTIAL_TRAFFICの解説にまとめています。

プロバイダーによっても、送信先は変わります。ドキュメントの既定値の表では、Bedrock・Vertex・Foundryのときテレメトリとエラーレポートは既定でオフです。Anthropic APIを直接使う場合は運用テレメトリが既定でオンで、エラーレポートはProとMaxのサインインなどの条件でオンになります。クラウドプロバイダー経由の構成なら、許可リストに足すホストは減ります。

制限があることをClaudeに伝えておく

許可リスト外への通信は失敗します。制限の存在を伝えておくと、失敗したときにClaudeが原因を探す手間を減らせます。リポジトリの CLAUDE.md に、次のような一節を置いておく方法があります。

## ネットワーク制限
- このコンテナの外向き通信は init-firewall.sh の許可先に限られる
- 許可外のホストで接続が拒否されたら、回避せずホスト名を報告する
- 許可先の追加は init-firewall.sh の編集で行う(勝手に変更しない)

これは運用の一例で、ドキュメントに載っている設定ではありません。ただ、REJECTで即座に失敗する構成とは相性が良く、失敗したホスト名がそのまま許可リスト更新の材料になります。

効いているかをClaude Codeに確かめさせる

ファイアウォールは、張れたことよりも「閉じるべき所が閉じている」ことの確認が肝心です。スクリプトの自己検証は example.com と api.github.com の2点だけなので、自分の構成に合わせた確認を足す余地があります。

コンテナ内のターミナルでは、次のように確認できます。

# 許可していない宛先(拒否されるはず)
curl --connect-timeout 5 -sS https://example.com -o /dev/null
# 許可している宛先(通るはず)
curl --connect-timeout 5 -sS https://api.github.com/zen
# 現在の ipset の中身
sudo ipset list allowed-domains | head -20

この確認はClaudeに頼むこともできます。--dangerously-skip-permissions で動かしていれば、実行の都度の許可も省けます。「許可リスト外の3つの宛先に curl で到達できないことを確かめ、結果を表にして」と頼み、実行結果を読ませます。拒否されているはずの宛先に届いたら、許可リストか自己検証の見直しが要る合図です。

ルールの順序には理由がある

スクリプトの並びは、手を入れるときの目印になります。

  • 最初にDockerの内部DNS向けNATルールを退避します。全ルールを消すとコンテナ内の名前解決が壊れるためで、消した後に必要な分だけ戻します
  • DNSの許可をDROPより前に置いています。後段でドメインを dig で引くには、名前解決が通っている必要があるからです
  • 既定ポリシーをDROPにした直後に、確立済み接続(ESTABLISHED,RELATED)を許可します。すでに承認した通信を切らないための順序です
  • 最後の拒否はDROPでなく REJECT --reject-with icmp-admin-prohibited です。スクリプトのコメントは、即座にフィードバックを返すためと説明しています

DROPは応答が返らず、接続がタイムアウトするまで待たされます。REJECTなら失敗がすぐ分かります。許可漏れを探す場面では、後者のほうが切り分けが速くなります。

起動時に止まったときの切り分け

waitFor があるので、スクリプトが失敗するとコンテナの起動が完了しません。スクリプトや ipset が出すメッセージから、原因を絞れます。

メッセージ原因の見当
Failed to fetch GitHub IP ranges原因の見当api.github.com/meta の取得に失敗している。GitHubへの通信が届いていない
GitHub API response missing required fields原因の見当応答に web / api / git のいずれかが無い
Failed to resolve <domain>原因の見当一覧のドメインのAレコードが引けない。綴りかDNSを確認
Element cannot be added to the set: it's already added原因の見当2つのドメインが同じIPに解決され、ipsetへの重複登録で停止している(下記)
Failed to detect host IP原因の見当デフォルトルートが見つからない
Firewall verification failed - was able to reach https://example.com原因の見当ルールが効いていない
Firewall verification failed - unable to reach https://api.github.com原因の見当GitHubの許可が効いていない

for domain in に足したドメインが引けないときも、同じく止まります。足す前に、コンテナの外で dig +short A <domain> が返るかを確かめておくと安全です。

重複登録は、ipset のエラーが set -e で止まる型です。issue #91327では、marketplace.visualstudio.com と vscode.blob.core.windows.net が同じIPに解決され、2つ目の ipset add が失敗して postStartCommand が終了コード1で止まったと報告されています。報告者が示す修正は、ドメインごとのループの ipset add を ipset add -exist に変える1行です。CDNの背後にあるホストを足すときは、同じ症状が出うる点に注意します。

ファイアウォールを外して自前制御に任せる選択

ドキュメントは、ファイアウォールのスクリプトと2つのcapabilityについて「Claude Code自体には要らない。外して自前のネットワーク制御に任せてよい」と書いています。使い分けの目安は次のとおりです。

使い分け

状況別の向く構成

  • 個人の検証用コンテナ

    許可先を自分で管理できるなら、参照スクリプトをそのまま使います。変更点が1ファイルに収まります。

  • 組織にegress制御がある

    ファイアウォールとcapabilityを外します。二重管理を避けられ、NET_ADMIN も要りません。

  • 許可先が頻繁に変わる

    プロキシなどの自前制御が向きます。起動時に1回しか解決しないスクリプトは、変化に追随しません。

自前で絞る場合の入口は、Claude Codeのプロキシ設定と、sandboxの許可ドメイン設定です。Codespacesでの構築はClaude CodeをCodespacesで始める手順を参照してください。

それでも残るリスク

ドキュメントは注意書きで、--dangerously-skip-permissions で動かした場合、コンテナは悪意あるプロジェクトによる持ち出しを防がないと述べています。持ち出されうるのは、コンテナ内でアクセスできるものすべてで、~/.claude の認証情報も含まれます。

claude --help(v2.1.289で実行)は、このフラグを次のように説明しています。

claude --help
#   --dangerously-skip-permissions        Bypass all permission checks.
#                                         Recommended only for sandboxes with no
#                                         internet access.

許可リストに載せた宛先へは、依然として通信できます。GitHubのIP範囲と api.anthropic.com は許可先そのものです。ファイアウォールは持ち出し先を減らす対策で、持ち出しをなくす対策ではありません。

併用できる手当ては、次のとおりです。

  • ~/.ssh やクラウド認証情報のファイルをコンテナにマウントしない。スコープの狭い短命なトークンを使う
  • --dangerously-skip-permissions は remoteUser を非rootにして使う。rootで起動するとこのフラグは拒否される
  • 使わせたくない組織では、managed settingsの permissions.disableBypassPermissionsMode を "disable" にする
  • 対話を減らしたいだけなら、分類器が事前に確認するauto modeを先に検討する

コンテナ設計そのもの(認証情報の遮断やDocker sandboxとの比較)は、Claude CodeをDevContainerで安全に動かす完全実装で扱っています。ファイアウォールは、その設計に足す通信面の層にあたります。

まとめ

参照スクリプトは、組織のegress制御が無い環境で許可先を自分で管理するための土台です。DNSとSSHが開いたままで、IPv6の記述も無い点を踏まえ、egress制御が別にあるなら外す選択も成り立ちます。

この記事を共有:XはてブLinkedIn