Skip to content

Instantly share code, notes, and snippets.

@mnpenner
Last active December 15, 2025 13:00
Show Gist options
  • Select an option

  • Save mnpenner/137ccdccf7619dfc403c53ddd1b626e6 to your computer and use it in GitHub Desktop.

Select an option

Save mnpenner/137ccdccf7619dfc403c53ddd1b626e6 to your computer and use it in GitHub Desktop.
A jail for LLMs
#!/usr/bin/env bash
{
set -euo pipefail
# --- Verify HOME (we bindmount a bunch of $HOME paths) ---
if [[ -z "${HOME-}" || "$HOME" == "/" ]]; then
echo "[error] HOME is empty/invalid: '${HOME-}'" >&2
exit 1
fi
# --- USER fallback ---
USERNAME="${USER-}"
if [[ -z "${USERNAME}" ]]; then
USERNAME="$(whoami)"
fi
# 1. Setup
exec_file="$(command -v "${1:-$SHELL}")"
real_path="$(readlink -f "$exec_file")"
bin_name="$(basename "$exec_file")"
# Setup JAIL Root
JAIL="$(mktemp -d "${TMPDIR:-/tmp}/aijail.XXXXXX")"
CHROOT="$JAIL/mnt"
mkdir -p "$CHROOT"
echo "[debug] JAIL: $JAIL" >&2
# --- SETUP JAIL /etc ---
mkdir -p "$CHROOT/etc"
# --- FAKE /etc/passwd and /etc/group ---
uid="$(id -u)"
gid="$(id -g)"
cat > "$CHROOT/etc/passwd" <<EOF
root:x:0:0:root:/root:/usr/sbin/nologin
$USERNAME:x:$uid:$gid::$HOME:/bin/bash
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
EOF
cat > "$CHROOT/etc/group" <<EOF
root:x:0:
$USERNAME:x:$gid:
nogroup:x:65534:
EOF
# --- SETUP JAIL HOME ---
JAIL_HOME="$CHROOT$HOME"
mkdir -p "$JAIL_HOME"
# 2. Construct Explicit PATH
# Start with standard system paths (excluding sbin as requested)
JAIL_PATH="/usr/local/bin:/usr/bin:/bin"
# Prepend User Local Bin
if [[ -d "$HOME/.local/bin" ]]; then JAIL_PATH="$HOME/.local/bin:$JAIL_PATH"; fi
# Prepend Common Runtimes
if [[ -d "$HOME/.cargo/bin" ]]; then JAIL_PATH="$HOME/.cargo/bin:$JAIL_PATH"; fi
if [[ -d "$HOME/.bun/bin" ]]; then JAIL_PATH="$HOME/.bun/bin:$JAIL_PATH"; fi
# Prepend Active Node/NVM Path
HOST_NODE="$(command -v node 2>/dev/null || true)"
if [[ -n "$HOST_NODE" ]]; then
NODE_DIR="$(dirname "$HOST_NODE")"
if [[ "$NODE_DIR" != "/usr/bin" && "$NODE_DIR" != "/bin" ]]; then
JAIL_PATH="$NODE_DIR:$JAIL_PATH"
fi
fi
# 3. Base Arguments
ns_args=(
--mode o
--time_limit 0
--skip_setsid
--forward_signals
# --- ENVIRONMENT ---
--env TERM="${TERM:-xterm-256color}"
--env HOME="$HOME"
--env USER="$USERNAME"
--env LANG=C.UTF-8
--env 'PS1=\u:\w\$ '
# Explicit PATH
--env PATH="$JAIL_PATH"
# NVM Support vars
--env NVM_DIR
--env NVM_INC
--env NVM_BIN
# --- FILESYSTEM STRATEGY: STRICT CHROOT ---
--chroot "$CHROOT"
# --- SYSTEM MOUNTS (Read-Only) ---
--bindmount_ro /usr:/usr
--bindmount_ro /bin:/bin
--bindmount_ro /lib:/lib
--bindmount_ro /lib64:/lib64
--bindmount_ro /boot:/boot
# DNS + NSS (host truth)
--bindmount_ro /etc/resolv.conf:/etc/resolv.conf
--bindmount_ro /etc/hosts:/etc/hosts
--bindmount_ro /etc/nsswitch.conf:/etc/nsswitch.conf
# Time / locale (optional but sane)
--bindmount_ro /etc/localtime:/etc/localtime
--bindmount_ro /etc/timezone:/etc/timezone
--bindmount_ro /etc/os-release:/etc/os-release
# /etc extras
--bindmount_ro /etc/ssl:/etc/ssl
#--bindmount_ro /etc/alternatives:/etc/alternatives
#--bindmount_ro /etc/fonts:/etc/fonts
# --- RUNTIME MOUNTS (Read-Write) ---
--mount 'none:/dev:tmpfs'
--mount 'devpts:/dev/pts:devpts'
--bindmount /dev/null:/dev/null
--bindmount /dev/zero:/dev/zero
--bindmount /dev/random:/dev/random
--bindmount /dev/urandom:/dev/urandom
--bindmount /proc:/proc
--mount 'none:/tmp:tmpfs:size=512m'
--bindmount /var:/var
--bindmount /run:/run
--mount 'none:/dev/shm:tmpfs:size=256m'
# --- WORKSPACE MOUNT (Read-Write) ---
--bindmount "$PWD:$PWD"
--cwd "$PWD"
# --- NAMESPACES ---
--disable_clone_newnet
--disable_clone_newpid
--disable_clone_newipc
--disable_clone_newuts
--disable_clone_newcgroup
--disable_proc
# --- RELAXATION ---
--disable_rlimits
)
# 4. Mount User Configs (Split RO vs RW)
# List A: READ-ONLY (Tools & Global Configs)
ro_items=(
".nvm"
".bun"
".cargo"
".rustup"
".local"
".config"
".gitconfig"
)
for item in "${ro_items[@]}"; do
if [[ -e "$HOME/$item" ]]; then
ns_args+=( --bindmount_ro "$HOME/$item:$HOME/$item" )
fi
done
# List B: READ-WRITE (State, Logs, Caches)
rw_items=(
".codex"
".npm"
".cache"
".local/state"
".local/share"
".config/github-copilot"
)
for item in "${rw_items[@]}"; do
if [[ -e "$HOME/$item" ]]; then
ns_args+=( --bindmount "$HOME/$item:$HOME/$item" )
fi
done
# Codex API Key
if [[ "$bin_name" == "codex" ]]; then
if [[ -n "${OPENAI_API_KEY-}" ]]; then
ns_args+=( --env OPENAI_API_KEY )
fi
fi
# 5. Run
nsjail "${ns_args[@]}" -- "$real_path" "${@:2}"
}
@mnpenner
Copy link
Author

mnpenner commented Dec 15, 2025

This depends on nsjail: https://github.com/google/nsjail?tab=readme-ov-file#installation

Usage:

aijail.sh codex

Or whatever your favorite LLM CLI is. Works with bash to if you want a safe-ish place to run arbitrary commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment