Skip to content

fix(transport): default an enabled sandbox to failIfUnavailable - #1209

Open
chintan-diwakar wants to merge 1 commit into
anthropics:mainfrom
chintan-diwakar:fix/sandbox-fail-closed
Open

fix(transport): default an enabled sandbox to failIfUnavailable#1209
chintan-diwakar wants to merge 1 commit into
anthropics:mainfrom
chintan-diwakar:fix/sandbox-fail-closed

Conversation

@chintan-diwakar

Copy link
Copy Markdown

Summary

_build_settings_value (_internal/transport/subprocess_cli.py:513) passes options.sandbox to the CLI verbatim. The CLI reads sandbox.failIfUnavailable as ?? false, so when a caller asks for a sandbox and it cannot start (missing dependencies, unsupported platform, unprivileged container) the CLI prints a warning and then runs every bash command unsandboxed. The caller silently loses the isolation they asked for.

The TypeScript SDK closes this. Its settings merge injects failIfUnavailable: true whenever sandbox.enabled is true and the caller did not set it, in sdk.mjs of @anthropic-ai/claude-agent-sdk@0.3.233 (the release paired with bundled CLI 2.1.233):

let n = t.enabled === !0 && t.failIfUnavailable === void 0 ? { ...t, failIfUnavailable: !0 } : t

So identical options fail closed in TypeScript and fail open in Python. This makes Python match.

The CLI does not apply this default itself when it parses --settings, so the SDK is the only place it can be applied.

Behavior change

This is deliberate and worth calling out. If you set sandbox={"enabled": True} on a host where the sandbox cannot start, the run previously warned and continued unsandboxed, and now fails instead. The CLI's error names the opt-out:

Sandbox required but unavailable: <reason>. Set sandbox.failIfUnavailable=false to allow unsandboxed execution.

Setting failIfUnavailable: False explicitly restores the old warn-and-continue behavior. TypeScript SDK users already live under the fail-closed default today.

Fix

  • src/claude_agent_sdk/_internal/transport/subprocess_cli.py: default failIfUnavailable to True when sandbox.enabled is True and the caller did not set it. An explicit value is preserved, a sandbox that is not enabled is untouched, and the caller's dict is copied rather than mutated. The enabled is True check mirrors the TypeScript === true, so a truthy non-True value is passed through unchanged.
  • src/claude_agent_sdk/types.py: add failIfUnavailable: bool to the SandboxSettings TypedDict so an explicit override type-checks. total=False, so this is non-breaking.
  • tests/test_transport.py: regression tests, plus an update to test_build_command_sandbox_minimal, whose pinned assertion encodes the old fail-open payload.

Sandboxes enabled only through a settings JSON string, with no sandbox option, get no injection. That matches the TypeScript merge, which returns early when the sandbox option is absent.

Tests

  • test_sandbox_enabled_defaults_to_fail_closed fails on main and passes with the fix, along with the updated test_build_command_sandbox_minimal.
  • test_sandbox_explicit_fail_if_unavailable_is_preserved, test_sandbox_not_enabled_gets_no_fail_if_unavailable and test_sandbox_options_dict_is_not_mutated pin the boundaries so the default cannot over-inject.
  • Full suite: python -m pytest tests/: 1370 passed, 5 skipped, same baseline as main plus the new tests.
  • python -m ruff check src/ tests/, ruff format --check and python -m mypy src/: clean.

Used AI assistance; reviewed and tested by me.

The CLI reads sandbox.failIfUnavailable as `?? false`, so when a caller asks
for a sandbox and it cannot start (missing dependencies, unsupported
platform, unprivileged container), the CLI prints a warning and then runs
every bash command unsandboxed. The Python transport passed options.sandbox
through verbatim, so callers silently lost the isolation they asked for.

The TypeScript SDK closes this: its settings merge injects
failIfUnavailable: true whenever sandbox.enabled is true and the caller did
not set it. Identical options therefore fail closed in TypeScript and fail
open in Python.

Default failIfUnavailable to true for an enabled sandbox. An explicit value
is always preserved, a sandbox that is not enabled is untouched, and the
caller's dict is copied rather than mutated.

Add failIfUnavailable to the SandboxSettings TypedDict so an explicit
override type-checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant