Created
July 21, 2026 07:17
-
-
Save timkuijsten/f69961f52898025667fcf29edceba20e to your computer and use it in GitHub Desktop.
Ergonomic sandbox for Codex CLI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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