Claude Media
Agent SDKのsandbox設定をコードから制御する — TypeScript/Python実装ガイド

Agent SDKのsandbox設定をコードから制御する — TypeScript/Python実装ガイド

Agent SDKのSandboxSettingsをTypeScript/Pythonで設定し、コマンド・ネットワークを制限する方法。両言語の設定差とdangerouslyDisableSandboxのフォールバックを解説。

Agent SDKのsandbox設定でできること

Agent SDKのsandboxオプションは、Claudeが実行するBashコマンドをOSレベルで隔離する機能を、設定ファイルではなくコードから直接制御する仕組みです。SandboxSettingsをquery()やClaudeAgentOptionsに渡すだけで、コマンド実行・ネットワーク到達先・ファイル書き込み範囲をプログラム的に絞り込めます。

TypeScript版とPython版は同じsandboxオプションを共有していますが、既定の挙動と設定項目の粒度が言語間で食い違います。両方でSDKを使い分けるチームほど、この差分を知らずに本番へ出すと片方だけ想定外の動作になります。本記事はその差分を実装コードで示します。SDK自体の基本的な始め方はClaude Agent SDK入門で扱っているので、はじめてSDKを触る場合は先にそちらで最小構成を動かしてください。

まず決めること — 隔離できないとき、止めるか続けるか

最初に決めるのは、サンドボックスが起動できない環境で処理を止めるか、隔離なしで続けるかです。SDKの既定値は言語によって逆になっています。

くらべる

failIfUnavailableの既定値

既定は true

TypeScript

enabled: trueなのに起動できないと、その場で止まります。隔離なしで走らせたいときだけfailIfUnavailable: falseを明示します。

既定は隔離なしで続行

Python

enabled: Trueでも起動できなければ、stderrに警告を出すだけでコマンドを実行します。止めたいときは"failIfUnavailable": Trueを自分で足します。

CI環境でサンドボックスの可用性が不安定なチームは、この差が効きます。両言語ともfailIfUnavailableを明示しておけば、既定値の違いに左右されません。

Python SDKと同じく、Claude Code本体の既定も同じ値です。起動できないときの既定は「隔離なしで実行」で、止めたいならsandbox.failIfUnavailableをtrueにします。SDKを使わずCLIで試すなら、--settingsにJSON文字列を渡せます。Claude Code v2.1.287のclaude --helpで確認したところ、サンドボックス専用のフラグは無く、--settingsが次のとおり説明されています。

$ claude --version
2.1.287 (Claude Code)
$ claude --help | grep -A2 -E "^\s+--settings"
  --settings <file-or-json>             Path to a settings JSON file or a JSON
                                        string to load additional settings from

--settingsでallowUnsandboxedCommands: falseを渡すと、サンドボックスは「管理者必須」扱いになります。リポジトリ側の設定ファイルによる緩和は無視されます。

TypeScriptでSandboxSettingsを有効にする

TypeScript版のSandboxSettingsは次の形をとります。

type SandboxSettings = {
  enabled?: boolean;
  failIfUnavailable?: boolean;
  autoAllowBashIfSandboxed?: boolean;
  excludedCommands?: string[];
  allowUnsandboxedCommands?: boolean;
  network?: SandboxNetworkConfig;
  filesystem?: SandboxFilesystemConfig;
  ignoreViolations?: Record<string, string[]>;
  enableWeakerNestedSandbox?: boolean;
  ripgrep?: { command: string; args?: string[] };
};

enabled: trueにするとBashコマンドの実行がサンドボックス内に隔離されます。実際に有効化するコードは次の通りです。

import { query } from "@anthropic-ai/claude-agent-sdk";
 
try {
  for await (const message of query({
    prompt: "Build and test my project",
    options: {
      sandbox: {
        enabled: true,
        autoAllowBashIfSandboxed: true,
        network: {
          allowLocalBinding: true
        }
      }
    }
  })) {
    if ("result" in message) console.log(message.result);
  }
} catch (error) {
  // サンドボックスが起動できない場合など、
  // 単発のquery()はエラー結果をyieldした後にthrowする
  console.log(`Session ended with an error: ${error}`);
}

autoAllowBashIfSandboxedは既定でtrueで、サンドボックス化されたBashコマンドを自動承認します。

サンドボックスが動くのはmacOS・Linux・WSL2です。ネイティブのWindowsでは隔離できません。CLIの既定やPython SDKの既定では隔離なしで実行され、failIfUnavailableがtrueのTypeScript SDKでは起動時に止まります。使うならWSL2ディストリビューション内でClaude Codeを動かします。macOSは組み込みのSeatbeltフレームワークを使うので追加インストールは不要です。LinuxとWSL2ではbubblewrapとsocatが要ります。

起動できないとquery()の結果メッセージがsubtype: "error_during_execution"になり、単発のquery()ではそのままthrowされます。上のコードのようにtry/catchで囲んでください。

Pythonでは何が違うのか — 型に無いキーと足りない項目

Python版のSandboxSettingsはTypedDictで定義されています。

class SandboxSettings(TypedDict, total=False):
    enabled: bool
    autoAllowBashIfSandboxed: bool
    excludedCommands: list[str]
    allowUnsandboxedCommands: bool
    network: SandboxNetworkConfig
    ignoreViolations: SandboxIgnoreViolations
    enableWeakerNestedSandbox: bool

TypeScript版と見比べると、failIfUnavailableとfilesystemとripgrepが型として宣言されていません。厳格に止めたいなら、型に無いキーを辞書へ直接足します。

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
 
sandbox_settings = {
    "enabled": True,
    "autoAllowBashIfSandboxed": True,
    "failIfUnavailable": True,
    "network": {"allowLocalBinding": True},
}
 
 
async def main():
    try:
        async for message in query(
            prompt="Build and test my project",
            options=ClaudeAgentOptions(sandbox=sandbox_settings),
        ):
            print(message)
    except Exception as error:
        print(f"Session ended with an error: {error}")
 
 
asyncio.run(main())

failIfUnavailableはSDKがそのままClaude Codeへ転送し、Claude Code側が値に従います。SandboxSettingsの型にはまだ宣言されていませんが、SDKが転送するので動作します。

ネットワークとファイルシステムを絞り込む

SandboxNetworkConfigはサンドボックス化されたBashコマンドのネットワーク到達先を制御します。WebFetchツールはこの設定の対象外で、権限ルール側で別途制御されます。

allowedDomainsで許可するドメインを指定し、deniedDomainsで拒否するドメインを指定します。両方に一致した場合は拒否が優先されるので、*.npmjs.orgのようなワイルドカードで広く許可しつつ、特定のホストだけ塞ぐ使い方ができます。

Managed Agentsのクラウドサンドボックスでは、こうしたネットワーク制御を自前で書かずに済みます。使える言語・ツールの一覧として、定義済みの環境が渡されます。

httpProxyPort/socksProxyPortを指定すると、内蔵のプロキシの代わりに自前のプロキシへ通信を経由させられます。

Unixソケットの扱いは言語ではなくOSで分かれます。

  • allowUnixSocketsは両言語ともmacOSでだけ効き、指定パスのソケットを許可する
  • LinuxとWSL2ではallowUnixSocketsが無視されるので、allowAllUnixSocketsをtrueにしてすべてのUnixソケットを許可する

TypeScript版のリファレンスはmacOS限定の注記を省いています。Claude Code本体の設定リファレンスでは、LinuxとWSL2で無視されると書かれています。

WSL2では、cmd.exeや/mnt/c/配下のWindowsバイナリの起動がUnixソケット経由でWindows側に渡ります。サンドボックス内からこれらを起動したいときは、allowAllUnixSocketsを使います。これが要るのはseccompフィルタが入っていてUnixソケットがブロックされる環境で、フィルタが無ければそもそもブロックされません。

ネットワーク設定の項目名も、両言語で完全には一致しません。TypeScript版だけがstrictAllowlistを持ち、Python版だけがallowMachLookup(macOSのXPC/Machサービス名の許可リスト)を持ちます。

strictAllowlistは、許可リスト外のホストへ接続しようとしたときにプロンプトを出さず、拒否に切り替える設定です。Claude Code v2.1.219以降が必要で、ユーザー設定・managed設定・CLIの--settingsでだけ有効です。プロジェクトの.claude/settings.jsonに書いても効きません。

ファイルシステムの読み書き制限は、TypeScript版だけがSandboxFilesystemConfigとして持っています。

type SandboxFilesystemConfig = {
  allowWrite?: string[];
  denyWrite?: string[];
  denyRead?: string[];
};

allowWriteで書き込みを許可するパスを、denyWriteとdenyReadで拒否するパスを指定します。

パス指定にワイルドカードを使うときは、プラットフォームで挙動が変わります。Claude Code本体の設定リファレンスでは、allowWriteとdenyWriteのワイルドカードはmacOSでは効きますが、LinuxとWSL2では*・?・[を含む項目が無視されます。Linuxで書き込み範囲を絞るなら、具体的なパスで書くのが確実です。

コンテナ単位でファイルシステムごと隔離したい場合は、DevContainerでの安全な実装のようにOSレベルの隔離と組み合わせる選択肢もあります。

サンドボックスの外に出る2つの経路

サンドボックス化されたコマンドが外に出る道は2つあり、承認の仕組みがまるで違います。

経路

外に出る経路の違い

  • allowUnsandboxedCommands(モデルの要求)

    既定はtrueです。モデルがツール入力にdangerouslyDisableSandbox: trueを付けて要求し、TypeScriptならcanUseTool、Pythonならcan_use_toolハンドラーが呼ばれます。falseにするとこのパラメーターは無視されます。

  • excludedCommands(設定で除外)

    列挙したコマンド(docker *など)は、モデルの関与なしに自動でサンドボックスの外で動きます。ハンドラーは呼ばれません。

認証チェックを挟みたいコマンドはexcludedCommandsではなく、allowUnsandboxedCommands側に寄せます。Managed Agentsでは同様の判断を自分のプロセスに書く代わりに、クラウド環境(Environment)側のネットワーク設定として宣言します。

allowUnsandboxedCommandsを使うときのcanUseToolの実装例です。

import { query } from "@anthropic-ai/claude-agent-sdk";
 
for await (const message of query({
  prompt: "Deploy my application",
  options: {
    sandbox: {
      enabled: true,
      allowUnsandboxedCommands: true
    },
    permissionMode: "default",
    canUseTool: async (tool, input) => {
      if (tool === "Bash" && input.dangerouslyDisableSandbox) {
        if (isCommandAuthorized(input.command)) {
          return { behavior: "allow" as const, updatedInput: input };
        }
        return {
          behavior: "deny" as const,
          message: "Command not authorized for unsandboxed execution"
        };
      }
      return { behavior: "allow" as const, updatedInput: input };
    }
  }
})) {
  if ("result" in message) console.log(message.result);
}

excludedCommandsのパターンにはクセがあります。Claude Code本体のリファレンスでは、Bash(...)の権限ルールと同じ構文で、ワイルドカードの無い項目は完全一致です。dockerは引数なしのdockerだけに一致し、docker *と書けば引数付きにも一致します。

呼び出しがコマンドを連結している場合は、すべてのコマンドが一致しないと除外されません。npm ci && docker compose buildは、npm ciにも一致する項目がなければサンドボックス内のままです。リダイレクト、cd、$(...)を含む呼び出しも、全体がサンドボックス内に残ります。

bypassPermissionsのままプロンプトを残したいなら、Bash(dangerouslyDisableSandbox:true)のaskルールを追加します。Claude Code本体の説明では、このaskルールはautoモードとbypassPermissionsでもプロンプトを出し、一致するallowルールより優先されます。逆にBash(curl *)のようなallowルールがあると、サンドボックス外への再実行もそのルールで承認される点にも注意が要ります。

よくあるつまずき

  • ignoreViolationsの型が言語で違う: TypeScript版はRecord<string, string[]>で、コマンド部分文字列(全コマンド対象なら*)をキーに、無視したい違反テキストの部分文字列を値にします。例は{ "*": ["/etc/hosts"] }です。Python版はSandboxIgnoreViolationsという別型で、file: list[str]とnetwork: list[str]を持ちます。移植時にキーをそのままコピーすると型が合いません。サンドボックスはアクセスを引き続きブロックし、抑えるのは報告だけです。
  • allowManagedDomainsOnlyは設定元を選ぶ: managed設定に置くと、managed設定のallowedDomainsとWebFetch(domain:...)のallowルールだけが有効になります。TypeScript版のSDKはmanagedSettingsオプションで渡せます。Python版のリファレンスは、SDKオプション経由では効果がないと書いています。管理者が配備したmanaged設定がある端末では、最優先のmanaged設定がparentSettingsBehavior: 'merge'を指定していない限り、Claude CodeはmanagedSettingsオプションを無視します。
  • 設定を書いたのに反映されない: strictAllowlistとallowManagedDomainsOnlyはどちらも設定元に条件があります。入力元(ユーザー設定/managed設定/プロジェクト設定/SDKオプション)を疑います。
  • 確かめずに信じない: サンドボックスが効いているかは、Claudeにtouch ~/sandbox-probeを実行させて確かめられます。隔離内ならmacOSではOperation not permitted、LinuxとWSL2ではRead-only file systemで失敗します。自分で!プロンプトに打つと、通常はサンドボックスの外で動くので検証になりません。
  • enableWeakerNestedSandboxを通常運用で有効にする: 互換性のための弱いネストサンドボックスを有効化する設定で、両言語とも既定はfalseです。Linux専用で、bubblewrapが新しい/procをマウントできない権限なしのDockerコンテナ内で動かすための設定です。プロセス情報が露出します。通常の単層構成で有効にする理由はありません。

まとめ

TypeScriptとPythonで型の揃わない項目があるため、言語を移すときは設定を写すだけで済ませず、移した先の型と既定値を先に突き合わせる必要があります。本番へ出す前の最後の関門は、宣言した隔離が実際に効いているかの実測です。

設計の全体像(コンテナ・gVisor・VMによる多層防御)はAgent SDKの安全なデプロイ設計にあります。承認疲れをどう減らす設計かはClaude Codeのサンドボックス設計、セッションとハーネスの分離思想はManaged Agentsの設計思想で扱っています。

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