From c5f67644709a5b51c9cd17076f2ff0af70b77a01 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Sat, 18 Apr 2026 23:31:07 +0200 Subject: [PATCH] feat(sandbox): enable Claude Code built-in sandbox for Bash + WebFetch egress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #10. Until now the sandbox container had unrestricted outbound NAT on sandbox-net — a compromised agent could curl arbitrary hosts, pip/npm install malicious packages, or exfiltrate files via WebFetch. The anthropic-proxy path-allowlist (#6) guarded only the credential-injection proxy, not general egress. Two layers added, both shipped as managed (enterprise) settings so user and project settings cannot downgrade them: - sandbox.* → bubblewrap + local proxy gating Bash subprocesses (and all their children: pip, npm, git, curl). enableWeakerNestedSandbox=true because we run inside Docker without privileged user namespaces; accepted because the Docker container boundary is unchanged and this adds new egress filtering that previously did not exist. - permissions.* → deny-by-default WebFetch with explicit domain allows (WebFetch bypasses the OS sandbox and needs its own allowlist). failIfUnavailable=true + allowUnsandboxedCommands=false close the two silent-fallback paths: if bubblewrap/socat install breaks, the container fails loud instead of serving unsandboxed bash; and the dangerouslyDisableSandbox escape hatch is disabled so sandbox violations can't be bypassed via a permission prompt in our unattended deployment. Co-Authored-By: Claude Opus 4.7 (1M context) --- sandbox/Dockerfile | 16 ++++++++++++++++ sandbox/managed-settings.json | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 sandbox/managed-settings.json diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index bb79d35..cafae20 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -13,6 +13,22 @@ ARG CLAUDE_CODE_VERSION=2.1.114 USER root RUN npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \ && claude --version + +# bubblewrap + socat are the Linux backends for Claude Code's built-in +# `/sandbox` feature. Without them, Claude falls back to unsandboxed bash — +# our managed-settings.json sets failIfUnavailable=true so that silent +# degradation becomes a hard error instead. +RUN apt-get update \ + && apt-get install -y --no-install-recommends bubblewrap socat \ + && rm -rf /var/lib/apt/lists/* + +# Managed (enterprise) settings live at the system-wide path and take +# precedence over per-user and per-project settings. This is how we pin +# Bash egress + WebFetch allowlists: users can extend via their own +# settings, but cannot downgrade these rules. +COPY managed-settings.json /etc/claude-code/managed-settings.json +RUN chmod 0644 /etc/claude-code/managed-settings.json + COPY cleanup.sh /opt/cleanup.sh RUN chmod +x /opt/cleanup.sh USER user diff --git a/sandbox/managed-settings.json b/sandbox/managed-settings.json new file mode 100644 index 0000000..167b771 --- /dev/null +++ b/sandbox/managed-settings.json @@ -0,0 +1,35 @@ +{ + "sandbox": { + "enabled": true, + "failIfUnavailable": true, + "allowUnsandboxedCommands": false, + "allowManagedDomainsOnly": true, + "enableWeakerNestedSandbox": true, + "network": { + "allowedDomains": [ + "pypi.org", + "files.pythonhosted.org", + "registry.npmjs.org", + "github.com", + "api.github.com", + "raw.githubusercontent.com", + "objects.githubusercontent.com", + "codeload.github.com", + "anthropic-proxy" + ] + } + }, + "permissions": { + "deny": [ + "WebFetch" + ], + "allow": [ + "WebFetch(domain:github.com)", + "WebFetch(domain:raw.githubusercontent.com)", + "WebFetch(domain:docs.anthropic.com)", + "WebFetch(domain:code.claude.com)", + "WebFetch(domain:pypi.org)", + "WebFetch(domain:registry.npmjs.org)" + ] + } +}