Skip to main content
When the bash tool executes a command it runs inside an OS-level sandbox configured from the agent’s workspace. This is filesystem + network enforcement at the kernel level — a separate concern from policy enforcement, which decides whether a tool may be invoked at all.

Runtime requirement

The bash tool depends on srt (Anthropic’s sandbox-runtime). It wraps each command in sandbox-exec + a Seatbelt profile (macOS) or bubblewrap + a network namespace + a seccomp filter (Linux).
npm install -g @anthropic-ai/sandbox-runtime
Supported on macOS and Linux only (Windows is unsupported). If srt is not on PATH, run_command raises SrtUnavailableError rather than falling back to unsandboxed execution — fail closed by design.

Configuration

Tune the boundary through LocalWorkspace:
from hexgate.runtime import LocalWorkspace

workspace = LocalWorkspace(
    root_dir="./project",
    allowed_domains=["api.github.com", "*.pypi.org"],
    extra_read_paths=["/etc/ssl"],
    extra_write_paths=["/tmp/build"],
    deny_write_paths=[".env"],
    allow_unix_sockets=["/var/run/docker.sock"],
    allow_local_binding=False,
    extra_env={"NODE_ENV": "test"},
)
KnobWhat it controlsDefault
root_dirWorkspace root; reads + writes allowed insiderequired
allowed_domainsHostnames the proxy forwards() — no egress
denied_domainsHostnames the proxy refuses()
extra_read_pathsRead-only paths beyond the workspace()
extra_write_pathsWritable paths beyond workspace + /tmp()
deny_write_pathsPaths the agent can never write to()
allow_unix_socketsUnix sockets the agent can connect()() — no IPC
allow_local_bindingWhether the agent can bind(127.0.0.1, …)False
extra_envEnv vars passed into the sandbox{}
Defaults add up to: no network egress, no IPC sockets, no localhost bind, reads allowed inside the workspace and on system paths but not $HOME, writes allowed only inside the workspace + /tmp. allow_unix_sockets and allow_local_binding exist because they’re the two ways traffic can leave the proxy lane (Unix-domain IPC and inbound localhost). Default-deny on both; opt in per-deployment when you actually need docker-socket access, a local dev server, etc.

Env scrubbing

The sandboxed child does not inherit the parent process’s environment. Only an explicit allowlist passes through:
  • PATH (curated baseline including /opt/homebrew/bin for Apple Silicon)
  • HOME (set to the workspace root, so cache writes land inside allowWrite)
  • TMPDIR, TERM
  • Locale keys: LANG, LC_ALL, LC_CTYPE, LC_COLLATE, LC_MESSAGES
  • Anything operator-supplied via extra_env
This means parent-process secrets — AWS_SECRET_ACCESS_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, GH_TOKEN, SSH_AUTH_SOCK, etc. — don’t leak into the agent. Tools that legitimately need credentials should receive them through extra_env, where you control exactly what’s passed.

Layering with policy + approval

LayerQuestionMechanism
PolicyIs this tool allowed at all for this caller’s role?adapter / enforce_policy(...)
ApprovalShould this specific call go ahead?enforce_policy(..., approval_handler=...)
SandboxWhat can the spawned shell actually do?OS-level via srt
Policy decides whether the bash tool is callable. The approval handler inspects each call gated by approval_required. The sandbox bounds reach if a call does run. They’re complementary — deploy whichever combination matches your threat model.

What the sandbox does NOT do

  • Resource limits. No CPU/memory/fork caps. A fork-bomb runs to completion. Use cgroups or ulimit if that matters.
  • Command-string semantics. srt sees sh -c "<command>" as an opaque arg. The sandbox bounds reach, not intent — rm -rf <workspace> is permitted because the workspace is in allowWrite.
  • Inside-sandbox actions. The sandbox stops the agent from exfiltrating a workspace file over the network or writing outside the boundary, but doesn’t reason about what the agent does within the boundary.