Claude Code's sandbox cannot start inside the client container #64

Closed
opened 2026-11-08 22:51:07 +01:00 by motoko · 0 comments
Collaborator

Where

bin/azkaban, _azk_build_podman_args, which runs the Linux container
with Podman's default masked /proc paths, and
docs/configuration.md § Nested sandboxes, which tells Claude Code
users to set sandbox.enableWeakerNestedSandbox to true. Seen at
cd974cb, from a claude client container, Claude Code 2.1.319.

What happens

With sandbox.enabled: true, every sandboxed Bash command fails before
it runs. With the default settings:

bwrap: Can't mount proc on /newroot/proc: Operation not permitted

With enableWeakerNestedSandbox: true, as the documentation says, and
also with allowNestedUserNamespaces: true:

apply-seccomp: write /proc/self/setgroups (nested userns is
capability-restricted; caller must provide CAP_SYS_ADMIN): Permission denied

Inside the container, /proc/self/mountinfo shows Podman's default
masks: /proc/acpi, /proc/kcore, /proc/keys, /proc/scsi,
/proc/sys, /proc/bus and the rest, as tmpfs, devtmpfs, or bind
mounts. The kernel refuses a fresh procfs mount in a child user
namespace while the parent's /proc has such mounts over it, which is
the first error, reproduced without Claude Code:

$ unshare -Urmp --mount-proc true
unshare: mount /proc failed: Operation not permitted

The weaker nested sandbox binds the container's /proc instead, as the
smoke test's probe does, and then applies seccomp from a second-level
user namespace. Writes to that namespace's setgroups and uid_map
through the bound outer /proc are refused, which is the second error,
also reproduced with bwrap alone:

$ bwrap --ro-bind / / --dev-bind /dev /dev --bind /proc /proc \
    --unshare-user --unshare-pid --unshare-net \
    sh -c 'unshare -Ur true'
unshare: write failed /proc/self/uid_map: Operation not permitted

The same unshare -Ur sh -c 'unshare -Ur true' succeeds outside
bwrap, so the container's user namespace limits are not the cause.
tests/e2e/smoke_images.sh passes its assert_bubblewrap check in
this situation, since that probe needs only one level of namespaces.

Scenario

A user turns on Claude Code's sandbox, from /sandbox or from its
settings, to hide credential files and variables from Bash commands.
Every command then fails at sandbox start-up, and Claude Code's
unsandboxed retry is the only way on, so the sandbox's protection is
lost and the documented setting gives no working path.

Smallest fix

Add --security-opt unmask=/proc/* to the Linux podman_args in
_azk_build_podman_args, which lets a child user namespace mount its
own /proc while the capability drop and the seccomp profile stay as
they are. Claude Code's regular sandbox then starts without
enableWeakerNestedSandbox, and the Nested sandboxes section can drop
that advice. A smoke probe that nests a second user namespace inside
bwrap, as the second reproduction above does, would catch a
regression. I could not confirm the fix from inside a running
container, since the masks are set at launch.

## Where `bin/azkaban`, `_azk_build_podman_args`, which runs the Linux container with Podman's default masked `/proc` paths, and `docs/configuration.md` § Nested sandboxes, which tells Claude Code users to set `sandbox.enableWeakerNestedSandbox` to `true`. Seen at cd974cb, from a `claude` client container, Claude Code 2.1.319. ## What happens With `sandbox.enabled: true`, every sandboxed Bash command fails before it runs. With the default settings: ``` bwrap: Can't mount proc on /newroot/proc: Operation not permitted ``` With `enableWeakerNestedSandbox: true`, as the documentation says, and also with `allowNestedUserNamespaces: true`: ``` apply-seccomp: write /proc/self/setgroups (nested userns is capability-restricted; caller must provide CAP_SYS_ADMIN): Permission denied ``` Inside the container, `/proc/self/mountinfo` shows Podman's default masks: `/proc/acpi`, `/proc/kcore`, `/proc/keys`, `/proc/scsi`, `/proc/sys`, `/proc/bus` and the rest, as tmpfs, devtmpfs, or bind mounts. The kernel refuses a fresh procfs mount in a child user namespace while the parent's `/proc` has such mounts over it, which is the first error, reproduced without Claude Code: ``` $ unshare -Urmp --mount-proc true unshare: mount /proc failed: Operation not permitted ``` The weaker nested sandbox binds the container's `/proc` instead, as the smoke test's probe does, and then applies seccomp from a second-level user namespace. Writes to that namespace's `setgroups` and `uid_map` through the bound outer `/proc` are refused, which is the second error, also reproduced with `bwrap` alone: ``` $ bwrap --ro-bind / / --dev-bind /dev /dev --bind /proc /proc \ --unshare-user --unshare-pid --unshare-net \ sh -c 'unshare -Ur true' unshare: write failed /proc/self/uid_map: Operation not permitted ``` The same `unshare -Ur sh -c 'unshare -Ur true'` succeeds outside `bwrap`, so the container's user namespace limits are not the cause. `tests/e2e/smoke_images.sh` passes its `assert_bubblewrap` check in this situation, since that probe needs only one level of namespaces. ## Scenario A user turns on Claude Code's sandbox, from `/sandbox` or from its settings, to hide credential files and variables from Bash commands. Every command then fails at sandbox start-up, and Claude Code's unsandboxed retry is the only way on, so the sandbox's protection is lost and the documented setting gives no working path. ## Smallest fix Add `--security-opt unmask=/proc/*` to the Linux `podman_args` in `_azk_build_podman_args`, which lets a child user namespace mount its own `/proc` while the capability drop and the seccomp profile stay as they are. Claude Code's regular sandbox then starts without `enableWeakerNestedSandbox`, and the Nested sandboxes section can drop that advice. A smoke probe that nests a second user namespace inside `bwrap`, as the second reproduction above does, would catch a regression. I could not confirm the fix from inside a running container, since the masks are set at launch.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
claudiomaradonna/azkaban#64
No description provided.