Easy Sandboxing on Linux with Bubblewrap

birdculture1 pts0 comments

Easy Sandboxing on Linux with Bubblewrap | Ivan Molodetskikh’s Webpage

Easy Sandboxing on Linux with Bubblewrap

Aug 9, 2026 19:51

1531 words

8 minute read

In these turbulent times, one frequently needs to run some tooling in a sandbox.<br>The goal is mainly to reduce the blast radius: make it so programs within the sandbox cannot damage the host system (e.g. delete or overwrite something unintended), but also, to a lesser extent, to hide most of the filesystem to avoid exfiltrating sensitive data.

Recently, Bartosz Taudul (of Tracy fame) showed how to use systemd-nspawn for this purpose.<br>He creates a container configuration, installs a distro inside, and bind-mounts some cache and project folders from the host.<br>The mounts have an overlayfs on top, so within the container, tools can write over the files, but those writes do not affect the host filesystem.

I also want to share my sandboxing approach.<br>My goal was to make it easy to use and reduce friction as much as possible, so that I always have a sandbox at my fingertips.

The result boils down to spawning a container-like environment, sharing enough of the host filesystem read-only to make all host binaries runnable, and sharing the current working directory read-write.<br>Within this sandbox, you don&rsquo;t need to install a separate distro—everything from your host just works, while the filesystem is kept mostly isolated (except for the folder where you run the sandbox).

For example, I&rsquo;ll run the script in a Tracy checkout.

┌ ((8c8d451a)) ~/s/c/tracy<br>└─ box fish<br>Welcome to fish, the friendly interactive shell<br>Type help for instructions on how to use fish<br>yalter@sandbox ~/s/c/tracy><br>I can run the build since all my host binaries are accessible:

yalter@sandbox ~/s/c/tracy> meson setup build<br>The Meson build system<br>Version: 1.11.2<br>Source dir: /home/yalter/source/cpp/tracy<br>Build dir: /home/yalter/source/cpp/tracy/build<br>Build type: native build<br>Project name: tracy<br>Project version: 0.13.1<br>C++ compiler for the host machine: /usr/bin/ccache c++ (clang 22.1.8 "clang version 22.1.8 (AerynOS)")<br>C++ linker for the host machine: c++ ld.lld 22.1.8<br>Host machine cpu family: x86_64<br>Host machine cpu: x86_64<br>Checking if define "_MSC_VER" exists: NO<br>Run-time dependency threads found: YES<br>Found pkg-config: YES (/usr/bin/pkg-config) 2.5.1<br>Build targets in project: 1

Found ninja-1.13.2 at /usr/bin/ninja<br>yalter@sandbox ~/s/c/tracy> ninja -C build<br>ninja: Entering directory `build'<br>[2/2] Linking target libtracy.so<br>The home folder contains the working directory, and is otherwise mostly empty:

yalter@sandbox ~/s/c/tracy> ls -l ~<br>total 0<br>drwx------ 4 1000 1000 80 Aug 9 20:20 source/<br>I can write into the home folder, but the write will go into a tmpfs, and will not affect the host system:

yalter@sandbox ~/s/c/tracy> touch ~/evil<br>yalter@sandbox ~/s/c/tracy> ^D<br>┌ ((8c8d451a)) ~/s/c/tracy<br>└─ cat ~/evil<br>cat: /home/yalter/evil: No such file or directory<br>Only changes to the Tracy folder, where I ran the sandbox, persisted on the host, all with correct user ID and everything:

┌ ((8c8d451a)) ~/s/c/tracy<br>└─ ls -l build/<br>total 28K<br>drwxr-xr-x 1 yalter yalter 48 Aug 9 20:21 libtracy.so.p<br>drwxr-xr-x 1 yalter yalter 496 Aug 9 20:21 meson-info<br>drwxr-xr-x 1 yalter yalter 56 Aug 9 20:21 meson-logs<br>drwxr-xr-x 1 yalter yalter 310 Aug 9 20:21 meson-private<br>drwxr-xr-x 1 yalter yalter 40 Aug 9 20:21 meson-uninstalled<br>-rw-r--r-- 1 yalter yalter 5,3K Aug 9 20:21 build.ninja<br>-rw-r--r-- 1 yalter yalter 545 Aug 9 20:21 compile_commands.json<br>-rwxr-xr-x 1 yalter yalter 14K Aug 9 20:21 libtracy.so<br>The box script #

I use Bubblewrap to spawn the sandbox.<br>This is an unprivileged sandboxing tool used by Flatpak (though, I hear there are plans to replace it with something else).

The script itself composes a long bwrap invocation.<br>Let&rsquo;s look at some of the parts.

#!/usr/bin/env bash<br>set -euo pipefail

# Export ALLOW_NET=0 to disable network access inside the sandbox.<br># Keep in mind that if your X11/Xwayland doesn't check Xauth,<br># then network access lets the sandbox connect to your X11<br># via an abstract Unix socket. This is quite dangerous.<br>ALLOW_NET="${ALLOW_NET:-1}"

# The current folder that we're binding read-write.<br>REPO="$(readlink -f .)"

BWRAP=( bwrap<br>--die-with-parent<br># Unshare (isolate) a bunch of things inside the sandbox.<br>--unshare-pid<br>--unshare-uts<br>--unshare-cgroup-try<br>--unshare-user-try<br>--cap-drop ALL<br># Create/mount important folders.<br>--proc /proc<br>--dev /dev<br>--tmpfs /tmp<br>--tmpfs /var<br>--dir /run<br>--dir /etc<br>--hostname sandbox

# Warning: this script shares all environment variables.<br># If on your system the environment can contain secrets,<br># you may want to clear them:<br># --clearenv

# Bind the current folder read-write and chdir there.<br>--bind "$REPO" "$REPO"<br>--chdir "$REPO"

# --- Read-only system binds ---<br>SYS_RO_BINDS=(<br># Folders with binaries and libraries.<br>/usr<br>/bin<br>/sbin<br>/lib<br>/lib64<br># Random configuration files that programs tend to...

yalter sandbox tracy host build read

Related Articles