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 | 役割ボリュームのマウント、runArgs のcapability、拡張機能、containerEnv |
Dockerfile | 役割ベースイメージ、開発ツール、Claude Codeのインストール |
init-firewall.sh | 役割外向き通信を許可先だけに制限 |
スクリプトの流れは6段階です。
- Docker内部DNS(127.0.0.11)向けのNATルールを退避する
- iptablesのルールとipsetを空にする
- DNS・SSH・localhostの通信を先に許可する
- GitHubのIP範囲とドメイン一覧を解決してipset
allowed-domainsに入れる - 既定ポリシーをINPUT / FORWARD / OUTPUTすべてDROPにし、ipsetに含まれる宛先だけACCEPTする
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 のネットワーク |
| 常時許可 | 内容外向きUDP 53(DNS)、外向きTCP 22(SSH)、localhost |
読むときの注意点が3つあります。
- ドメインは起動時に1回だけIPへ解決され、ipsetに固定されます。その後のDNSの変化は追いません
- 解決に失敗したドメインが1つでもあると、スクリプトは
exit 1で止まります - スクリプトに
ip6tablesの記述はなく、登録するのもAレコード(IPv4)だけです
DNSとSSHは宛先を問わず通ります。許可リストに無い宛先へHTTPSで出られなくなる一方、22番ポートとDNSの問い合わせは開いたままです。「外向き通信が完全に閉じる」構成ではありません。
許可ドメインの調べ方とスクリプトへの足し方
許可先を決める一次情報は、ドキュメントの「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 なども並びます。全体は原本で確認してください。
参照スクリプトのドメイン一覧と、この表を並べると差があります。claude.ai と platform.claude.com は、スクリプトの一覧に入っていません。自分の構成で必要なホストはこの表から拾い、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 ではなくプロバイダー側へ向かいます。プロバイダーのエンドポイントも許可に足します。
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を直接使う場合だけ、Datadog向けが既定でオンになります。クラウドプロバイダー経由の構成なら、許可リストに足すホストは減ります。
制限があることを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--dangerously-skip-permissions で動かす場合は、確認そのものもClaudeに任せられます。「許可リスト外の3つの宛先に curl で到達できないことを確かめ、結果を表にして」と頼み、実行結果を読ませます。拒否されているはずの宛先に届いたら、許可リストか自己検証の見直しが要る合図です。
ルールの順序には理由がある
スクリプトの並びは、手を入れるときの目印になります。
- 最初にDockerの内部DNS向けNATルールを退避します。全ルールを消すとコンテナ内の名前解決が壊れるためで、消した後に必要な分だけ戻します
- DNSの許可をDROPより前に置いています。後段でドメインを
digで引くには、名前解決が通っている必要があるからです - 既定ポリシーをDROPにした直後に、確立済み接続(ESTABLISHED,RELATED)を許可します。すでに承認した通信を切らないための順序です
- 最後の拒否はDROPでなく
REJECT --reject-with icmp-admin-prohibitedです。スクリプトのコメントは、即座にフィードバックを返すためと説明しています
DROPは応答が返らず、接続がタイムアウトするまで待たされます。REJECTなら失敗がすぐ分かります。許可漏れを探す場面では、後者のほうが切り分けが速くなります。
起動時に止まったときの切り分け
waitFor があるので、スクリプトが失敗するとコンテナの起動が完了しません。スクリプトが出す ERROR: のメッセージから、原因を絞れます。
| メッセージ | 原因の見当 |
|---|---|
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を確認 |
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> が返るかを確かめておくと安全です。
ファイアウォールを外して自前制御に任せる選択
ドキュメントは、ファイアウォールのスクリプトと2つのcapabilityについて「Claude Code自体には要らない。外して自前のネットワーク制御に任せてよい」と書いています。使い分けの目安は次のとおりです。
| 状況 | 向く構成 | 理由 |
|---|---|---|
| 個人の検証用コンテナで、許可先を自分で管理できる | 向く構成参照スクリプトをそのまま使う | 理由変更点が1ファイルに収まる |
| 組織にプロキシやegress制御がすでにある | 向く構成ファイアウォールとcapabilityを外す | 理由二重管理を避けられ、NET_ADMIN も不要 |
| 許可先が頻繁に変わる | 向く構成自前制御(プロキシなど) | 理由起動時に1回しか解決しないスクリプトは追随しない |
自前で絞る場合の入口は、Claude Codeのプロキシ設定と、sandboxの許可ドメイン設定です。Codespacesでの構築はClaude CodeをCodespacesで始める手順を参照してください。
それでも残るリスク
ドキュメントは注意書きで、--dangerously-skip-permissions で動かした場合、コンテナは悪意あるプロジェクトによる持ち出しを防がないと述べています。持ち出されうるのは、コンテナ内でアクセスできるものすべてで、~/.claude の認証情報も含まれます。
許可リストに載せた宛先へは、依然として通信できます。GitHubのIP範囲と api.anthropic.com は許可先そのものです。ファイアウォールは持ち出し先を減らす対策で、持ち出しをなくす対策ではありません。
併用できる手当ては、次のとおりです。
~/.sshやクラウド認証情報のファイルをコンテナにマウントしない。スコープの狭い短命なトークンを使う--dangerously-skip-permissionsはremoteUserを非rootにして使う。rootで起動するとこのフラグは拒否される- 使わせたくない組織では、managed settingsの
permissions.disableBypassPermissionsModeを"disable"にする - 対話を減らしたいだけなら、分類器が事前に確認するauto modeを先に検討する
コンテナ設計そのもの(認証情報の遮断やDocker sandboxとの比較)は、Claude CodeをDevContainerで安全に動かす完全実装で扱っています。ファイアウォールは、その設計に足す通信面の層にあたります。
まとめ
init-firewall.sh は、起動時に許可先を固定して既定DROPにする1本のスクリプトで、NET_ADMIN と NET_RAW はそれを走らせるためだけの権限です。許可先は表から自分の構成に合わせて足し、DNSとSSHが開いたままである点、IPv6の記述が無い点を踏まえて使います。組織にegress制御があるなら、スクリプトもcapabilityも外す選択が成り立ちます。