Codex

OpenAI's CLI coding agent with an external launcher boundary and kernel sandbox enforcement.

Last updated

Cloud API TypeScript

1. Installation

Prerequisites

  • macOS 13+ or Linux (kernel 5.13+)
  • Node.js 22+
  • An OpenAI API key (or GitHub Copilot subscription for OAuth)

Install Codex

brew install --cask codex

Install the preferred stack

brew tap nvk/tap
brew install nvk/tap/agent-bondage
brew install nono

# Optional, for API-key launches instead of OAuth tokens on disk:
brew install nvk/tap/envchain-xtra

Verify

codex --version
bondage --help
nono --version
envchain --version  # if installed

2. nono Profile

Codex has its own internal sandbox, but nono adds kernel-level enforcement that the tool itself cannot bypass. In the preferred setup, bondage chooses the exact target and the external custom-codex profile before Codex starts.

What the sandbox allows

ResourceAccessWhy
Current working directoryRead + WriteProject files
api.openai.comNetworkAPI calls
~/.codex/ReadConfig and auth
/dev/tty, /dev/nullRead + WriteTerminal I/O
/dev/urandomReadCrypto randomness
Double sandbox: Codex ships with its own application-level sandbox. nono adds a second, kernel-level layer. Even if a prompt injection disables Codex's internal sandbox, nono's restrictions remain enforced by the OS kernel.

3. Optional envchain-xtra

Codex supports two auth methods. The preferred launcher path is still bondage + nono either way. Use envchain-xtra only if you want direct API keys instead of OAuth tokens on disk.

Store your API key

envchain --set codex OPENAI_API_KEY

API key vs OAuth

MethodCredential storageRecommendation
envchain + API key macOS Keychain (encrypted) Preferred — no plaintext on disk
OAuth via /connect ~/.codex/auth.json (plaintext) Use if Copilot subscription, but be aware tokens are on disk
OAuth tokens are not safer than API keys. The refresh tokens stored in auth.json are long-lived and grant the same access. They sit in plaintext JSON just like a raw API key would.

4. bondage Wrapper

Add this thin wrapper to your shell config:

codex() {
  bondage exec codex ~/.config/bondage/bondage.conf -- "$@"
}

Sample stack snippets

Assuming your shared [global] block already exists in ~/.config/bondage/bondage.conf, this is a minimal Codex shape to adapt:

# ~/.config/bondage/bondage.conf
[profile "codex"]
use_envchain = false
use_nono = true
nono_profile = codex
touch_policy = none
target_kind = native
target = /absolute/path/to/codex
target_fp = sha256:replace-me
nono_allow_cwd = true
nono_allow_file = /dev/tty
nono_allow_file = /dev/null
nono_read_file = /dev/urandom
env_set = GIT_TERMINAL_PROMPT=0
{
  "extends": "codex",
  "meta": {
    "name": "codex",
    "description": "Codex with project-only access"
  },
  "policy": {
    "add_deny_access": ["/Volumes"]
  },
  "workdir": {
    "access": "readwrite"
  }
}

If you prefer API-key mode instead of OAuth tokens on disk, switch the profile to use_envchain = true and add namespace = codex.

How it works

  1. The shell wrapper passes only the profile name and your arguments
  2. bondage verifies the exact Codex target
  3. bondage applies the external nono profile
  4. If configured for API-key mode, bondage also launches through envchain-xtra

Reload your shell:

source ~/.zshrc

5. Operational resilience

The most common failure is not “Codex broke.” It is that the launcher path quietly stopped being the thing your shell actually runs.

Keep home-shell bootstrap stable

Do not point ~/.zshrc straight at a repo symlink and hope it stays readable forever. Keep a tiny stable bootstrap file in $HOME and let it source the real repo-backed config if available.

# ~/.zshrc
if [ -r "$HOME/src-repo/.dotfiles/zsh/.zshrc" ]; then
  . "$HOME/src-repo/.dotfiles/zsh/.zshrc"
elif [ -r "$HOME/src-repo/.dotfiles/ai/shell.zsh" ]; then
  . "$HOME/src-repo/.dotfiles/ai/shell.zsh"
fi

Treat upgrades as launcher changes

If Homebrew or your package manager upgrades nono or Codex, re-verify the launcher stack. A stale pinned path is enough to strand the whole wrapper chain.

bondage verify codex ~/.config/bondage/bondage.conf
bondage chain codex ~/.config/bondage/bondage.conf -- --help

Keep repair access explicit

If you maintain several profiles, keep a named repair tier such as codex-fix. It should still be sandboxed, but broad enough to repair shell startup files, launcher config, hooks, and profile trees without dropping straight to rawdog.

Keep sandbox recovery out of Codex hooks

Codex hooks can be useful for deterministic checks, but hook output is model input. Do not inject nono recovery workflows through SessionStart or PostToolUse hooks. A hook that tells the agent to offer choices, wait, stop, or create a profile can derail the real task. Put normal path grants in the managed nono profile or bondage config, then restart Codex.

6. Verification

Test the full chain

# Should launch Codex normally
bondage verify codex ~/.config/bondage/bondage.conf
bondage chain codex ~/.config/bondage/bondage.conf -- --help
codex --help

Confirm no plaintext keys

# Should return nothing
grep -r "sk-proj\|sk-live" ~/.codex/ ~/.zshrc 2>/dev/null

Troubleshooting

SymptomCauseFix
"Invalid API key" API-key mode selected but envchain namespace empty envchain --set codex OPENAI_API_KEY
Codex starts without the expected profile Running outside the wrapper Launch through bondage exec codex ..., not the bare binary
Wrapper commands disappeared after a shell restart Home shell startup depends on an unreadable sourced path Use a tiny stable ~/.zshrc bootstrap and keep the real logic below it
"Permission denied" on ~/.codex/ nono profile too restrictive Add ~/.codex to the managed nono profile or bondage nono grants, rerun bondage verify/bondage chain, then restart Codex
Wrapped launch broke after upgrading nono Launcher still pins an old package-manager path Re-pin the launcher config and rerun bondage verify
Shell name → bondage → [envchain-xtra] → nono → Codex Convenience Launch policy Optional secrets Kernel sandbox Actual agent