Skip to content

Instantly share code, notes, and snippets.

@timkuijsten
Created July 21, 2026 07:17
Show Gist options
  • Select an option

  • Save timkuijsten/f69961f52898025667fcf29edceba20e to your computer and use it in GitHub Desktop.

Select an option

Save timkuijsten/f69961f52898025667fcf29edceba20e to your computer and use it in GitHub Desktop.
Ergonomic sandbox for Codex CLI
#!/bin/sh
# Ergonomic sandbox for Codex CLI using pasta(1) and bwrap(1). It isolates your
# homedir, the localhost network and environment from codex. Export
# CODEX_GH_TOKEN with a restricted fine-grained personal access token if you
# want to give it access to GitHub.
#
# v1.0 2026-07-07
# v1.1 2026-07-08 add set -u and use init function
# v1.2 2026-07-09 better localhost isolation + tweaks
# v1.3 2026-07-09 test ancestors of home dir
set -u
cwd=$(pwd -P)
home=$(cd -P -- "$HOME" && pwd -P) || {
echo "\$HOME not a directory: $HOME" >&2
exit 1
}
# Host dirs with user-created files that the sandbox must keep hidden.
# Refuse when $cwd is one of these dirs or an ancestor of one: binding
# $cwd into the sandbox would then expose the protected files.
# ($cwd is an ancestor-or-self of $p exactly when $p/ starts with $cwd/,
# hence $p is the case subject and $cwd the pattern.)
for p in "$home" /tmp /var/tmp /dev/shm "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
do
case ${p%/}/ in
"${cwd%/}"/*)
echo "refusing: $cwd is or contains $p, running here would expose it" >&2
exit 1 ;;
esac
done
command -v pasta >/dev/null || {
echo "pasta missing: apt install passt" >&2
exit 1
}
command -v rg >/dev/null || {
echo "rg missing: apt install ripgrep" >&2
exit 1
}
# If this is the first time Codex CLI is started, init.
init() {
if [ ! -d "$HOME/.codex" ]; then
mkdir -m700 ~/.codex
fi
}
init
# carry-over any CODEX_GH_TOKEN
# TODO use file instead of env and command-line
gh_flag=""
[ -n "${CODEX_GH_TOKEN:-}" ] && gh_flag="--setenv GH_TOKEN $CODEX_GH_TOKEN"
# We give it its own network to shield it from localhost, therefore we
# can't --unshare-all but have to unshare everything except --unshare-net.
exec pasta \
--config-net \
--no-map-gw \
--dns none \
--tcp-ports none \
--udp-ports none \
-- \
bwrap \
--ro-bind /usr /usr \
--ro-bind /etc /etc \
--symlink usr/lib /lib \
--symlink usr/lib64 /lib64 \
--symlink usr/bin /bin \
--symlink usr/sbin /sbin \
--proc /proc \
--dev /dev \
--tmpfs /tmp \
--tmpfs /run \
--tmpfs "$HOME" \
--unshare-user --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup \
--die-with-parent \
--new-session \
--clearenv \
--setenv PATH "$PATH" \
--setenv HOME "$HOME" \
--setenv USER "${USER:-$(id -un)}" \
--setenv TERM "${TERM:-vt100}" \
--setenv LANG "${LANG:-C.UTF-8}" \
\
$gh_flag \
--ro-bind /run/systemd/resolve/resolv.conf /run/systemd/resolve/stub-resolv.conf \
--bind ~/.codex ~/.codex \
--bind "$PWD" "$PWD" \
--chdir "$PWD" \
-- codex -s workspace-write "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment