From 47f9519fc8a005558decbb453c9e6ad66f019417 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Wed, 22 Apr 2026 15:43:14 +0530 Subject: [PATCH] feat: add missing sandbox type definitions for CLI settings parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Python SDK's sandbox configuration types were significantly behind the TypeScript SDK and the documented CLI settings schema. This commit brings them to parity by adding types and fields that the CLI already supports but the Python SDK did not expose. Changes: 1. New SandboxFilesystemConfig TypedDict: - allowWrite: additional writable paths for sandboxed commands - denyWrite: paths where sandboxed commands cannot write - denyRead: paths where sandboxed commands cannot read - allowRead: re-allow reading specific paths within denyRead regions - allowManagedReadPathsOnly: restrict to managed allowRead entries 2. Extended SandboxNetworkConfig with missing fields: - allowMachLookup: XPC/Mach service names (macOS, supports * prefix) - allowedDomains: outbound domains (supports wildcards like *.example.com) - deniedDomains: blocked domains (takes precedence over allowedDomains) - allowManagedDomainsOnly: restrict to managed allowedDomains entries 3. Extended SandboxSettings with missing fields: - failIfUnavailable: exit with error if sandbox deps are unavailable - filesystem: SandboxFilesystemConfig for filesystem restrictions - enableWeakerNetworkIsolation: allow system TLS access (macOS only) 4. Exported SandboxFilesystemConfig in public API (__init__.py / __all__) 5. Added 3 new tests: - test_sandbox_filesystem_config: verifies filesystem config passthrough - test_sandbox_network_domains: verifies domain-based network config - test_sandbox_new_settings_fields: verifies failIfUnavailable and enableWeakerNetworkIsolation All new fields use total=False (optional), so this is fully backward compatible with existing code. Relates to #861 — users in containerized environments (e.g. AWS Bedrock AgentCore) need filesystem and network config options to work around bwrap sandbox DNS resolution issues where /etc/hosts is not mounted. Co-Authored-By: Claude Opus 4.6 --- src/claude_agent_sdk/__init__.py | 2 + src/claude_agent_sdk/types.py | 45 ++++++++++++++++- tests/test_transport.py | 87 ++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/claude_agent_sdk/__init__.py b/src/claude_agent_sdk/__init__.py index e88971d0..0f2d0f17 100644 --- a/src/claude_agent_sdk/__init__.py +++ b/src/claude_agent_sdk/__init__.py @@ -98,6 +98,7 @@ RateLimitStatus, RateLimitType, ResultMessage, + SandboxFilesystemConfig, SandboxIgnoreViolations, SandboxNetworkConfig, SandboxSettings, @@ -638,6 +639,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any: # Sandbox support "SandboxSettings", "SandboxNetworkConfig", + "SandboxFilesystemConfig", "SandboxIgnoreViolations", # MCP Server Support "create_sdk_mcp_server", diff --git a/src/claude_agent_sdk/types.py b/src/claude_agent_sdk/types.py index 83aae5cb..8d518a50 100644 --- a/src/claude_agent_sdk/types.py +++ b/src/claude_agent_sdk/types.py @@ -779,6 +779,25 @@ class SdkPluginConfig(TypedDict): # Sandbox configuration types +class SandboxFilesystemConfig(TypedDict, total=False): + """Filesystem configuration for sandbox. + + Attributes: + allowWrite: Additional paths where sandboxed commands can write. + denyWrite: Paths where sandboxed commands cannot write. + denyRead: Paths where sandboxed commands cannot read. + allowRead: Re-allow reading specific paths within denyRead regions. + allowManagedReadPathsOnly: When True, only allowRead entries from managed + settings are respected. Default: False + """ + + allowWrite: list[str] + denyWrite: list[str] + denyRead: list[str] + allowRead: list[str] + allowManagedReadPathsOnly: bool + + class SandboxNetworkConfig(TypedDict, total=False): """Network configuration for sandbox. @@ -786,6 +805,13 @@ class SandboxNetworkConfig(TypedDict, total=False): allowUnixSockets: Unix socket paths accessible in sandbox (e.g., SSH agents). allowAllUnixSockets: Allow all Unix sockets (less secure). allowLocalBinding: Allow binding to localhost ports (macOS only). + allowMachLookup: XPC/Mach service names accessible in sandbox + (macOS only, supports ``*`` prefix matching). + allowedDomains: Outbound network domains allowed in sandbox + (supports wildcards like ``*.example.com``). + deniedDomains: Blocked domains (takes precedence over allowedDomains). + allowManagedDomainsOnly: When True, only allowedDomains entries from managed + settings are respected. Default: False httpProxyPort: HTTP proxy port if bringing your own proxy. socksProxyPort: SOCKS5 proxy port if bringing your own proxy. """ @@ -793,6 +819,10 @@ class SandboxNetworkConfig(TypedDict, total=False): allowUnixSockets: list[str] allowAllUnixSockets: bool allowLocalBinding: bool + allowMachLookup: list[str] + allowedDomains: list[str] + deniedDomains: list[str] + allowManagedDomainsOnly: bool httpProxyPort: int socksProxyPort: int @@ -823,14 +853,19 @@ class SandboxSettings(TypedDict, total=False): Attributes: enabled: Enable bash sandboxing (macOS/Linux only). Default: False + failIfUnavailable: Exit with error if sandbox dependencies are not available + when enabled. Default: False autoAllowBashIfSandboxed: Auto-approve bash commands when sandboxed. Default: True excludedCommands: Commands that should run outside the sandbox (e.g., ["git", "docker"]) allowUnsandboxedCommands: Allow commands to bypass sandbox via dangerouslyDisableSandbox. When False, all commands must run sandboxed (or be in excludedCommands). Default: True network: Network configuration for sandbox. + filesystem: Filesystem configuration for sandbox. ignoreViolations: Violations to ignore. enableWeakerNestedSandbox: Enable weaker sandbox for unprivileged Docker environments (Linux only). Reduces security. Default: False + enableWeakerNetworkIsolation: Allow system TLS service access for Go-based tools + with MITM proxy (macOS only). Reduces security. Default: False Example: ```python @@ -840,19 +875,27 @@ class SandboxSettings(TypedDict, total=False): "excludedCommands": ["docker"], "network": { "allowUnixSockets": ["/var/run/docker.sock"], - "allowLocalBinding": True + "allowLocalBinding": True, + "allowedDomains": ["github.com", "*.npmjs.org"] + }, + "filesystem": { + "allowWrite": ["/tmp/build"], + "denyRead": ["~/.aws/credentials"] } } ``` """ enabled: bool + failIfUnavailable: bool autoAllowBashIfSandboxed: bool excludedCommands: list[str] allowUnsandboxedCommands: bool network: SandboxNetworkConfig + filesystem: SandboxFilesystemConfig ignoreViolations: SandboxIgnoreViolations enableWeakerNestedSandbox: bool + enableWeakerNetworkIsolation: bool # Content block types diff --git a/tests/test_transport.py b/tests/test_transport.py index f7091b79..e6363418 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -1507,6 +1507,93 @@ def test_sandbox_network_config(self): assert network["httpProxyPort"] == 8080 assert network["socksProxyPort"] == 8081 + def test_sandbox_filesystem_config(self): + """Test sandbox with filesystem configuration.""" + import json + + from claude_agent_sdk import SandboxSettings + + sandbox: SandboxSettings = { + "enabled": True, + "filesystem": { + "allowWrite": ["/tmp/build", "~/.kube"], + "denyRead": ["~/.aws/credentials"], + "allowRead": ["/etc/hosts", "/etc/resolv.conf"], + }, + } + + transport = SubprocessCLITransport( + prompt="test", + options=make_options(sandbox=sandbox), + ) + + cmd = transport._build_command() + settings_idx = cmd.index("--settings") + settings_value = cmd[settings_idx + 1] + + parsed = json.loads(settings_value) + fs = parsed["sandbox"]["filesystem"] + + assert fs["allowWrite"] == ["/tmp/build", "~/.kube"] + assert fs["denyRead"] == ["~/.aws/credentials"] + assert fs["allowRead"] == ["/etc/hosts", "/etc/resolv.conf"] + + def test_sandbox_network_domains(self): + """Test sandbox with domain-based network configuration.""" + import json + + from claude_agent_sdk import SandboxSettings + + sandbox: SandboxSettings = { + "enabled": True, + "network": { + "allowedDomains": ["github.com", "*.npmjs.org"], + "deniedDomains": ["evil.com"], + "allowManagedDomainsOnly": False, + }, + } + + transport = SubprocessCLITransport( + prompt="test", + options=make_options(sandbox=sandbox), + ) + + cmd = transport._build_command() + settings_idx = cmd.index("--settings") + settings_value = cmd[settings_idx + 1] + + parsed = json.loads(settings_value) + network = parsed["sandbox"]["network"] + + assert network["allowedDomains"] == ["github.com", "*.npmjs.org"] + assert network["deniedDomains"] == ["evil.com"] + assert network["allowManagedDomainsOnly"] is False + + def test_sandbox_new_settings_fields(self): + """Test sandbox with failIfUnavailable and enableWeakerNetworkIsolation.""" + import json + + from claude_agent_sdk import SandboxSettings + + sandbox: SandboxSettings = { + "enabled": True, + "failIfUnavailable": True, + "enableWeakerNetworkIsolation": True, + } + + transport = SubprocessCLITransport( + prompt="test", + options=make_options(sandbox=sandbox), + ) + + cmd = transport._build_command() + settings_idx = cmd.index("--settings") + settings_value = cmd[settings_idx + 1] + + parsed = json.loads(settings_value) + assert parsed["sandbox"]["failIfUnavailable"] is True + assert parsed["sandbox"]["enableWeakerNetworkIsolation"] is True + def test_build_command_with_tools_array(self): """Test building CLI command with tools as array of tool names.""" transport = SubprocessCLITransport(