wren-linux / bin / browser-sandbox
#!/bin/bash
# browser-sandbox: written by Wren Linux lib/browser-sandbox.sh, installed as
# /usr/local/bin/browser-sandbox, with the names tor-browser, tor-browser-safest and
# torbrowser-launcher (extras/tor-browser.sh), tor-browser-unsafe (extras/tor-browser-unsafe.sh)
# and mullvad-browser (extras/mullvad-browser-sandbox.sh) linked to it.
# tor-browser [URL...] Tor Browser as its own user (Safest; tor-browser-safest too)
# tor-browser-unsafe [URL...] a second Tor Browser, its own user, at Safer: some JavaScript
# mullvad-browser [URL...] Mullvad Browser as its own user
# browser-sandbox clip [in|out] [NAME] move one line of text in or out of an open
# browser (NAME: tor-browser, tor-browser-unsafe or mullvad-browser)
# Each runs as its own user, in a window system of its own, with no way to your files,
# your screen or your other windows, and a firewall keeps it off your computer and network.
# Nothing crosses by itself. browser-sandbox clip shows you the text first, and takes
# one line of plain text: letters, numbers and ASCII punctuation, so no look-alike letters
# and nothing that can steer your terminal. Files: ~/Browsers/NAME.
# /usr/bin/torbrowser-launcher and /usr/bin/mullvad-browser, by their full path, run as you.
# shellcheck disable=SC2154 # rt, share, sock, audio_arg: set by the wren-sandbox functions
set -euo pipefail
die() { printf '%s: %s\n' "${0##*/}" "$*" >&2; exit 1; }
SESSION=/usr/local/libexec/browser-session
BROWSERS="tor-browser tor-browser-unsafe mullvad-browser"
# One line, plain text. Long enough for a long address.
CLIP_MAX=4096
name_ok() { case ${1:-} in tor-browser | tor-browser-unsafe | mullvad-browser) ;; *) return 1 ;; esac; }
[ "$(id -u)" -ne 0 ] || die "run it as your normal user, not root."
rt=${XDG_RUNTIME_DIR:-}
[ -n "$rt" ] && [ -d "$rt" ] || die "XDG_RUNTIME_DIR isn't set."
command -v flock >/dev/null || die "flock is missing (package util-linux)."
# is_open NAME: its lock is held. (The only way to know: nothing else lasts.)
is_open() {
local free=1
exec 8>"$rt/$1.lock"
if flock -n 8; then free=0; fi
exec 8>&-
[ "$free" = 1 ]
}
# clip_ok TEXT: at most CLIP_MAX characters of printable ASCII, on one line
clip_ok() {
local LC_ALL=C
[ ${#1} -le "$CLIP_MAX" ] && [[ $1 =~ ^[\ -~]+$ ]]
}
clip() {
local way='' name='' open=() b text a
while [ $# -gt 0 ]; do
case $1 in
in | out) way=$1 ;;
*) name_ok "$1" || die "usage: browser-sandbox clip [in|out] [tor-browser|tor-browser-unsafe|mullvad-browser]"; name=$1 ;;
esac
shift
done
command -v wl-copy >/dev/null || die "wl-copy is missing (package wl-clipboard)."
if [ -z "$name" ]; then
for b in $BROWSERS; do
if is_open "$b"; then open+=("$b"); fi
done
[ ${#open[@]} -gt 0 ] || die "no sandboxed browser is open."
[ ${#open[@]} -eq 1 ] || die "more than one is open; name one: browser-sandbox clip ${way:-in|out} NAME"
name=${open[0]}
fi
is_open "$name" || die "$name isn't open."
if [ -z "$way" ]; then
printf 'Into %s, from your clipboard (i), or out of %s, to your clipboard (o)? ' "$name" "$name"
IFS= read -r a || a=
case $a in
i | I) way=in ;;
o | O) way=out ;;
*) die "nothing was moved." ;;
esac
fi
if [ "$way" = in ]; then
text=$(wl-paste -n -t text 2>/dev/null | head -c $((CLIP_MAX + 1))) || true
else
text=$(sudo -n -u "$name" "$SESSION" clip-out 2>/dev/null | head -c $((CLIP_MAX + 1))) || true
fi
# Checked before it is shown: from a browser, it isn't to be trusted
[ -n "$text" ] || die "that clipboard is empty, so nothing was moved."
clip_ok "$text" || die "that isn't one line of plain text of at most $CLIP_MAX characters, so nothing was moved."
if [ "$way" = in ]; then
printf 'Into %s:\n\n %s\n\nSend it? [y/N] ' "$name" "$text"
else
printf 'Out of %s:\n\n %s\n\nCopy it to your clipboard? [y/N] ' "$name" "$text"
fi
IFS= read -r a || a=
[[ $a =~ ^[Yy] ]] || die "nothing was moved."
if [ "$way" = in ]; then
printf '%s' "$text" | sudo -n -u "$name" "$SESSION" clip-in || die "it didn't get there."
else
printf '%s' "$text" | wl-copy
fi
printf 'Done.\n'
}
case ${0##*/} in
tor-browser | tor-browser-safest | torbrowser-launcher) name=tor-browser ;;
tor-browser-unsafe) name=tor-browser-unsafe ;;
mullvad-browser) name=mullvad-browser ;;
*)
case ${1:-} in
clip) shift; clip "$@"; exit ;;
# A key: in a window of its own, which stays until you have read it
clip-window)
rc=0
( clip ) || rc=$?
printf 'Press Enter to close. '
IFS= read -r _ || true
exit "$rc" ;;
*) die "usage: browser-sandbox clip [in|out] [tor-browser|tor-browser-unsafe|mullvad-browser]" ;;
esac ;;
esac
for a in "$@"; do
[[ $a =~ ^https?://[^[:space:]]+$ ]] || die "only http:// and https:// addresses can be opened."
done
user=$name
WS=${WREN_SANDBOX_LIB:-/usr/local/libexec/wren-sandbox}
# shellcheck source=wren-sandbox
. "$WS" || die "$WS is missing. Run: wrenctl install $name"
ws_session
ws_tools "wrenctl install $name"
ws_allowed "$SESSION" "wrenctl install $name"
ws_lock "$rt/$name.lock" || die "it is already open."
# What it is handed is in a new folder of its own (ws_share): the session accepts only such a folder.
ws_share "$name"
ws_traps
ws_wayland "$name-wayland" "$name"
# The sound pipe: the browser writes, a player of yours reads and plays it. Only raw sound can
# come through it, and it hears nothing of yours.
ws_audio true "$name-audio.fifo"
rc=0
sudo -n -u "$name" "$SESSION" start "$share" "${sock##*/}" "$audio_arg" "$@" || rc=$?
exit "$rc"