DEV Community

Cover image for Ubuntu 24.04 Restricts User Namespaces. That's Why Your Agent's Sandbox Won't Start
Gabriel Anhaia
Gabriel Anhaia

Posted on Edited on AI-assisted

Ubuntu 24.04 Restricts User Namespaces. That's Why Your Agent's Sandbox Won't Start


You install bubblewrap and socat on a fresh Ubuntu 24.04 machine, open Claude Code, and turn the sandbox on with /sandbox. Every package it asked for is there. The sandbox still won't start, and the agent's shell commands fall back to the regular permission prompts.

No package is missing. A kernel setting is on, and Ubuntu turns it on by default. Since Claude Code 2.1.310, the tool tells you so: /sandbox and the sandbox startup warning now "name Ubuntu's AppArmor restriction on user namespaces, and how to lift it, when it keeps bubblewrap from starting the sandbox." According to Anthropic's sandbox docs, Claude Code didn't check for this restriction at startup before v2.1.310, so /sandbox didn't report it.

The fix that most threads paste turns the protection off for the whole machine. There is a narrower one. It is narrower by less than it looks, and Ubuntu's own bug tracker explains why.

Why an agent sandbox needs user namespaces at all

On Linux and WSL2, Claude Code's Bash sandbox runs on bubblewrap, which the docs call "the unprivileged sandboxing tool that enforces filesystem isolation", plus socat to relay traffic through the sandbox's proxy. Each sandboxed command "runs in a separate network namespace that has no connection to your network."

bubblewrap runs unprivileged. Its README puts it plainly: user namespaces let unprivileged users use container features, and "Bubblewrap uses these to build the sandbox, allowing any user to use the tool." The old setuid mode has been removed. bubblewrap gets no root from anywhere. It gets its power from a new user namespace.

The user_namespaces(7) man page describes the trick: a process can have a normal unprivileged user ID outside the namespace and user ID 0 inside it, with "full privileges for operations inside the user namespace." Those capabilities only reach "resources governed by that namespace." That is enough to mount a private view of the filesystem and bring up an empty network namespace, which is what an agent sandbox needs.

That same property makes user namespaces a kernel attack surface. Canonical's announcement of the restriction says they "expose kernel interfaces that are normally restricted to processes with privileged capabilities (root) to use by unprivileged users." It cites a Google report in which "44% of the exploits they saw required unprivileged user namespaces as part of their exploit chain." The agent sandbox and the exploit chain both depend on the same kernel feature.

What Ubuntu 24.04 actually restricts

The restriction arrived in Ubuntu 23.10 and is on by default from 24.04 LTS. The tracking bug is Launchpad #2046477, "Enable unprivileged user namespace restrictions by default". The switch is the sysctl kernel.apparmor_restrict_unprivileged_userns, set to 1.

It does not stop a program from creating a user namespace. The 24.04 release notes say a default AppArmor profile "allows the use of user namespaces for unprivileged and unconfined applications" but "will deny the subsequent use of any capabilities within the user namespace." A program gets the full feature back only when its own AppArmor profile carries a userns, rule, which is how Ubuntu ships profiles for apps such as Google Chrome.

So bubblewrap creates its namespace and finds itself root in name only. The man page says writing a namespace's uid_map needs CAP_SETUID in that namespace. With the capabilities denied, bubblewrap fails at its first privileged step.

A small sandbox box with a figure inside wearing an empty crown, blocked by a large shield-shaped gate standing in front of it
The namespace gets created. The capabilities inside it don't come with it.

Reproduce it on a 24.04 VM

I haven't run these on a VM myself. The outputs below are quoted from Qualys and from Anthropic's docs, and the commands are the ones they describe. Start by checking the switch:

sysctl kernel.apparmor_restrict_unprivileged_userns
Enter fullscreen mode Exit fullscreen mode

Anthropic's docs say kernel.apparmor_restrict_unprivileged_userns = 1 means the restriction is on. Any other output means the restriction isn't the cause, and /feedback inside Claude Code is the way to report it.

Next, ask for a user namespace with root mapped inside it, the way Qualys did as an unprivileged user on a default 24.04 install:

unshare -U -r -m /bin/sh
Enter fullscreen mode Exit fullscreen mode

Qualys got this back:

unshare: write failed /proc/self/uid_map: Operation not permitted
Enter fullscreen mode Exit fullscreen mode

That is the uid_map write from the man page, refused. Now the same thing through bubblewrap, with a user and a network namespace like Claude Code asks for:

bwrap --unshare-user --unshare-net \
  --ro-bind / / true
Enter fullscreen mode Exit fullscreen mode

The sandbox docs list the two bubblewrap errors you get while the restriction is on: bwrap: setting up uid map: Permission denied and bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted.

By default, the docs say, when the sandbox can't start, "Claude Code runs commands without sandboxing." Until you lift the restriction, commands run "under the regular permission flow": your allow rules and permission mode decide what runs, and anything they don't cover waits for your approval. If you set sandbox.failIfUnavailable to true, Claude Code refuses to start instead.

Fix one: the sysctl everyone pastes

sudo sysctl -w \
  kernel.apparmor_restrict_unprivileged_userns=0
