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.