shandbox is a simple
Linux sandboxing script that serves my needs well. Perhaps it works for you
too? No dependencies between a shell and util-linux (unshare and nsenter).
In short, it aims to provide fairly good isolation for personal files (i.e.
your $HOME) while being very convenient for day to day use. It's designed to
be run as an unprivileged user - as long as you can make new namespaces you
should be good to go. By default /home/youruser/sandbox shows up as
/home/sandbox within the sandbox, and other than standard paths like /usr,
/etc, /tmp, and so on it's left for you to either copy things into the
sandbox or expose them via a mount. There's a single shared sandbox (i.e.
processes within the sandbox can see and interact with each other, and the
exposed sandbox filesystem is shared as well), which trades off some ease of
use for the security you might get with a larger number of more targeted
sandboxes. On the other hand, you only gain security from a sandbox if you
actually use it and this is a setup that offers very low friction for me. The
network is not namespaced (although this is something you could change with a
simple edit).
Usability is both subjective and highly dependent on your actual use case, so
the tradeoffs may or may not align with what is interesting for you!
Bubblewrap is an example of a
mature alternative unprivileged sandboxing
tool that offers a lot of configurability as well as options with greater
degrees of sandboxing. Beyond that, look to
Firecracker based solutions or
gvisor. shandbox obviously aims to provide a
reasonable sandbox as much as Linux namespaces alone are able to offer, but if
you're looking for a security property stronger than "makes it harder for
something to edit or access unwanted files" it's down to you to both carefully
review its implementation and consider alternatives.
$ shandbox run uvx pycowsay
Installed 1 package in 5ms
------------
< Hello, world >
------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ shandbox status
running (pid 1589364)
log:
2026-02-11 13:02:51 stopped
2026-02-11 13:05:06 started (pid 1589289)
$ shandbox add-mount ~/repos/llvm-project /home/sandbox/llvm-project
mounted /home/asb/repos/llvm-project -> /home/sandbox/llvm-project
$ shandbox run touch /home/sandbox/llvm-project/write-attempt
touch: cannot touch '/home/sandbox/llvm-project/write-attempt': Read-only file system
$ shandbox remove-mount /home/sandbox/llvm-project
unmounted /home/sandbox/llvm-project
$ shandbox add-mount --read-write ~/repos/llvm-project /home/sandbox/llvm-project
mounted /home/asb/repos/llvm-project -> /home/sandbox/llvm-project
$ shandbox run touch /home/sandbox/llvm-project/write-attempt
shandbox enter will open a shell within the sandbox for easy interactive
usage. As a convenience, if the current working directory is in
$HOME/sandbox (e.g. $HOME/sandbox/foo) then the working directory within
the sandbox for shandbox run or shandbox enter will be set to the
appropriate path within the sandbox (/home/sandbox/foo in this case). i.e.,
the case where this mapping is trivial. Environment variables are not passed
through.
shandbox start: Start the sandbox, creating the necessary namespaces and
mount layout. Fails if the sandbox is already running.shandbox stop: Stop the sandbox by killing the process holding the
namespaces. Fails if the sandbox is not running.shandbox restart: Stop the sandbox and start it again.shandbox status: Print whether the sandbox is running and if it is, the
pid. Also print the last 20 lines of the log.shandbox enter: Open bash within the sandbox, starting the sandbox first
if it's not already running.shandbox run <command> [args...]: Run a command inside the sandbox. The
current working directory is translated to an in-sandbox path if it falls
within the sandbox home directory. Starts the sandbox first if it isn't
already running.shandbox add-mount [--read-write] <host-path> <sandbox-path>: Bind-mount a
host path into the running sandbox. Mounts are read-only by default; pass
--read-write to allow writes. The sandbox must already be running.
Both directories and individual files are supported.shandbox remove-mount <sandbox-path>: Remove a previously added bind mount
from the running sandbox.The core sandboxing functionality is provided by the Linux namespaces
functionality exposed by
unshare
and
nsenter.
The script's
implementation should be
quite readable but I'll try to summarise some key points here.
The goal is that:
To implement that:
nsenter to
enter the namespace./etc/passwd is bind-mounted naming the current user as sandbox.shandbox start is executed, the necessary directories are bind
mounted in a directory that will be used as root (/) for the user sandbox
in .local/share/shandbox/root. This happens within the sandbox_root
namespace, which then uses unshare again to create a new user namespace
with an unprivileged user, executing within a chroot.shandbox start to use pivot_root.The script should be straight-forward enough to customise to your needs if
they're not too dissimilar to what is offered out of the box. Some variables
at the top provide things you may be more likely to want to change, such as
the home directory location, and a list of files or directories in $HOME to
always bind-mount into the sandbox home:
SANDBOX_HOME_DIR="$HOME/sandbox"
HOME_FILES_TO_MAP=".bashrc .vimrc"
HOME_DIRS_TO_MAP=".vim bin"
SB_HOME="/home/sandbox"
SB_PATH="$SB_HOME/bin:/usr/local/bin:/usr/bin"