Claude Code's allowNestedUserNamespaces: Fixing Chromium Breaks Outer Sandbox File Boundary
This article analyzes Claude Code's allowNestedUserNamespaces setting, explaining how enabling it to support Chromium and rootless Podman weakens the outer sandbox's file protections, compares three workarounds with different security trade-offs, and provides a decision framework based on asset sensitivity and trust domains.
Errors Come from a Sandbox Inside the Sandbox
On a Linux machine running Claude Code's sandbox, frontend test runners like Playwright fail with No usable sandbox! when launching Chromium, while rootless Podman fails with cannot clone: Operation not permitted. Both failures stem from the same default: commands inside the sandbox cannot create user namespaces, yet both tools need to create an additional isolation layer inside the existing sandbox.
Claude Code 2.1.305 (released 2026-10-20 19:29 UTC) added sandbox.allowNestedUserNamespaces. The official changelog states it lets sandboxed commands create user namespaces for Chromium and rootless Podman, then immediately adds: "this lets them get around the sandbox's file protections." The purpose and the cost are written in the same release note line.
The key question is which boundary gets replaced. Claude Code's Bash sandbox is a shell wrapped around commands; Chromium and Podman need to build another shell inside it, so they must get the permission to create that shell. Once allowed, the outer shell's file rules are no longer a reliable boundary for that command. The problem becomes: when two sandboxes cannot both apply to the same command, which layer do you keep, and what assets does it protect?
What the Outer Sandbox Actually Restricts
Claude Code's file isolation on Linux and WSL2 relies on bubblewrap, which works via unprivileged user namespaces: it arranges mounts, UID mappings, and read-only views in a new namespace without needing setuid. Commands inside the sandbox already run inside one user namespace.
The documentation lists tools needing this capability: Chromium/Electron's own sandbox, nested bubblewrap, and unshare. All must create another namespace inside the existing one. Specific symptoms are documented: Chromium exits with No usable sandbox!, unshare -U true prints unshare: unshare failed: Operation not permitted, rootless Podman prints cannot clone: Operation not permitted or stalls on a read-only filesystem in its own directory. The default behavior rejects these calls.
The kernel supports nesting. user_namespaces(7) specifies a nesting limit of 32 layers (since Linux 3.11). A process creating a new namespace has full capabilities within it, including CAP_SYS_ADMIN over its mount namespace, allowing bind mounts and mounting proc, tmpfs, overlayfs. Allowing nesting is not a binary whitelist; it returns to sandboxed processes the capabilities the outer sandbox took away.
What the outer sandbox removes must be stated clearly. By default, sandboxed commands can write to the working directory, the user's temporary directory, and directories added via --add-dir. Read permissions cover the entire machine except a few default-denied directories; credential files are readable unless credentials is configured separately. Within writable paths, a set of protected paths continues to reject writes. Network has no direct egress; connections go through a local proxy, allowed by a domain allowlist that defaults to empty. Thus the sandbox's file promise mainly restricts writes; reads are wide. Read, write, and network together form a boundary: blocking only writes leaves credentials readable; blocking only network leaves files modifiable.
Anthropic's documentation does not hide the consequences. The settings reference states: setting allowNestedUserNamespaces to true means "you turn off a security boundary: code from a sandboxed command can end up running outside the sandbox." The Security Restrictions section lists it under "User namespaces on Linux and WSL2" with identical wording. The Protected Paths section adds that with it enabled, do not rely on protected paths. The documentation gives only the conclusion, not the escape paths; therefore it requires abandoning reliance on any sandbox restriction, not just one file category.
Three Compatibility Paths, Each Abandoning a Different Sandbox Layer
The troubleshooting documentation provides three fixes, each explicitly stating what is kept and what is given up.
Chromium/Electron --no-sandbox : Retains Claude Code's outer sandbox; gives up the browser's own sandbox. Granularity: per launch.
excludedCommands matching command : Retains sandbox for other commands; matched command runs outside sandbox with full privileges. Granularity: command string.
allowNestedUserNamespaces : Retains the browser's own sandbox; gives up outer sandbox's write limits and configured credential protections. Granularity: all sandbox commands for that user, all sessions.
The first path keeps risk inside the browser process: the documentation says Claude Code's sandbox stays intact, but the browser runs web content without its own sandbox. The cost does not leak to other commands, but web content loses a layer.
The second path has an easily overlooked sentence: matched commands run under your full privileges, and Claude can modify scripts in the working directory before running them. The exception constrains the matched command string, not the code it actually executes. The documentation also notes that commands starting with unshare stay in the sandbox regardless of excludedCommands patterns, because unshare can launch any other program. Patterns must match the command Claude actually runs; browsers are launched by test commands, so match npm test entry points, not the browser binary. The higher the entry point matched, the more code ends up outside the sandbox.
The third path preserves the browser's sandbox; the cost falls on the outer layer: file protections are no longer a reliable boundary for that command. Its key difference from the first two is granularity: it is a boolean, not a per-command match.
All three paths make the error disappear, but they protect different assets. The outer sandbox restricts host writes and egress; configured credential protections live here; Claude Code's own config is guarded by protected paths. The browser sandbox restricts what web content can do inside the browser. Choosing depends on which side's compromise is less acceptable, not which switch is fastest to flip.
This Switch Applies to All Projects, Not One Tool
The settings reference defines it as a Boolean, default false, scope "User or managed." True only takes effect from ~/.claude/settings.json, JSON passed via --settings, and managed settings. Project-level .claude/settings.json or .claude/settings.local.json writing true is ignored. False can take effect from any settings file; precedence follows settings priority. When the session sets CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, any source's true is ignored.
This key only works on Linux and WSL2. On these platforms, true applies to all projects, all sessions, and all sandbox commands for the user loading the settings. The documentation notes sub-agents and parent sessions share the same process and sandbox config, so sub-agent commands also fall under this grant. It does not apply only to Chromium. Project settings cannot enable it, but can write false to opt out; false follows settings precedence.
This is exactly how it differs from the first two fixes. --no-sandbox is written per launch; excludedCommands matches command patterns; allowNestedUserNamespaces has no pattern, no per-invocation toggle. When a repo needing browser tests and a repo holding production credentials share a dev machine, the former's one-time compatibility need lands on the latter's commands.
Auto-Allow's Safety Argument Relies on the OS Layer
Claude Code's permission decision happens before command execution, based on the command string; in auto mode a classifier adds a categorization. The sandbox decision happens during execution, enforced by the OS on the process. The documentation makes this distinction clear: the OS's mandatory enforcement "holds regardless of what the model chose to run and even if an allowed command does more than its name suggests."
Auto-allow skips prompts in most cases precisely because of this: commands launched in the sandbox are auto-approved; what they actually do is contained by the OS layer. It is not unconditional. Commands with prefix variable assignments (e.g., FOO=bar npm test), make CC=clang, eval, and Monitor commands still go through the normal permission flow; deny rules and content-specific ask rules still apply. This is a trade: fewer human confirmations, in exchange for a boundary that does not depend on command semantics. The classifier also judges before execution, seeing user messages, non-read-only tool calls, and CLAUDE.md; tool results are stripped, so it cannot observe what the command creates at runtime.
Nested user namespaces break this premise. For sandboxed commands not hitting the above exceptions, auto-allow still lets them run without prompts; they gain namespace creation capability at runtime and may then escape the outer layer. The sequence is critical: first judged as a sandboxed command, then walks out of the sandbox during execution. The documentation places this in a warning: in auto-allow mode, such commands can run without permission prompts.
The prompt-free decision happens before execution, based on command form; wrapper scripts, interpreters, and test runners can hide the real action behind them. A command that appears to only run tests can mid-way launch Chromium and create a namespace.
The result is a misleading state: the mode hasn't changed, config still shows sandbox enabled, but the effective result approaches a bypass for affected commands. To assess risk, don't just check /sandbox showing sandbox active; check the Config tab for the resolved allowNestedUserNamespaces.
Why True Can Only Be Enabled by a Human
This key's scope rules are directional: repositories can tighten, not loosen. Project settings writing true are ignored, so a freshly cloned repo cannot enable it by committing a settings file. This applies only to this key; repos can still change sandbox behavior via other settings. The same handling appears on allowAppleEvents (reads only from user settings, managed settings, and --settings input) and filesystem.disabled (same, plus managed settings once configuring sandbox.filesystem locks it to managed control).
The cost is visibility. Loosening happens outside the repo; code review sees nothing; colleagues cloning the project don't carry it. To verify if a machine has it enabled, you must check user settings or run /sandbox and open the Config tab. When teams treat it as a personal temporary fix, the next person hitting the same error will likely enable it the same way, and neither instance leaves a review trail.
The documentation does not explain why it isn't a command-matched allowlist. From its handling of unshare we can infer: command names cannot constrain this capability; wrappers and interpreters can bring anything in, so design chose a coarse-grained boolean requiring explicit human ownership. This is inference, not a documented rationale.
A similarly named key does the opposite. enableWeakerNestedSandbox handles Claude Code running inside an unprivileged Docker container: bubblewrap cannot mount a new /proc, so the inner sandbox bind-mounts the container's existing /proc. The documentation says this exposes process info a fresh mount would hide, and recommends it only when the outer container already provides isolation. Its scope is "Any file"; project settings can enable it unless sandbox is admin-required. allowNestedUserNamespaces cannot be enabled by project settings. The former's cost stays inside the container; the latter's cost lands on the host. The scope difference aligns with this. Both keys contain "nested," but one acknowledges the outer container is already managing, the other returns capability to processes inside the sandbox.
After Protected Paths Stop Counting
The sandbox rejects writes to a set of protected paths within the writable range: .claude settings files in the working directory and its parents, .claude/skills, agents, commands, hooks directories, .mcp.json, and .claude/workflows (files Claude Code runs itself); .bashrc, .zshrc, .gitconfig, .vscode, .idea in the working directory; .git hooks and config; and most of ~/.claude plus .credentials.json. The documentation's reason is direct: commands that can modify these files can grant themselves permissions or add a hook/MCP server that Claude Code runs outside the sandbox.
The documentation also reminds that file isolation and network isolation are two layers; missing one, the other can be bypassed: without network isolation, a compromised agent can exfiltrate SSH keys; without file isolation, it can turn system resources into a backdoor to gain network access. So a switch affecting only files does not stop at files.
This sandbox's coverage was already limited. It only wraps shell commands and their children; Read, Edit, Write, WebFetch follow permission rules; local MCP servers, hooks, LSP servers all run outside the sandbox with full privileges. Enabling nested namespaces puts the only OS-enforced layer of this execution surface into an unreliable position.
This Switch Doesn't Fix Podman
The settings reference advises using excludedCommands for rootless Podman and Buildah, because the sandbox blocks them in other ways; the troubleshooting page says this setting "alone isn't enough" for them. If the goal is just to run Podman, enabling it pays the cost without solving the problem. The documentation does not elaborate on the other blocking mechanisms, only the conclusion; if a team truly depends on Podman, testing once in the target environment is more reliable than inferring from the setting name.
Commands starting with unshare always stay in the sandbox regardless of excludedCommands patterns. This rule aligns with the design principle: entry points that can launch arbitrary programs cannot be allowed by command name.
If the session sets CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, all commands run in the sandbox; all the above workarounds become ineffective; Chromium and Electron can only use --no-sandbox. This is an explicit directional choice: prefer tools not running over loosening the boundary.
Deciding Whether to Enable: Look at Assets and Trust Domains
Summarizing the three paths' applicability:
Only need one browser test to run, web content comes from your own build artifacts: --no-sandbox is usually smaller than loosening the entire user environment's outer sandbox. Cost: this browser launch lacks its own sandbox.
Need nested bubblewrap for builds: try a narrower excludedCommands pattern covering the specific command. Commands starting with unshare are unaffected by excludedCommands; this path doesn't work for them; consider containers or VMs instead; this is usually easier to justify than a global user-settings switch.
Session will execute untrusted code (install scripts, generated scripts, third-party tests): do not enable. In auto-allow mode, such commands typically won't wait for a prompt.
Another dimension: what was this sandbox originally preventing? The documentation treats it as part of a two-layer file-and-network isolation, includes compromised agents in the threat model; it defaults to restricting writes and egress, while reads are relatively wide. After nested user namespaces, this layer is no longer reliable for affected commands: if used only against accidental misuse, the cost is limited; if treated as isolation against malicious code, it is no longer that isolation. The documentation's direction is the latter: to enclose tools, hooks, MCP together, put the Claude Code process itself into a container, VM, or sandbox runtime.
If you must keep the browser's sandbox and don't want the dev machine to lose outer protection, move such tasks entirely to an already-isolated environment: CI containers, ephemeral VMs, or run Claude Code itself inside a container. Then allowNestedUserNamespaces only needs to exist in that environment's user settings; dev machine sessions don't have to loosen. The cost is maintaining a separate environment with browser and dependencies installed; the gain is narrowing the loosening scope from a user environment to a task environment. The judgment starting point is therefore not whether this repo needs browser tests, but what else runs in this user environment.
Verification can be concrete. Run unshare -U true in the sandbox; default should yield Operation not permitted. Run touch ~/sandbox-probe; on macOS it's Operation not permitted, on Linux/WSL2 it's Read-only file system. Note that commands typed at the ! prompt usually run outside the sandbox; they cannot test sandbox state. Have Claude run them.
At the organization level, true can be locked down: managed settings setting allowNestedUserNamespaces to false overrides developers' local values. The documentation's "Keep developers from widening the policy" section lists these sandbox-weakening keys together so admins can disable them once.
Thus this switch's applicability can be written narrowly: when the outer sandbox is only for preventing accidental misuse, and real isolation lives in containers or VMs, enabling it is a reasonable compatibility path; when it is your only isolation layer against untrusted code, it should not appear in user settings. The most dangerous zone is the ambiguity between the two: one person enables it on a machine to run browser tests, and another project's commands silently lose a protection layer.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architecture Development Notes
Focused on architecture design, technology trend analysis, and practical development experience sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
