Course · Codex · Approvals & sandbox
Network is off by default - and how to widen the fence on purpose
Under workspace-write - the sandbox level most interactive work runs in, entered with codex -s workspace-write (plus -a on-request, the approval policy that pauses when an action needs to cross the sandbox boundary) - the agent can edit the project. Sooner or later a task arrives that also needs the internet: fetch a live exchange rate, download a data file, query an API. In that comfortable daily combination you expect it to just work. It doesn’t, and the way it fails is the single most important thing to understand about the workspace-write sandbox.
Network is a separate permission
Section titled “Network is a separate permission”An illustrative reconstruction, not a captured run - the exact wording of the refusal varies by version; the behavior is the documented default:
> fetch today's EUR-to-USD rate from the exchange-rate API and cache it for the euro transaction
• Read app/main.py • Read app/models.py • Ran python -c "import httpx; httpx.get('https://api.exchangerate.host/latest')" blocked - network access is disabled in this sandboxRead that carefully, because the instinct is to think the agent did something wrong. It didn’t. In workspace-write, outbound network access is off by default. The sandbox lets the agent write inside the workspace - but reaching the internet is a separate permission, and it starts closed. “Can write the project” and “can open a socket” are different capabilities, and workspace-write grants only the first. Under on-request, the blocked call surfaces as an approval prompt: approve it and the call runs as an exception beyond the default boundary. That works, but it relitigates the network on every crossing - which is why the deliberate, scoped route below is usually the better answer. Even inside one sandbox setting, “where” and “how far” are separate fences.
The default is deliberate, and worth understanding precisely. Damage that leaves your machine - exfiltrating a secret, posting your records to some endpoint, pulling code from a URL and running it - needs the network, and this boundary removes that path. It is a control boundary on network-mediated disclosure and downloads, not a general safety guarantee: the writes the sandbox does allow (your project, its temp directories) can still corrupt or destroy local files, exactly as any collaborator with write access could. Hitting this wall isn’t a bug to route around - it’s the boundary asking you to decide, explicitly, before data leaves your machine.
Opening network on purpose
Section titled “Opening network on purpose”When the network is what the task legitimately needs, you open it deliberately, in config, scoped to the workspace-write sandbox:
# ~/.codex/config.toml or .codex/config.toml[sandbox_workspace_write]network_access = trueTwo conditions on that comment line. First, ~/.codex/config.toml is your user config and applies everywhere; a .codex/config.toml inside the project applies only where Codex trusts that project, and an untrusted project skips project-local config entirely - the config reference documents per-project trust_level. Second, the flag opens outbound access in general, not just the one API in your task; it does not scope network to a host.
With that in place, the sandbox permits outbound connections - whether a particular call then succeeds still depends on the usual suspects (DNS, credentials, the service being up). The rest of the fence is untouched: writes still land only in the workspace and its temp directories, and the approval checkpoint still governs other kinds of escalation. You’ve widened exactly one capability, the one this task actually requires. Reaching for danger-full-access to “just let it use the internet” would also hand it your entire filesystem. network_access gives it the network and nothing else.
Widening which directories it can write
Section titled “Widening which directories it can write”The same scoped-widening logic applies to the filesystem. Say the task’s data lives outside the repo - downloaded bank statements kept in ~/budget-data/, raw exports you deliberately keep out of git. The work will need to read and write there, and workspace-write fences writes to the workspace, which is working directory plus temp. Two ways to extend, both surgical.
A one-off, per launch - --add-dir adds a writable root for this session only:
codex -s workspace-write --add-dir ~/budget-dataOr persistently, in the same [sandbox_workspace_write] block, with writable_roots (shown here without the network line, which stays off unless your task needs it):
[sandbox_workspace_write]writable_roots = ["~/budget-data"]Either way the agent can now write ~/budget-data and the workspace - and the extension behaves like the rest of the writable scope: temp directories remain writable, and protected paths such as .git keep their read-only protection inside any writable root (the agent approvals and security docs list the protected paths; the config reference documents each key’s exact read and write scope). Note what you did not do: switch to danger-full-access to get one data directory. --add-dir and writable_roots are preferred precisely because they grant the one directory you need instead of the whole disk. Keep the wall; cut one door in it.
Widen by exactly one capability
Section titled “Widen by exactly one capability”workspace-write ships with a tight, sensible fence: the working directory plus temp directories, no internet. When a task genuinely needs more reach, the move is always the same: widen the fence by the exact amount required and no more. network_access opens the network; writable_roots and --add-dir open specific directories. The keys, their defaults, and the temp-directory toggles (exclude_slash_tmp, exclude_tmpdir_env_var) are documented in the config reference.
Typing these flags repeatedly, or editing config for one task the next task shouldn’t inherit, is its own friction. If you will reuse a posture, there is a cleaner way to carry it around as one named thing - bundle the axes into a profile.