bubblewrap is a wonderful tool for creating sandboxes as an unprivileged user. Here’s some useful sandboxes that I regularly use.

Reading Mail in Isolation

#!/bin/sh

exec bwrap --unshare-user --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup \
    --ro-bind /bin /bin \
    --ro-bind /lib /lib \
    --ro-bind /lib64 /lib64 \
    --ro-bind /usr/bin /usr/bin \
    --ro-bind /usr/lib /usr/lib \
    --ro-bind /usr/share/terminfo /usr/share/terminfo \
    --ro-bind /etc/passwd /etc/passwd \
    --ro-bind $HOME/.muttrc $HOME/.muttrc \
    --bind $HOME/.mutt_certificates $HOME/.mutt_certificates \
    --bind $MAIL $MAIL \
    --tmpfs /tmp \
    mutt "$@"

Text-mode Web Browsing without Distractions

#!/bin/bash

exec bwrap --unshare-user --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup \
    --ro-bind /bin /bin \
    --ro-bind /lib /lib \
    --ro-bind /lib64 /lib64 \
    --ro-bind /usr/bin /usr/bin \
    --ro-bind /usr/lib /usr/lib \
    --ro-bind /usr/share/terminfo /usr/share/terminfo \
    --ro-bind /etc/passwd /etc/passwd \
    --ro-bind /etc/lynx /etc/lynx \
    --ro-bind /etc/ssl/certs /etc/ssl/certs \
    --ro-bind /usr/share/ca-certificates /usr/share/ca-certificates \
    --bind $HOME/Downloads $HOME/Downloads \
    --tmpfs /tmp \
    lynx "$@"

Just Reading a PDF

#!/bin/sh

for last; do true; done

exec bwrap --unshare-all \
    --ro-bind /lib /lib \
    --ro-bind /bin /bin \
    --ro-bind /lib64 /lib64 \
    --ro-bind /usr/bin /usr/bin \
    --ro-bind /usr/lib /usr/lib \
    --ro-bind "$last" "$last" \
    --bind /tmp /tmp \
    --dev /dev \
    --proc /proc  \
    --chdir / \
    /usr/bin/mupdf "$@"

Sandboxing ssh Clients

#!/bin/bash

ssh=/usr/bin/ssh
ssh_auth_dir=$(dirname $SSH_AUTH_SOCK)
exec bwrap --unshare-user --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup \
    --ro-bind /lib /lib \
    --ro-bind /lib64 /lib64 \
    --ro-bind /usr/lib /usr/lib \
    --bind $HOME/.ssh/ $HOME/.ssh/ \
    --ro-bind /etc/passwd /etc/passwd \
    --ro-bind /bin/bash /bin/bash \
    --bind $ssh_auth_dir $ssh_auth_dir \
    --bind $ssh $ssh \
    --dev /dev \
    $ssh "$@"

The Private Crypto Vault

#!/bin/bash
NAME=vault
function cleanup {
    echo closing the $NAME..
    sudo /sbin/cryptsetup close $NAME
}

trap cleanup EXIT

sudo /sbin/cryptsetup open /root/$NAME.fs $NAME && \
sudo fsck /dev/mapper/$NAME && \
sudo unshare --mount --ipc --net sudo /bin/sh -c "mount $HOME/mnt && sudo -u $USER $SHELL"

The Development Idyll

Name the script dev, then usage:

$ dev go
$ go get evilscript # this will fail, no network
#!/bin/bash

CMD=$SHELL
NET="--unshare-net --ro-bind /etc/resolv.conf /etc/resolv.conf"
EXTRA=""
CONFIGS=".bashrc .tmux.conf .vimrc .vim .ipython .jupyter .gitconfig"
HOST=dev

for opt in "$@"; do
    case $opt in
        net)
            NET=""
            EXTRA="$EXTRA --bind /etc/ssl/certs /etc/ssl/certs"
            HOST="$HOST-net"
            ;;
        ssh*)
            NET=""
            key=$(echo $opt | sed -e 's/ssh.//')
            EXTRA="$EXTRA --bind $SSH_AUTH_SOCK $SSH_AUTH_SOCK"
            EXTRA="$EXTRA --bind $HOME/.ssh/config $HOME/.ssh/config"
            EXTRA="$EXTRA --bind $HOME/.ssh/known_hosts $HOME/.ssh/known_hosts"
            if [ -n "$key" ]; then
                EXTRA="$EXTRA --bind $HOME/.ssh/id_$key $HOME/.ssh/id_$key"
                EXTRA="$EXTRA --bind $HOME/.ssh/id_$key.pub $HOME/.ssh/id_$key.pub"
                HOST="$HOST-ssh2$key"
            else
                HOST="$HOST-ssh"
            fi
            ;;
        web)
            EXTRA="$EXTRA --bind $HOME/web $HOME/web"
            CONFIGS="$CONFIGS .linkchecker"
            HOST="$HOST-web"
            ;;
        src)
            EXTRA="$EXTRA --bind $HOME/src $HOME/src"
            HOST="$HOST-src"
            ;;
        cwd)
            EXTRA="$EXTRA --bind $PWD $PWD"
            ;;
        py)
            EXTRA="$EXTRA --bind $HOME/.local $HOME/.local"
            HOST="$HOST-py"
            ;;
        go)
            EXTRA="$EXTRA --bind $GOPATH $GOPATH"
            HOST="$HOST-go"
            ;;
        *)
            cat <<EOF 2>&1
usage: $0 mode1 mode2 ... modeN

 mode is one of:
 * net
    enable network + share public ssl certs

 * web
    share web directory

 * src
    share src directory

 * cwd
    share current working directory

 * py
    share python stuff

 * ssh=keyname
    where keyname exists as .ssh/id_keyname*
    implies enabling network
EOF
            exit 1
            ;;
    esac
done

for config in $CONFIGS; do
    CONFIG="$CONFIG --ro-bind $HOME/$config $HOME/$config"
done

exec bwrap \
    --unshare-user \
    --unshare-ipc \
    --unshare-pid \
    $NET \
    --unshare-uts \
    --unshare-cgroup \
    --hostname $HOST.home \
    --proc /proc \
    --dev /dev \
    --tmpfs /tmp \
    --ro-bind /bin /bin \
    --ro-bind /usr /usr \
    --ro-bind /lib /lib \
    --ro-bind /lib64 /lib64 \
    --ro-bind /sbin /sbin \
    --ro-bind /etc/passwd /etc/passwd \
    --ro-bind /etc/group /etc/group \
    --ro-bind /etc/alternatives /etc/alternatives \
    $CONFIG \
    $EXTRA \
    $CMD