[BUG] Bash sandbox fails to initialize on Ubuntu 24.04+ - conflict with the distro's default bwrap-userns-restrict AppArmor profile (not a Claude Code regression)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
On a stock Ubuntu 26.04 host with bubblewrap and socat installed exactly as the
sandboxing docs describe, the Bash sandbox cannot start. Every Bash tool call fails
during sandbox setup, before the command itself ever runs, leaving the Bash tool entirely
unusable.
To be clear up front: this is not a regression in Claude Code. Claude Code 2.1.234
behaves the same as earlier versions here. The breaking change comes from Ubuntu's
progressive user-namespace hardening:
- Ubuntu 24.04 introduced
kernel.apparmor_restrict_unprivileged_userns = 1, which
blocks unprivileged processes from creating user namespaces. The Claude Code docs already
cover this and recommend adding an AppArmor profile for bwrap.
- Later Ubuntu releases (26.04 here) now ship and enable by default a profile at
/etc/apparmor.d/bwrap-userns-restrict. This moves the goalposts: bwrap itself is now
permitted to create a user namespace, so the documented check and remedy look satisfied -
but the profile stacks every process bwrap executes into a child profile,
unpriv_bwrap, which contains audit deny capability,.
Claude Code's sandbox then tries to create a nested user namespace inside bwrap (itsapply-seccomp step). That needs CAP_SYS_ADMIN, which unpriv_bwrap has just stripped
by design. The profile's own comment confirms the intent:
bwrapis allowed access to user namespaces and capabilities within the user namespace, but its children do not have capabilities, blockingbwrapfrom being able to be used to arbitrarily by-pass the user namespace restrictions.
So Ubuntu deliberately allows exactly one level of user-namespace creation and forbids
nesting a second - and Claude Code's sandbox requires that second level.
Two things make the impact worse than a plain incompatibility:
- The documented AppArmor fix no longer applies. The docs suggest creating
profile bwrap /usr/bin/bwrap flags=(unconfined) { userns, ... }, but
bwrap-userns-restrict already declares profile bwrap /usr/bin/bwrap. Two
declarations for the same binary don't compose - the shipped profile has to be disabled
first - so following the docs doesn't fix it.
- With Strict sandbox mode active, there is no fallback. The unsandboxed-retry escape
hatch is suppressed and dangerouslyDisableSandbox: true is ignored (correctly, per the
docs). The result is a completely non-functional Bash tool, reported only as a low-level
error that never mentions AppArmor, bwrap, or sandbox misconfiguration. Recovering
required editing settings through the file tools, since Bash was dead.
What Should Happen?
Either of the following would be acceptable:
- The sandbox initializes and Bash commands run confined, on a stock Ubuntu install with
the documented dependencies present; or
- Claude Code detects that it cannot create the nested namespace and reports a clear,
actionable diagnostic naming AppArmor, the unpriv_bwrap profile, and the required
capability - ideally at startup or in /doctor / the /sandbox panel, rather than as a
per-call failure with no path forward.
What should not happen is the current outcome: an opaque apply-seccomp error on every
Bash invocation, with no fallback and no indication of the cause.
Error Messages/Logs
Every Bash tool call fails with one of the following, depending on configuration.
Default configuration (`{"sandbox": {"enabled": true}}`):
apply-seccomp: write /proc/self/setgroups (nested userns is capability-restricted; caller must provide CAP_SYS_ADMIN): Permission denied
With `"enableWeakerNestedSandbox": true` - the setting takes effect and changes the code
path, but does not resolve the problem:
apply-seccomp: unshare(CLONE_NEWUSER): Invalid argument
**Kernel audit log** - the decisive evidence, timestamped to the exact moments of the
failed Bash calls (PIDs redacted):
kernel: audit: type=1400 audit(...): apparmor="DENIED" operation="capable" class="cap" \
profile="unpriv_bwrap" pid=<pid> comm="3" capability=21 capname="sys_admin"
`capability=21` is `CAP_SYS_ADMIN`; the profile is `unpriv_bwrap`.
**The relevant part of the shipped Ubuntu profile** (`/etc/apparmor.d/bwrap-userns-restrict`):
profile bwrap /usr/bin/bwrap flags=(attach_disconnected,mediate_deleted) {
allow capability, # bwrap itself: capabilities allowed
allow userns,
...
allow pix /** -> &bwrap//&unpriv_bwrap, # children stacked into unpriv_bwrap
}
profile unpriv_bwrap flags=(attach_disconnected,mediate_deleted) {
allow userns,
...
audit deny capability, # ...where all capabilities are stripped
}
**Both profiles confirmed loaded and enforcing:**
$ sudo aa-status | grep -E 'bwrap|unpriv_bwrap'
bwrap
unpriv_bwrap
/usr/bin/bwrap (<pid>) bwrap
**Related but distinct**, worth noting since it is easy to hit: if
`sandbox.filesystem.denyRead` includes the filesystem root (e.g. `["/**", "~/**"]`), the
sandbox hides `/bin`, `/usr`, and `/lib` from the mount view and Bash fails with:
bwrap: execvp /bin/bash: No such file or directory
This one is arguably user error, but the message gives no hint that a `denyRead` rule
caused it.
Steps to Reproduce
- Install Ubuntu 24.04 or later (reproduced on 26.04) with the default AppArmor policy -
i.e. kernel.apparmor_restrict_unprivileged_userns = 1 and the bwrap-userns-restrict
profile loaded. Both are distribution defaults; no hardening was added by hand.
sudo apt-get install bubblewrap socat- Confirm the dependencies are healthy -
/sandboxshows no Dependencies tab, and a
standalone bubblewrap isolation test works correctly:
``bash``
bwrap --unshare-all --die-with-parent --ro-bind /tmp /tmp cat /etc/hostname
# correctly fails with "execvp: No such file or directory" - proving the mount
# namespace really is isolated, so bwrap itself is functional
- Enable the sandbox in
.claude/settings.json:
``json``
{ "sandbox": { "enabled": true } }
- Restart Claude Code and ask it to run any Bash command, e.g.
echo ok.
Result: the command fails with the apply-seccomp error above. Reproduced across
several full process restarts.
Claude Model
Opus
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.234
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
Environment
| Component | Version |
|---|---|
| Claude Code | 2.1.234 |
| OS | Ubuntu 26.04 LTS |
| Kernel | 7.0.0-29-generic |
| Node.js | v24.16.0 |
| bubblewrap | 0.11.1 (0.11.1-1ubuntu0.1) |
| socat | 1.8.1.1 |
| apparmor_parser | 5.0.0~beta1 |
kernel.unprivileged_userns_clone = 1
kernel.apparmor_restrict_unprivileged_userns = 1
Running as a normal unprivileged user - not root, not in a container, on bare metal.
Hypotheses ruled out
Listing these so this isn't triaged as one of the more common setup problems:
| Hypothesis | Result |
|---|---|
| bubblewrap not installed | Installed (0.11.1), on PATH |
| socat not installed | Installed (1.8.1.1); installing it did not fix the issue |
| Optional @anthropic-ai/sandbox-runtime seccomp filter missing | Not installed globally; apply-seccomp is bundled in the Claude Code binary and not resolvable via which or find |
| bwrap can't create user namespaces at all | False - standalone isolation test succeeds (step 3 above) |
| AppArmor blocks bwrap itself | False - bwrap has its own profile with allow capability and allow userns; only its children are restricted |
| Process already inside a user namespace (e.g. via su) | False - invoking shell and the claude process share the same host userns inode user:[........837] |
| Running inside a container | False - bare metal |
| Stale session/config state | False - reproduced across multiple full restarts |
| Multithreaded process can't call unshare(CLONE_NEWUSER) | Plausible for the EINVAL variant specifically, but the audit log identifies the AppArmor capability denial as the primary cause |
Workaround
Adding a rule-priority override in the distribution's local/ include directory clears the
capability denial without replacing the shipped profile. The shipped unpriv_bwrap block
already ends with include if exists <local/unpriv_bwrap>. Requires AppArmor ≥ 4 forpriority= support:
sudo tee /etc/apparmor.d/local/unpriv_bwrap > /dev/null <<'EOF'
priority=1 capability sys_admin,
EOF
sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict
This is a real security trade-off, not a free fix. It re-opens the nested-user-namespace
escalation path Ubuntu's hardening was written to close, for all bwrap consumers on the
machine (Flatpak, GNOME image loaders, and others), not just Claude Code. It should not be
presented to users as a routine setup step without that caveat. It is narrower than theflags=(unconfined) profile the docs currently suggest, which would remove capability
confinement for bwrap children entirely.
Documentation issues
- The recommended profile collides with the shipped one. Both declare
profile bwrap /usr/bin/bwrap; the distro profile must be disabled for the docs' profile
to apply. The docs don't mention this.
- It's broader than necessary.
flags=(unconfined)removes confinement for allbwrap
children system-wide, when the actual blocker is a single capability.
- The gating check is now misleading. The docs say to run
sysctl kernel.apparmor_restrict_unprivileged_userns and apply the profile if it returns
1. It does return 1 here - but the documented remedy is no longer the right one,
because Ubuntu now ships a bwrap-specific profile that already grants userns to
bwrap. The failure has moved down a level, to the stacked child profile.
Suggested improvements
- Detect this condition and surface a diagnostic naming AppArmor,
unpriv_bwrap, and the
required capability - in /doctor or the /sandbox Dependencies tab - instead of the raw
apply-seccomp error.
- Fail at startup rather than per-call. A sandbox that cannot initialize under Strict mode
bricks Bash with no recovery path from inside the session.
- Update the AppArmor documentation for Ubuntu releases shipping
bwrap-userns-restrict,
including the local/unpriv_bwrap override and its trade-off.
- Consider whether the nested user namespace can be avoided - the outer
bwrapnamespace
already establishes the isolation boundary, and dropping the nesting requirement would
make Claude Code compatible with Ubuntu's default hardening rather than requiring users to
weaken it.
Possibly separate issue
sandbox.allowUnsandboxedCommands: true set in project-level .claude/settings.json did not
appear to take effect: /sandbox continued to report Strict sandbox mode, and an explicitdangerouslyDisableSandbox: true call still failed identically. This may be correct
precedence behavior (a higher-precedence source enforcing strict mode) or a settings-scope
bug - it was not isolated further, and is reported here only as an observation.