| PolarSPARC |
Bubblewrap - Pop Goes the Privilege!
| Bhaskar S | 05/22/2026 |
Overview
AI has dramatically accelerated the development and release of new tools - both beneficial and malicious - at an unprecedented pace. How do we evaluate these promising new tools safely, within a controlled, sandboxed environment ???
Well - one of the answers is the Bubblewrap sandboxing tool in Linux !!!
Bubblewrap is a low-privileged sandboxing tool used in production by many projects in Linux. Unlike heavy virtual machines or containers (Docker, Podman), Bubblewrap uses the following Linux kernel primitives directly:
User Namespaces :: to map host UIDs/GIDs to sandboxed ones without root
Mount Namespaces :: to build a private filesystem view
PID Namespaces :: to isolate process trees
Network Namespaces :: to cut off or reshape networking
IPC Namespaces :: to isolate SysV IPC and POSIX primitives
UTS Namespaces :: to set a private hostname
Seccomp Filters :: to restrict which syscalls the sandboxed process may make
Because Bubblewrap runs entirely in unprivileged user space, it is suitable for sandboxing untrusted code, AI agents, build systems, downloaded scripts, and developer tools that need internet access but should not touch your home directory.
Bubblewrap works by:
Forking a child process
Setting up Linux namespaces (mount, PID, UTS, IPC, network, user, CGroup)
Re-mounting thefilesystem according to your desired specifications
Executing the target command inside that isolated environment
The critical insight is that Bubblewrap starts with a root filesystem (typically your current system root `/`) and then layers bind mounts on top. Everything NOT explicitly mounted or bound is invisible inside the sandbox.
Hands-on Bubblewrap
All the commands will be executed on a Ubuntu 24.04 LTS based Linux desktop.
To install Bubblewrap, execute the following command in a new terminal window:
$ sudo apt update; sudo apt install bubblewrap
Once the install succeeds, execute the following command in the terminal window:
$ bwrap --version
At the time of this article, the following was the output:
bubblewrap 0.9.0
To list all the avialable command-line options, execute the following command in the terminal window:
$ bwrap --help
The following would be a typical output:
usage: bwrap [OPTIONS...] [--] COMMAND [ARGS...] --help Print this help --version Print version --args FD Parse NUL-separated args from FD --argv0 VALUE Set argv[0] to the value VALUE before running the program --unshare-all Unshare every namespace we support by default --share-net Retain the network namespace (can only combine with --unshare-all) --unshare-user Create new user namespace (may be automatically implied if not setuid) --unshare-user-try Create new user namespace if possible else continue by skipping it --unshare-ipc Create new ipc namespace --unshare-pid Create new pid namespace --unshare-net Create new network namespace --unshare-uts Create new uts namespace --unshare-cgroup Create new cgroup namespace --unshare-cgroup-try Create new cgroup namespace if possible else continue by skipping it --userns FD Use this user namespace (cannot combine with --unshare-user) --userns2 FD After setup switch to this user namespace, only useful with --userns --disable-userns Disable further use of user namespaces inside sandbox --assert-userns-disabled Fail unless further use of user namespace inside sandbox is disabled --pidns FD Use this pid namespace (as parent namespace if using --unshare-pid) --uid UID Custom uid in the sandbox (requires --unshare-user or --userns) --gid GID Custom gid in the sandbox (requires --unshare-user or --userns) --hostname NAME Custom hostname in the sandbox (requires --unshare-uts) --chdir DIR Change directory to DIR --clearenv Unset all environment variables --setenv VAR VALUE Set an environment variable --unsetenv VAR Unset an environment variable --lock-file DEST Take a lock on DEST while sandbox is running --sync-fd FD Keep this fd open while sandbox is running --bind SRC DEST Bind mount the host path SRC on DEST --bind-try SRC DEST Equal to --bind but ignores non-existent SRC --dev-bind SRC DEST Bind mount the host path SRC on DEST, allowing device access --dev-bind-try SRC DEST Equal to --dev-bind but ignores non-existent SRC --ro-bind SRC DEST Bind mount the host path SRC readonly on DEST --ro-bind-try SRC DEST Equal to --ro-bind but ignores non-existent SRC --bind-fd FD DEST Bind open directory or path fd on DEST --ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST --remount-ro DEST Remount DEST as readonly; does not recursively remount --exec-label LABEL Exec label for the sandbox --file-label LABEL File label for temporary sandbox content --proc DEST Mount new procfs on DEST --dev DEST Mount new dev on DEST --tmpfs DEST Mount new tmpfs on DEST --mqueue DEST Mount new mqueue on DEST --dir DEST Create dir at DEST --file FD DEST Copy from FD to destination DEST --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST --ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST --symlink SRC DEST Create symlink at DEST with target SRC --seccomp FD Load and use seccomp rules from FD (not repeatable) --add-seccomp-fd FD Load and use seccomp rules from FD (repeatable) --block-fd FD Block on FD until some data to read is available --userns-block-fd FD Block on FD until the user namespace is ready --info-fd FD Write information about the running container to FD --json-status-fd FD Write container status to FD as multiple JSON documents --new-session Create a new terminal session --die-with-parent Kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies. --as-pid-1 Do not install a reaper process with PID=1 --cap-add CAP Add cap CAP when running as privileged user --cap-drop CAP Drop cap CAP when running as privileged user --perms OCTAL Set permissions of next argument (--bind-data, --file, etc.) --size BYTES Set size of next argument (only for --tmpfs) --chmod OCTAL PATH Change permissions of PATH (must already exist)
The following are some of the most important options for filesystem isolation:
| Option | Description |
|---|---|
| --bind SRC DEST | Bind mount host path SRC to the sandbox path DEST in read-write mode |
| --ro-bind SRC DEST | Bind mount host path SRC to the sandbox path DEST in read-only mode |
| --proc DEST | Mount a new proc filesystem to the sandbox path DEST |
| --dev DEST | Mount a new dev filesystem to the sandbox path DEST |
| --tmpfs DEST | Mount a new tmp filesystem to the sandbox path DEST |
The following are some of the most important options for managing the namespace isolation:
| Option | Description |
|---|---|
| --unshare-all | Unshare all namespace types by default |
| --unshare-pid | Create a new pid namespace |
| --unshare-uts | Create a new uts namespace (hostname) |
| --unshare-ipc | Create a new ipc namespace |
| --unshare-net | Create a new network namespace |
| --unshare-user | Create a new user namespace |
| --share-net | Retain the network namespace from the host |
The following are the other important options for managing the sandbox isolation:
| Option | Description |
|---|---|
| --die-with-parent | Child proces dies if parent process terminates |
| --dir DEST | Create a dir at the sandbox path DEST |
| --chdir DEST | Change working directory inside the sandbox to the path DEST |
| --clearenv | Unset all environment variables in the sandbox |
| --setenv VAR VALUE | Set the environment variable VAR to the VALUE |
| --hostname NAME | Custom hostname NAME in the sandbox (requires --unshare-uts) |
Time to get our hands dirty with practical examples !!!
To verify that a sandbox environment has no access to the home directory on the host, execute the following command in the terminal window:
$ bwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --proc /proc --dev /dev --unshare-all --die-with-parent -- ls /home
The following would be the typical output:
ls: cannot access '/home': No such file or directory
To verify that a sandbox environment has no access to the host password file (/etc/passwd), execute the following command in the terminal window:
$ bwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --proc /proc --dev /dev --tmpfs /tmp --unshare-all --die-with-parent -- cat /etc/passwd
The following would be the typical output:
cat: /etc/passwd: No such file or directory
To create a bare minimum sandbox environment, execute the following command in the terminal window:
$ bwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /bin /bin --proc /proc --dev /dev --tmpfs /tmp --tmpfs /run --uid 1000 --gid 1000 --dir /home/sandbox --chdir /home/sandbox --unshare-all --hostname sandbox --clearenv --setenv HOME /home/sandbox --setenv USER sandbox --die-with-parent -- bash --norc --noprofile
The following would be the typical output:
bash-5.2$
The above is the prompt from the sandbox environment.
To check if the sandbox envrionment has access to the home directory (/home/polarsparc) on the host, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ ls /home/polarsparc
The following would be the typical output:
ls: cannot access '/home/polarsparc': No such file or directory bash-5.2$
To exit the sandbox envrionment, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ exit
To create a sandbox environment that has read-only access to the home directory (/home/polarsparc) on the host, execute the following command in the terminal window:
$ bwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /bin /bin --ro-bind /home/polarsparc /home/polarsparc --proc /proc --dev /dev --tmpfs /tmp --tmpfs /run --dir /home/sandbox --chdir /home/sandbox --unshare-all --hostname sandbox --setenv HOME /home/sandbox --setenv USER sandbox --die-with-parent -- bash --norc --noprofile
The following would be the typical output:
bash-5.2$
The above is the prompt from the sandbox environment.
To list the contents of the home directory (/home/polarsparc) on the host, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ ls /home/polarsparc
The following would be the typical output:
Applications Desktop Documents Downloads Music MyProjects Pictures Public Templates Videos bash-5.2$
To create a test file (test.txt) in home directory (/home/polarsparc) on the host, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ echo 'text' > /home/polarsparc/Downloads/test.txt
The following would be the typical output:
bash: /home/polarsparc/Downloads/test.txt: Read-only file system bash-5.2$
To exit the sandbox envrionment, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ exit
To verify that a sandbox environment has no access to the network, execute the following command in the terminal window:
$ bwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /bin /bin --proc /proc --dev /dev --tmpfs /tmp --unshare-all --die-with-parent -- curl https://httpbin.org/get
The following would be the typical output:
curl: (6) Could not resolve host: httpbin.org
To create a sandbox environment with network access, execute the following command in the terminal window:
$ bwrap --ro-bind /usr /usr --ro-bind /etc/resolv.conf /etc/resolv.conf --ro-bind /etc/ssl /etc/ssl --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /bin /bin --proc /proc --dev /dev --tmpfs /tmp --unshare-all --share-net --die-with-parent -- curl https://httpbin.org/get
The following would be the typical output:
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/8.5.0",
"X-Amzn-Trace-Id": "Root=1-6a0e44a9-39ce695f4f8656734153dcc1"
},
"origin": "173.70.1.174",
"url": "https://httpbin.org/get"
}
To execute a python script in a sandbox environment, execute the following command in the terminal window:
$ bwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /bin /bin --proc /proc --dev /dev --tmpfs /tmp --unshare-all --die-with-parent -- python3 -c "print('hello from sandbox')"
The following would be a typical output:
hello from sandbox
To create a sandbox environment with a persistent home directory (/home/sandbox), execute the following commands in the terminal window:
$ mkdir -p $HOME/.local/share/sandbox
bwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /bin /bin --proc /proc --dev /dev --tmpfs /tmp --bind $HOME/.local/share/sandbox /home/sandbox --chdir /home/sandbox --unshare-all --hostname sandbox --setenv HOME /home/sandbox --setenv USER sandbox --die-with-parent -- bash
The following would be the typical output:
bash-5.2$
To create a test file (test.txt) in the sandbox home directory (/home/sandbox), execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ echo 'text' > /home/sandbox/test.txt
The following would be the typical output:
bash-5.2$
To verify if a sandbox environment has access to any nvidia gpu, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ nvidia-smi
The following would be the typical output:
NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running. bash-5.2$
To exit the sandbox envrionment, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ exit
To check if the test file (test.txt) is visible on the host, execute the following command in the terminal window:
$ ls -l $HOME/.local/share/sandbox
The following would be a typical output:
total 4 -rw-rw-r-- 1 polarsparc polarsparc 5 May 22 14:40 text.txt
To create a sandbox environment with a persistent home directory (/home/sandbox) as well as access to the nvidia gpu, execute the following commands in the terminal window:
$ mkdir -p $HOME/.local/share/sandbox
bwrap --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /bin /bin --proc /proc --dev /dev --dev-bind /dev/nvidia0 /dev/nvidia0 --dev-bind /dev/nvidiactl /dev/nvidiactl --dev-bind /dev/nvidia-uvm /dev/nvidia-uvm --dev-bind /dev/nvidia-uvm-tools /dev/nvidia-uvm-tools --ro-bind /sys/bus/pci /sys/bus/pci --ro-bind /sys/class/drm /sys/class/drm --tmpfs /tmp --bind $HOME/.local/share/sandbox /home/sandbox --chdir /home/sandbox --unshare-all --hostname sandbox --setenv HOME /home/sandbox --setenv USER sandbox --die-with-parent -- bash
The following would be the typical output:
bash-5.2$
To verify if a sandbox environment has access to any nvidia gpu, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ nvidia-smi
The following would be the typical output:
Fri May 22 19:04:48 2026 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 580.159.03 Driver Version: 580.159.03 CUDA Version: 13.0 | +-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 NVIDIA GeForce RTX 4060 Ti Off | 00000000:04:00.0 On | N/A | | 0% 45C P8 11W / 165W | 911MiB / 16380MiB | 22% Default | | | | N/A | +-----------------------------------------+------------------------+----------------------+ +-----------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| | No running processes found | +-----------------------------------------------------------------------------------------+ bash-5.2$
To exit the sandbox envrionment, execute the following command from the sandbox prompt in the terminal window:
bash-5.2$ exit
BOOM - with this we conclude the hands-on demonstration of creating a sandbox environment using Bubblewrap !!!
References