OK, so the world is collapsing, everything is getting hacked, all dependencies are probably stealing keys and mining crypto, slop is everywhere, and I'm part of the problem.
So what do I do? I'm going to isolate!
I had this in mind for a long while, but only recently LLM agents became good enough that I actually find it really useful to let them go without babysitting every command they are trying to run.
So the goals are:
- protect my systems from the slopus,
- protect my systems from malicious dependencies (at least somewhat),
- retain the usual UX.
Because of the last point, I am not going to be doing separate user account, or a separate VM, or play with dockers.
What I'm going to do is to use the BubbleWrap, to remount only parts of host system and home directory, and most of them in read-only mode. This way my tooling and general DX remains almost exactly the same, but if the Slopus has an episode of psychosis, or pulls in a cryptomining malware, there is only so much damage that it can do.
So the core of this system is the isolate script:
#!/usr/bin/env bash
set -euo pipefail
# Skip re-isolating if already inside an isolated environment
if [[ -n "${ISOLATE_ENV:-}" ]]; then
>&2 echo "warning: already isolated"
exec "$@"
fi
tiocsti_path="/proc/sys/dev/tty/legacy_tiocsti"
if [ ! -f "$tiocsti_path" ] || [ "$(cat "$tiocsti_path")" != "0" ]; then
>&2 echo "warning: TIOCSTI not disabled"
fi
args=()
args+=(
--dev-bind /dev /dev \
--proc /proc \
--tmpfs /tmp \
--tmpfs /run \
--setenv PROMPT_ENV_INDICATOR "isolated" \
--setenv ISOLATE_ENV "$(pwd)"
)
for p in \
/bin \
/usr/bin \
/etc \
/nix \
/run/current-system \
"$HOME/bin" \
"$HOME/.config" \
"$HOME/nix/dot" \
"$HOME/.gitconfig" \
"$HOME/.nix-profile" \
"$HOME/.local/share/direnv/allow/" \
; do
args+=(--ro-bind "$p" "$p")
done
for p in \
"$HOME/.cargo" \
"$HOME/.claude" \
"$HOME/.claude.json" \
"$HOME/nix/dot/.claude" \
"$XDG_RUNTIME_DIR/gnupg/S.gpg-agent" \
"$(pwd)"\
; do
args+=(--bind "$p" "$p")
done
if [[ -n "${NIRI_SOCKET:-}" && -S "$NIRI_SOCKET" ]]; then
args+=(--bind "$NIRI_SOCKET" "$NIRI_SOCKET")
args+=(--setenv NIRI_SOCKET "$NIRI_SOCKET")
fi
args+=(
--dir "$HOME/.gnupg" \
--chmod 0700 "$HOME/.gnupg" \
)
# Source extra config (e.g. set by auto-isolate) to allow
# project-specific additions to args
if [[ -n "${ISOLATE_EXTRA_CONFIG:-}" && -f "$ISOLATE_EXTRA_CONFIG" ]]; then
source "$ISOLATE_EXTRA_CONFIG"
# Hide the config file inside the sandbox by overlaying /dev/null
args+=(--ro-bind /dev/null "$ISOLATE_EXTRA_CONFIG")
fi
exec bwrap \
"${args[@]}" \
"$@"
I'm not going to spend time explaining every detail here, please read the Bubblewrap docs, or ask your local LLM.
But if you, dear reader, are planning to do the same/similar thing, you'll probably want to go over each path and consider implications. E.g. I'm using a fancy Yubikey SSH/GPG setup and I need to touch the hardware yubikey every time I ssh somewhere. Because of that I'm not afraid of mounting the ssh/gpg socket into the isolated environment.
Anyway, in a nutshell: isolate will run a given command
in an environment where almost only the current working directory
is writable, and rest are only the bare minimum parts needed
to get things working, mostly in read-only mode. Kind-a, mostly.
The goal here is a good enough security and robustness without
sacrificing almost any DX.
Thanks to using Nix, the first thing I'm going to use this isolate
script to wrap Slopus. That guy should never get a full
access to anything important directly.
Inside my system's flake.nix in the main overlay I have something like this:
claude-code =
let
orig = pkgs-unstable.claude-code;
in
final.writeShellScriptBin "claude" ''
exec env CARGO_TERM_QUIET=true PATH="${final.not-git}/bin:$PATH" ${./dot/bin/isolate} ${orig}/bin/claude "$@"
'';
This makes Slopus always start in an isolated environment,
so I can't forget about it. I also replace git with a wrapper
reminding Slopus that we're using Jujutsu, and make cargo build less
noisy by default to (maybe) save some tokens.
Then I want to automate entering isolated environment in every project I'm working on.
For that I have auto-isolate:
#!/usr/bin/env bash
set -euo pipefail
if [[ ! -x "$HOME/bin/isolate" ]]; then
>&2 echo "warning: $HOME/bin/isolate not found, skipping isolation"
exec "$@"
fi
dir="$(pwd)"
while true; do
if [[ -f "$dir/.isolate" ]]; then
exec env ISOLATE_EXTRA_CONFIG="$dir/.isolate" $HOME/bin/isolate "$@"
fi
if [[ "$dir" == "/" ]]; then
break
fi
dir="$(dirname "$dir")"
done
exec "$@"
auto-isolate will automatically call isolate for a given
command if it can find .isolate in current working dir
or any ancestor dir.
One could wire it in a shell startup file, but since I have
a whole system for working in the CLI heavily rooted in tmux,
I am going to put it in ~/.config/tmux/tmux.conf like this:
set-option -g default-command "$HOME/bin/auto-isolate ${SHELL}"
This way every time I'm starting a new pane in tmux,
it will trigger auto-isolate. Now I touch ~/lab/.isolate and
since all my dev projects are always inside ~/lab I pretty much
can't ever forget to isolate my projects.
If you haven't noticed before, the isolate script supports ISOLATE_EXTRA_CONFIG
which allows adding project-specific modifications to the isolated
environment. E.g. for a little GUI project I'm working on, I had to
create following .isolate file:
if [[ -n "$WAYLAND_DISPLAY" ]]; then
args+=(--ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY")
fi
args+=(
--bind /dev/dri/ /dev/dri/ \
--bind /dev/shm /dev/shm \
--bind /tmp/.X11-unix/ /tmp/.X11-unix/ \
--bind /run/opengl-driver/lib/ /run/opengl-driver/lib/
)
so it can show me the UI, when I run cargo r.