Claude Code に秘密を読ませない — sandbox.credentials で認証情報ファイルと環境変数を守る

Claude Code のサンドボックスは、既定ではファイルシステム全体を『読み取り可』にしており、~/.aws/credentials や ~/.ssh といった認証情報ファイルもエージェントから読めてしまいます。さらにサンドボックス内の Bash コマンドは親プロセスの環境変数(GITHUB_TOKEN など)を継承します。v2.1.187 で追加された sandbox.credentials 設定は、これらのファイルの読み取り拒否と秘密の環境変数の除去(deny)、v2.1.199 以降はツールを動かしたまま値を隠す mask を宣言できます。本記事では、既定の露出範囲・deny と mask の使い分け・スコープと安全設計・組織での強制・補助の環境変数スクラブまでを実装目線で整理します。

Claude Code のサンドボックスは、Bash コマンドをファイルシステム・ネットワークの境界内で走らせる安全機構です。ですが見落とされがちな既定があります——サンドボックスの読み取りポリシーは「コンピュータ全体を読み取り可」で、~/.aws/credentials~/.ssh といった認証情報ファイルもエージェントから読めてしまう。さらにサンドボックス内の Bash コマンドは親プロセスの環境変数を継承するので、GITHUB_TOKEN などの秘密もそのまま渡ります。v2.1.187 で追加された sandbox.credentials は、この穴を塞ぐための設定です。本記事で、既定の露出範囲と守り方を整理します。関連する権限制御は auto mode の hard_deny も参照。

到達点

  • サンドボックス既定では認証情報が読めてしまうことを理解する
  • sandbox.credentialsfiles / envVarsdeny の使い方
  • denymask の違い(ツールを壊すか、動かしたまま隠すか)
  • スコープと安全設計(deny は誰でも追加可・除去不可、mask は信頼スコープのみ)
  • 組織での強制と、補助の 環境変数スクラブ

まず既定の露出を知る

サンドボックスの既定のファイル読み取りはこうです。

  • 書き込み:作業ディレクトリとセッション一時ディレクトリのみ
  • 読み取り:コンピュータ全体(一部の拒否ディレクトリを除く)

つまり書き込みは厳しく制限される一方、読み取りはほぼ全開。公式ドキュメントも明記していますが、この既定は ~/.aws/credentials~/.ssh/ の読み取りを許してしまいます。加えて、環境変数の面でも:

サンドボックス内の Bash コマンドは、既定で親プロセスの環境変数を継承する。そこに設定された認証情報を含む。

エージェントが実行するコマンド(やそのサブプロセス)が、あなたのクラウド認証情報・SSH 鍵・トークンを読める——これが出発点の問題です。組み込みの拒否リストは無いため、放置すると露出したままになります。

sandbox.credentials(v2.1.187+)

この設定で、守るべき認証情報ファイルと環境変数を宣言します。sandbox.enabled: true が前提です。

