# Codex: five config rules and a five-check script for .codex/config.toml (free sample)

Version 1.0, 2026-10-29. By PaperChaseWebb. For developers who use Codex every day, alone or next to Claude Code, Hermes or Grok Bot. One Markdown file and one Python script, standard library only (Python 3.11 or newer, for tomllib). The script was tested on its worked example (6 findings across its five checks, then 0) and on a missing file (exit 2). It reads one config file; it was not run inside Codex or any other harness.

Most Codex setups that go wrong go wrong in the same file. The sandbox was set to danger-full-access to get past one prompt and never set back. approval_policy is never, so nothing stops. Network is on for every task. No model is pinned, so a default change switches it under you. An MCP server is installed at @latest with its token written into the config. This sample gives you the five rules that cover those, and a script that checks them in one config.toml.

## Install in one line

Save this page as FREE-SAMPLE.md, then:

```
python3 -c "import re;t=open('FREE-SAMPLE.md').read();open('cx_mini.py','w').write(re.search(r'~~~python\n(.*?)~~~',t,re.S).group(1))"
python3 cx_mini.py .codex/config.toml
```

## Five config rules

1. **Keep the sandbox on.** sandbox_mode is read-only or workspace-write. danger-full-access is for a throwaway machine, not your project.
2. **Let Codex ask.** approval_policy "on-request". never means nothing stops it, and "untrusted" is no longer supported.
3. **Network off by default.** [sandbox_workspace_write] network_access = false. Turn it on for one task, then off again.
4. **Pin the model.** Write the model you tested with, so a default change does not switch it under you.
5. **Pin servers, keep keys out.** Exact versions for MCP packages, https for remote servers, and key names in env_vars instead of values in env.

## The five checks

| Id | Check |
|---|---|
| M1 | sandbox_mode is read-only or workspace-write |
| M2 | approval_policy is on-request (or a granular table) |
| M3 | network_access in the workspace sandbox is off |
| M4 | model is set |
| M5 | MCP servers are pinned or https, and no key or token value is written in env |

Exit 0 clean, 1 findings, 2 missing or unreadable file. The script only reads.

## Worked example

A made-up project's .codex/config.toml. Before:

```toml
approval_policy = "never"
sandbox_mode = "danger-full-access"

[sandbox_workspace_write]
network_access = true

[mcp_servers.docs]
command = "npx"
args = ["-y", "@example/docs-mcp@latest"]
env = { DOCS_TOKEN = "dt-8812-local" }
```

python3 cx_mini.py .codex/config.toml prints:

```text
M1 sandbox_mode is 'danger-full-access'; use read-only or workspace-write
M2 approval_policy is 'never'; use on-request so Codex stops and asks
M3 network_access is on in the sandbox; turn it off and allow it per task
M4 no model is set; pin it so a default change does not switch it under you
M5 MCP server docs: package is not pinned to an exact version
M5 MCP server docs: DOCS_TOKEN is written into the config; list it in env_vars instead
5 checks, 6 findings
```

After the fix (sandbox back to workspace-write, approval on-request, network off, the model pinned, the server pinned to 1.4.2 and the token moved to env_vars):

```toml
model = "gpt-5-codex"
approval_policy = "on-request"
sandbox_mode = "workspace-write"

[sandbox_workspace_write]
network_access = false

[mcp_servers.docs]
command = "npx"
args = ["-y", "@example/docs-mcp@1.4.2"]
env_vars = ["DOCS_TOKEN"]
```

the same command prints:

```text
5 checks, 0 findings
```

## One prompt

"Read .codex/config.toml and ~/.codex/config.toml. Tell me the sandbox mode, approval policy, network setting and model you are actually running with, and which file each one came from. Change nothing."

## What the full bundle adds

The Codex bundle ($69) holds four packs in full (Spec-First Build Docs, Two-Agent Build and Test Loop, Planner Over Cheap Models and Token Saver, $101 bought separately) and a Codex Setup Runbook made only for the bundle: setup files for the owner, the pinned Codex version and model, every skill and server with its source, version, sha256 and who vetted it, and the ten setup changes that wait for a person; cx_check.py with fifteen read-only checks across AGENTS.md, config.toml and skills, including the 32 KiB AGENTS.md limit and the keys Codex ignores in a project config; cx.py, which gates those changes and takes the owner's yes only at a real terminal, tied to the sha256 of each request; a skill file for Codex, Claude Code and Hermes, an AGENTS.md snippet and a Grok Bot rules block; ten prompts, a failure table, a worked example and the tests. If you want it set up with you, the Install sprint, Manage and Expand are on paperchasewebb.ai.

## The script

~~~python
#!/usr/bin/env python3
"""cx_mini.py: five checks on one Codex .codex/config.toml.
Usage: python3 cx_mini.py .codex/config.toml   exit 0 clean, 1 findings, 2 missing or broken file. Reads only. Python 3.11 or newer."""
import re, sys
try:
    import tomllib
except ImportError:
    print("needs Python 3.11 or newer (tomllib)", file=sys.stderr); sys.exit(2)
SECRET = re.compile(r"(?i)(key|token|secret|password|pass|auth)")
def pinned(cmd, args):
    a = [str(x) for x in args if not str(x).startswith("-")]
    if cmd in ("npx", "bunx", "pnpx"):
        return bool(a) and bool(re.fullmatch(r".+@\d+\.\d+\.\d+", a[0]))
    if cmd == "uvx":
        return bool(a) and "==" in a[0]
    return True
def main(p):
    try:
        c = tomllib.load(open(p, "rb"))
    except FileNotFoundError:
        print(f"no such file: {p}", file=sys.stderr); return 2
    except Exception as e:
        print(f"not a valid config file: {e}", file=sys.stderr); return 2
    out = []
    if c.get("sandbox_mode") not in ("read-only", "workspace-write"):
        out.append(f"M1 sandbox_mode is {c.get('sandbox_mode')!r}; use read-only or workspace-write")
    ap = c.get("approval_policy")
    if not (ap == "on-request" or (isinstance(ap, dict) and "granular" in ap)):
        out.append(f"M2 approval_policy is {ap!r}; use on-request so Codex stops and asks")
    if (c.get("sandbox_workspace_write") or {}).get("network_access", False) is not False:
        out.append("M3 network_access is on in the sandbox; turn it off and allow it per task")
    if not c.get("model"):
        out.append("M4 no model is set; pin it so a default change does not switch it under you")
    for name, s in (c.get("mcp_servers") or {}).items():
        s = s or {}
        if s.get("url") and not str(s["url"]).startswith("https://"):
            out.append(f"M5 MCP server {name}: url is not https")
        elif not s.get("url") and not pinned(str(s.get("command", "")), s.get("args") or []):
            out.append(f"M5 MCP server {name}: package is not pinned to an exact version")
        for k in (s.get("env") or {}):
            if SECRET.search(k):
                out.append(f"M5 MCP server {name}: {k} is written into the config; list it in env_vars instead")
    for line in out:
        print(line)
    print(f"5 checks, {len(out)} findings")
    return 1 if out else 0
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(__doc__); sys.exit(2)
    sys.exit(main(sys.argv[1]))
~~~

PaperChaseWebb 💸
