How the Bubblewrap Backend Uses User Namespaces for Linux Sandboxing

The Bubblewrap backend creates unprivileged Linux sandboxes by invoking the bwrap binary with --unshare-user and related flags, establishing isolated user, PID, IPC, and UTS namespaces without requiring root privileges.

The Microsoft MXC (Multiplatform eXecution Container) SDK provides secure workload isolation through its Bubblewrap backend, which leverages Linux kernel features to create lightweight sandboxes. By utilizing user namespaces, the backend enables unprivileged users to spawn isolated processes that run with distinct UID/GID mappings separate from the host system. This approach eliminates the need for elevated privileges while maintaining strong security boundaries between the sandboxed application and the underlying host.

How User Namespaces Enable Unprivileged Sandboxing

The Bubblewrap backend achieves unprivileged Linux sandboxing by relying on the kernel's ability to create new user namespaces without root access. When the Linux kernel parameter /proc/sys/kernel/unprivileged_userns_clone is set to 1, an unprivileged user can invoke the bwrap binary to create a namespace-isolated process. According to the documentation in docs/bwrap-support/bubblewrap-backend.md (lines 24-28), this kernel configuration allows the SDK to sandbox processes without requiring sudo or root privileges.

Inside the new user namespace, the sandboxed process receives a distinct set of UID and GID mappings that are independent of the host's identifiers. This mapping effectively gives the process root privileges within the sandbox (UID 0) while mapping to a non-privileged UID on the host, preventing privilege escalation outside the namespace.

Namespace Isolation Architecture

The Bubblewrap backend constructs a multi-layered isolation environment by unsharing several Linux namespaces simultaneously. As documented in docs/bwrap-support/bubblewrap-backend.md (lines 56-58), the backend passes specific --unshare-* flags to the bwrap binary to create these isolation boundaries.

User Namespace Creation

The --unshare-user flag instructs bwrap to create a new user namespace before executing the target command. This namespace provides the foundation for all other sandboxing features, as it allows the process to acquire capabilities within the sandbox that are isolated from the host's privilege model. The user namespace ensures that any capability adjustments or permission changes remain contained within the sandbox environment.

Process and System Isolation

Beyond user namespaces, the backend unshares three additional critical namespaces. The --unshare-pid flag creates a new PID namespace, causing the sandboxed process to run as PID 1 and treating it as the init process for that process tree. The --unshare-ipc flag isolates inter-process communication resources, while --unshare-uts creates a separate UTS namespace for hostname isolation. These flags work together to ensure that process IDs, shared memory, and system identifiers remain private to the sandbox.

Filesystem and Device Views

The sandboxed process receives a restricted view of the host filesystem through specific bind mounts and virtual filesystems. The configuration applies --ro-bind / / to provide a read-only view of the host root, while --dev /dev, --proc /proc, and --tmpfs /tmp create private instances of these critical directories (as noted in docs/bwrap-support/bubblewrap-backend.md, lines 57-60). This setup prevents the sandboxed application from modifying system devices or persistent host data while maintaining access to necessary pseudo-filesystems.

SDK Configuration and Execution Flow

The MXC SDK does not execute bwrap commands directly; instead, it delegates to a native binary that interprets high-level configuration objects.

Detecting Bubblewrap Availability

Before attempting to create a sandbox, the SDK verifies that the bwrap binary exists on the host system. In sdk/src/platform.ts (lines 316-320), the isBubblewrapAvailable function executes bwrap --version via execSync to confirm the binary is present and functional. This check prevents runtime errors and allows the SDK to fall back to alternative containment strategies if Bubblewrap is unavailable.

Building the Container Configuration

When a user requests a Bubblewrap sandbox, the SDK constructs a ContainerConfig object with the containment field explicitly set. In sdk/src/sandbox.ts (lines 90-99), the buildBubblewrapConfig function sets containment: 'bubblewrap' and applies Linux-specific network policies. This configuration object is then serialized to JSON and passed to the native wxc-exec binary on Linux hosts.

Native Binary Invocation and Cleanup

The wxc-exec binary translates the JSON configuration into the final bwrap command line, automatically injecting the necessary --unshare-* flags. When the sandboxed command exits, the bwrap process terminates automatically, triggering cleanup of all associated namespaces. As documented in docs/bwrap-support/bubblewrap-backend.md (lines 63-64), this design ensures that namespace resources are released immediately upon process completion without requiring manual intervention.

Practical Implementation Example

The following TypeScript example demonstrates how to launch a sandboxed process using the Bubblewrap backend via the MXC SDK:

import { spawnSandbox } from "mxc/sdk";

const policy = {
  version: "0.6.0-alpha",
  containment: "bubblewrap",
  process: { commandLine: "echo 'Hello from Bubblewrap'" },
};

spawnSandbox("echo 'Hello from Bubblewrap'", policy, { experimental: true })
  .onData(data => console.log(data))
  .onExit(e => console.log(`Exited with code ${e.exitCode}`));

Behind the scenes, the SDK generates a configuration flagged with containment: 'bubblewrap', which the native binary translates into a command equivalent to:

bwrap \
  --unshare-user \
  --unshare-pid \
  --unshare-ipc \
  --unshare-uts \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --setenv PATH /usr/bin \
  --setenv HOME /tmp \
  -- sh -c "echo 'Hello from Bubblewrap'"

This execution model ensures the command runs inside an unprivileged user namespace with automatic resource cleanup upon completion.

Summary

  • The Bubblewrap backend leverages Linux user namespaces to create sandboxes without requiring root privileges, provided the kernel allows unprivileged namespace creation.
  • The backend unshares user, PID, IPC, and UTS namespaces via --unshare-* flags to establish process isolation and independent resource views.
  • The MXC SDK detects bwrap availability in sdk/src/platform.ts and configures sandboxes through sdk/src/sandbox.ts by setting containment: 'bubblewrap'.
  • The native wxc-exec binary constructs the final command line and ensures automatic namespace cleanup when the sandboxed process terminates.
  • Sandboxed processes receive read-only host filesystem access with private /dev, /proc, and /tmp directories to prevent host modification.

Frequently Asked Questions

Do I need root privileges to use the Bubblewrap backend?

No. The Bubblewrap backend is designed specifically for unprivileged sandboxing. As long as your Linux kernel has /proc/sys/kernel/unprivileged_userns_clone set to 1, the bwrap binary can create new user namespaces without root access. The SDK performs this operation through the standard bwrap invocation, allowing regular users to spawn isolated environments safely.

Which specific namespaces does the Bubblewrap backend create?

The backend creates four distinct namespaces using the --unshare-user, --unshare-pid, --unshare-ipc, and --unshare-uts flags. The user namespace isolates privileges and UID mappings, the PID namespace provides a separate process tree with the sandboxed process as PID 1, the IPC namespace isolates shared memory and semaphores, and the UTS namespace provides an isolated hostname.

How does the MXC SDK detect if Bubblewrap is installed?

The SDK checks for the bwrap binary at runtime using the isBubblewrapAvailable function in sdk/src/platform.ts (lines 316-320). This function attempts to execute bwrap --version synchronously via execSync. If the command succeeds, the SDK proceeds with Bubblewrap containment; otherwise, it may fall back to alternative isolation methods or report an error depending on the configuration.

What happens to the namespaces when the sandboxed process exits?

The namespaces are automatically destroyed when the sandboxed process terminates. The bwrap binary monitors the child process (the user-specified command) and exits immediately when that child exits. According to docs/bwrap-support/bubblewrap-backend.md (lines 63-64), this behavior ensures that all associated namespace resources, including user and PID namespaces, are cleaned up by the kernel without requiring manual intervention or explicit unmount operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →