
Codex in the Jail

I wanted to run codex-cli in a real sandbox on macOS. The goal was simple: keep the tool useful, but stop it from touching anything outside my working tree. This is the story of two tries: a native macOS sandbox first, then a container‑based setup that finally worked.
Attempt 1: macOS sandbox-exec
My first idea was to use sandbox-exec with a strict “deny by default” profile and poke holes only where needed.
% sandbox-exec -f codex-full.sb /opt/homebrew/bin/codex
zsh: abort sandbox-exec -f codex-full.sb /opt/homebrew/bin/codexProfile (trimmed to the essentials):
(version 1)
(trace "/private/tmp/codex.trace")
(deny default)
(allow file-read* file-write*
(subpath "/Users/jhartman/scripts/codex"))
(allow file-read*
(subpath "/System")
(subpath "/usr/lib")
(subpath "/usr/share")
(subpath "/Library"))
(allow file-read*
(subpath "/opt/homebrew"))
(allow file-read*
(subpath "/usr/lib/dyld")
(subpath "/var/db/dyld"))
(allow file-read* file-write*
(subpath "/dev"))
(allow file-read* file-write*
(subpath "/private/tmp"))
;; OpenAI config
(allow file-read*
(subpath "/Users/jhartman/.config/openai"))
(allow process*)
(allow mach-lookup)
(allow network*)The sandbox denied root reads and sysctl probes:
Sandbox: codex-aarch64-apple-darwin(...) deny(1) file-read-data /
Sandbox: codex-aarch64-apple-darwin(...) deny(1) sysctl-read security.mac.lockdown_mode_state
Sandbox: codex-aarch64-apple-darwin(...) deny(1) sysctl-read kern.bootargsIn practice this meant:
codex(via Node + system libraries) performs early environment checks that touch/andsysctl.- Allowing
file-read-data /would effectively expose the whole filesystem, which defeats the point. - I could keep expanding the profile, but I’d end up re‑creating “allow almost everything” just to get it to start.
Verdict: sandbox-exec is too brittle for this use case. It’s great for a fixed, minimal app, but not for a CLI that probes the system at startup.
Attempt 2: Container on Apple Silicon (no Docker)
Next idea: containers. I didn’t want Docker, so I used Apple’s native container runtime on Apple Silicon. It gives me a real filesystem boundary and a predictable runtime without installing a massive daemon.
Build the image
- Clone the repo:
https://github.com/openai/codex.git- Patch
codex/codex-cli/Dockerfile:
diff --git a/codex-cli/Dockerfile b/codex-cli/Dockerfile
index 21a90a483..c6f9afe39 100644
--- a/codex-cli/Dockerfile
+++ b/codex-cli/Dockerfile
@@ -23,6 +23,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
unzip \
ripgrep \
zsh \
+ python3 python3-pip \
+ imagemagick libpng-dev libjpeg-dev libtiff-dev \
&& rm -rf /var/lib/apt/lists/*
@@ -39,8 +41,7 @@ ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
ENV PATH=$PATH:/usr/local/share/npm-global/bin
# Install codex
-COPY dist/codex.tgz codex.tgz
-RUN npm install -g codex.tgz \
+RUN npm install -g @openai/codex \
&& npm cache clean --force \
@@ -57,3 +58,5 @@ RUN chmod 500 /usr/local/bin/init_firewall.sh
USER node
+
+ENTRYPOINT [ "codex" ]
- Build script:
#!/usr/bin/env bash
pushd ../codex/codex-cli > /dev/null
git diff > ../../image/codex-cli.patch
git checkout .
git pull
git apply ../../image/codex-cli.patch
container build -t codex:latest
popd > /dev/nullRun it like a tool
#!/usr/bin/env bash
container system start
container run --rm -it \
-v "$PWD:/workspace" \
-v /Users/jhartman/scripts/codex/.codex:/home/node \
-w /workspace \
codex:latest "$@"Notes:
-v "$PWD:/workspace"gives Codex only the current repo.- I keep my token and preferences in
/Users/jhartman/scripts/codex/.codexand bind‑mount that to/home/node. - No Docker daemon, no virtualization UI — just a light container runtime on Apple Silicon.
Demo
Wrap‑up
The macOS sandbox approach was elegant on paper but fragile in reality. The container runtime is boring — and that’s exactly what I want. I now have Codex isolated, reproducible, and still fast enough to use daily.