sandbox/sandbox.sh
2026-07-23 11:00:57 +03:00

64 lines
1.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# Run Claude Code inside a bubblewrap sandbox.
# Filesystem: read-only except for the project dir and /tmp
# Network: shared with host (needed to reach docker-compose services on localhost)
#
# Prerequisite: bwrap must have the suid bit set to run unprivileged:
# sudo chmod u+s $(which bwrap)
#
# Usage: sandbox.sh [-o|--opencode] [-s|--shell] [args...]
# -o, --opencode launch opencode instead of claude (default)
# -s, --shell launch an interactive bash shell for inspection
set -euo pipefail
LAUNCH_OPENCODE=0
LAUNCH_SHELL=0
while [ $# -gt 0 ]; do
case "$1" in
-o|--opencode) LAUNCH_OPENCODE=1; shift ;;
-s|--shell) LAUNCH_SHELL=1; shift ;;
*) break ;;
esac
done
# Rename the tmux window to the project dir so it's easy to identify.
if [ -n "$TMUX_PANE" ]; then
tmux rename-window "$(basename "$PWD")"
fi
if [ "$LAUNCH_SHELL" -eq 1 ]; then
COMMAND=(bash --noprofile --norc)
elif [ "$LAUNCH_OPENCODE" -eq 1 ]; then
COMMAND=("$HOME/.opencode/bin/opencode")
else
COMMAND=("$HOME/.local/bin/claude")
fi
exec bwrap \
--ro-bind /usr /usr \
--symlink usr/bin /bin \
--symlink usr/sbin /sbin \
--symlink usr/lib /lib \
--symlink usr/lib64 /lib64 \
--ro-bind /etc/resolv.conf /etc/resolv.conf \
--ro-bind /etc/hosts /etc/hosts \
--ro-bind /etc/passwd /etc/passwd \
--ro-bind /etc/group /etc/group \
--ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \
--ro-bind /etc/localtime /etc/localtime \
--ro-bind /etc/ssl/certs /etc/ssl/certs \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
--tmpfs /home \
--bind "$PWD" "$PWD" \
--ro-bind "$HOME/.local" "$HOME/.local" \
--ro-bind "$HOME/.nvm" "$HOME/.nvm" \
--bind "$HOME/.claude" "$HOME/.claude" \
--bind "$HOME/.claude.json" "$HOME/.claude.json" \
--bind "$HOME/.opencode" "$HOME/.opencode" \
--unshare-pid \
--die-with-parent \
--chdir "$PWD" \
-- "${COMMAND[@]}" "$@"