Enter fullscreen mode Exit fullscreen mode

Anthropic's docs describe exactly what that does: it "lifts the restriction for every program on the machine and lasts until you reboot." To keep it, the 24.04 release notes give a file, followed by a reboot:

echo 'kernel.apparmor_restrict_unprivileged_userns=0' \
  | sudo tee /etc/sysctl.d/60-apparmor-namespace.conf
Enter fullscreen mode Exit fullscreen mode

This puts the machine back to pre-24.04 behavior for every process: your agent's sandbox, the commands the agent runs outside it, and any other program on the box. The release notes are blunt about the trade: it "does not mitigate against kernel exploits that abuse the unprivileged user namespaces feature."

On a throwaway VM that only exists to run agents, that may be a fine trade. On a laptop that also holds your SSH keys and your browser profile, it's a big switch to flip for one tool.

Fix two: a profile for bwrap only

The docs offer a narrower route, an AppArmor profile that grants userns to the bubblewrap binary and to nothing else:

sudo tee /etc/apparmor.d/bwrap > /dev/null <<'EOF'
abi <abi/4.0>,
include <tunables/global>

profile bwrap /usr/bin/bwrap flags=(unconfined) {
  userns,
  include if exists <local/bwrap>
}
EOF
sudo systemctl reload apparmor
Enter fullscreen mode Exit fullscreen mode

flags=(unconfined) leaves bwrap otherwise unrestricted, and userns, gives it back its capabilities inside the namespace. The sysctl stays at 1, so a random binary calling unshare still gets the empty namespace. The profile attaches by path, so check which bwrap you run with command -v bwrap. The docs say Claude Code runs the first bwrap on PATH unless managed settings set sandbox.bwrapPath, and that for any path other than /usr/bin/bwrap you should use the sysctl instead.

Two ways through a wall: a lever that lowers the whole wall, and a single small door cut for one box that other shapes also pass through
The sysctl lowers the wall for everything. The profile cuts one door, and anything can walk through it.

Ubuntu chose not to ship this profile. The Launchpad bug notes that the bwrap binary is installed by default on Ubuntu Desktop 24.04, and that no profile is planned for it because bwrap can launch arbitrary binaries in a new user namespace and so bypass the restriction. unshare and setns were left out for the same reason. With the profile in place, any local program that wants a full user namespace can ask /usr/bin/bwrap for one.

It still beats the sysctl. The setting survives reboots, and a full namespace now comes through one known binary instead of from any process that asks. That is narrower, and nowhere near sealed.

The restriction itself is hardening, and it has holes. In March 2025 Qualys published three bypasses (through aa-exec, busybox, and a preloaded library in Nautilus). Qualys also noted that each bypass gives full capabilities inside a namespace, not on the host. Ubuntu's explainer covers hardening that closes the first two of the three and calls the restriction "a stepping stone towards a stronger security posture."

Whichever fix you pick, add this to ~/.claude/settings.json so a missing sandbox stops the session instead of quietly falling back to prompts:

{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true
  }
}
Enter fullscreen mode Exit fullscreen mode

One more knob to leave alone. Inside the sandbox, commands can't create user namespaces of their own. Claude Code 2.1.305 added sandbox.allowNestedUserNamespaces for tools like Chromium that need them, and the docs warn that turning it on means "code from a sandboxed command can end up running outside the sandbox." Don't flip it because one test failed.

Where Hermes fits, and where it doesn't

I build Hermes IDE, so weigh this section with that in mind. Hermes runs on Linux (.deb and AppImage), and it starts each agent's own CLI "in a real terminal, exactly as it would anywhere else," per the README. Claude Code inside Hermes reads the same ~/.claude/settings.json and runs into the same AppArmor wall on 24.04. Hermes doesn't sandbox agents itself. The vendor's sandbox is the safety layer.

What Hermes adds sits around that layer. ⌘N (Ctrl+Shift+N on Linux) starts each task on its own hermes/ branch in its own git worktree, so two agents don't share a checkout and overwrite each other's files. A worktree is a separate folder with no security boundary around it. It keeps agents from colliding with each other, and it does nothing to stop a command that reads files outside it.

The Hermes New task sheet with a refund-endpoint task typed, chips for Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and the Hermes will run preview of the claude command
The ⌘N New task sheet: Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and a preview of the claude command Hermes will run.

Hermes runs Claude Code's own claude CLI, as the preview at the bottom of that sheet shows, so you fix the sandbox once, with the profile or the sysctl plus failIfUnavailable, and every Claude Code session it starts inherits that. Hermes is free to download, source-available under BSL 1.1, and runs on macOS, Windows and Linux. Download it or read the source. It is not affiliated with Anthropic, Canonical, Nous Research or any agent vendor.

If you want to see what the sandbox actually blocks once it runs, I took Claude Code's sandbox runtime apart in an earlier post.


If this was useful

A sandbox is the layer that still holds when an instruction you didn't write lands in your agent's context, from a README, an issue comment or a page it fetched. The other half of that defence is how you write the prompts and project instructions the agent works from. My Prompt Engineering Pocket Guide covers that half, prompt injection included.

Prompt Engineering Pocket Guide: Techniques for Getting the Most from LLMs

Top comments (0)