[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)

Status Open
Reported on v2.1.234
Maintainer reply None cached
Activity 0 comments · opened Aug 18, 2026

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 (its
apply-seccomp step). That needs CAP_SYS_ADMIN, which unpriv_bwrap has just stripped
by design. The profile's own comment confirms the intent:

bwrap is allowed access to user namespaces and capabilities within the user namespace, but its children do not have capabilities, blocking bwrap from 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:

  1. 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.

  1. 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:

  1. The sandbox initializes and Bash commands run confined, on a stock Ubuntu install with

the documented dependencies present; or

  1. 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

  1. 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.

  1. sudo apt-get install bubblewrap socat
  2. Confirm the dependencies are healthy - /sandbox shows 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
``

  1. Enable the sandbox in .claude/settings.json:

``json
{ "sandbox": { "enabled": true } }
``

  1. 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 for
priority= 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 the
flags=(unconfined) profile the docs currently suggest, which would remove capability
confinement for bwrap children entirely.

Documentation issues

  1. 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.

  1. It's broader than necessary. flags=(unconfined) removes confinement for all bwrap

children system-wide, when the actual blocker is a single capability.

  1. 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

  1. 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.

  1. 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.

  1. Update the AppArmor documentation for Ubuntu releases shipping bwrap-userns-restrict,

including the local/unpriv_bwrap override and its trade-off.

  1. Consider whether the nested user namespace can be avoided - the outer bwrap namespace

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 explicit
dangerouslyDisableSandbox: 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.

View original on GitHub ↗