{
  "sandbox": {
    "enabled": true,
    "credentials": {
      "files": [
        { "path": "~/.aws/credentials", "mode": "deny" },
        { "path": "~/.ssh", "mode": "deny" }
      ],
      "envVars": [
        { "name": "GITHUB_TOKEN", "mode": "deny" },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    }
  }
}

"mode": "deny" の挙動:

  • files:サンドボックス内でその読み取りを拒否(filesystem.denyRead と同じ制限)。ファイルは deny のみ対応
  • envVars:サンドボックス化された各コマンドの実行前に、その環境変数を unset(除去)する

credentials ブロックにまとめることで、一般的な filesystem ルールと認証情報ルールを分離して管理できます。ファイルパスは sandbox.filesystem.* と同じ prefix ルール(/=絶対、~/=ホーム、./=プロジェクト)に従います。

denymask の使い分け

deny には弱点があります——変数を完全に消すため、それを必要とするツール(ghnpm など)が壊れます。そこで v2.1.199 以降、環境変数には "mode": "mask" が使えます。

**mask は「認証情報を守りつつ、認証するツールは動かす」**ための仕組みです。

  • サンドボックス内のコマンドには、実値の代わりにセッションごとのセンチネル値が見える
  • リクエストが指定ホスト(injectHosts)向けにサンドボックスを出るとき、プロキシがセンチネルを実値に置換する
  • コマンド本体やそのログには実値が残らないが、リクエストは正しく認証される
{
  "sandbox": {
    "enabled": true,
    "network": {
      "tlsTerminate": {},
      "allowedDomains": ["*.github.com", "registry.npmjs.org"]
    },
    "credentials": {
      "envVars": [
        { "name": "GH_TOKEN", "mode": "mask", "injectHosts": ["api.github.com"] },
        { "name": "NPM_TOKEN", "mode": "mask" }
      ]
    }
  }
}

上の例では、GH_TOKENapi.github.com へのリクエスト時のみ、NPM_TOKENinjectHosts 無しなので allowedDomains の全ホストで置換されます。

mask の必須条件と注意

  • network.tlsTerminate が必須:プロキシがリクエスト内容(≒ HTTPS)を見て置換するため、プロキシが TLS を終端する必要があります。無いと fail closed(センチネルのまま送られ認証失敗)。この設定ミスは起動時と /doctor で報告されます
  • injectHostsallowedDomains に含まれている必要がある
  • 信頼スコープからのみ有効:mask はプロキシに実値の送信を許可するため、user 設定・managed 設定・--settings フラグからのみhonored。リポジトリの .claude/settings.json 等に書いた mask / tlsTerminate無視されます(悪意あるリポジトリ設定で鍵を外部に送らせない安全設計)
  • 同じ変数が deny でも指定されたら deny が優先
denymask
値の見え方消える(unset)センチネル(偽値)
ツール動作壊れうる動く
必要条件なしtlsTerminate + 信頼スコープ
対象files / envVarsenvVars のみ(v2.1.199+)

スコープと安全設計

  • deny は「狭める」だけ:どのスコープからでも deny追加できるが、あるスコープが追加した deny を別スコープが除去することはできない。プロジェクトが緩めても、user/managed の deny は残る
  • mask は信頼スコープのみ:前述の通り、リポジトリ設定からは効かない
  • 設定ファイル自体は保護:サンドボックスは全スコープの settings.json と managed 設定ディレクトリへの書き込みを自動拒否するので、サンドボックス内コマンドが自分のポリシーを書き換えられない

補助:全サブプロセスから認証情報を剥ぐ

sandbox.credentialsサンドボックス化された Bash コマンドにのみ効きます。サンドボックスの有無に関わらずすべてのサブプロセスから Anthropic・クラウドプロバイダの認証情報を剥ぎたいなら、環境変数 CLAUDE_CODE_SUBPROCESS_ENV_SCRUB を使います。用途に応じて併用してください。

実務レシピ

  1. サンドボックスを有効化:sandbox.enabled: true(macOS / Linux / WSL2)
  2. 鍵ファイルを deny:~/.aws~/.ssh~/.config/gcloud など、コマンドが読む必要のない認証情報ディレクトリを credentials.filesdeny
  3. 不要な秘密の環境変数を deny:コマンドが使わないトークンは deny で unset
  4. 使うトークンは mask:ghnpm 等が必要とするものは deny でなく mask にし、tlsTerminate + allowedDomains + injectHosts を設定(user/managed 設定に置く)
  5. 組織なら managed 設定で強制:enabled / failIfUnavailable を managed で固定し、credentials の deny をベースラインとして配布

注意点

  • 組み込みの拒否リストは無い:列挙したものだけが守られます。設定しなければ既定のまま露出します。ここが最重要。
  • mask は tlsTerminate 前提:忘れると認証が静かに壊れます(fail closed)。/doctor で確認を
  • mask をリポジトリ設定に書いても効かない:必ず user / managed / --settings に置く
  • サンドボックス外は別管理:Read/Edit ツールやサブエージェントのファイルアクセスは権限システム側。サンドボックスは Bash サブプロセスの境界です
  • ネットワークも併せて締める:読み取りを塞いでも、広い allowedDomains があると持ち出し経路が残ります。ファイルとネットワークは両輪で締める

まとめ

Claude Code のサンドボックスは書き込みには厳しい一方、既定の読み取りはほぼ全開で、~/.aws/credentials~/.ssh、継承された GITHUB_TOKEN などがエージェントから読める状態です。sandbox.credentials(v2.1.187+)で、**ファイルの読み取り拒否と秘密の環境変数の除去(deny)**を宣言し、ツールを動かしたまま値を隠したいものは mask(v2.1.199+、tlsTerminate 必須・信頼スコープのみ)にする——これが基本形です。

勘所は 3 つ — 組み込みの拒否リストは無いので必ず自分で列挙する、使うトークンは deny でなく mask にしてツールを壊さない、mask は user/managed 設定に置いて tlsTerminate を忘れない。エージェントに自律的に Bash を任せるほど、「秘密を読ませない」この一手が効いてきます。まず ~/.aws~/.sshdeny から始めるのがおすすめです。