Skip to main content
The sandbox limits what shell commands run by the model can write and which hosts they can reach. It applies to commands from the bash tool, including background tasks and sub-agents. It works alongside permissions: permissions decide whether a command runs at all, and the sandbox limits what an approved command can do. A command approved in auto or bypass mode is still confined by the sandbox.

Sandbox tiers

In an interactive session the sandbox has three tiers. The default is guarded.
  • open: no sandbox. Commands run with your full user permissions.
  • guarded: commands can write to the workspace, /tmp, the system temp directory, and common tool caches in your home folder (~/.cache, ~/.cargo, ~/.npm, ~/.o4/cache, and your platform cache folder). Paths that installed plugins in ~/.o4/plugins declare in their manifest’s permissions.paths are writable too, unless they overlap a protected location. Network access goes through an egress proxy that allows these hosts by default: registry.npmjs.org, registry.yarnpkg.com, crates.io, static.crates.io, pypi.org, files.pythonhosted.org, proxy.golang.org, github.com, gitlab.com and bitbucket.org. Requests to other hosts ask you first (see Network access).
  • airlock: commands can write only inside the workspace and have no network access. Your [sandbox] config doesn’t widen it.
In every tier except open, commands can still read most of the filesystem, so they can load programs and libraries. The sandbox blocks reads and writes to credential locations in your home folder: ~/.ssh, ~/.aws, ~/.gnupg, ~/.gpg, ~/.kube, ~/.docker, ~/.azure, ~/.gcloud, ~/.config/gcloud, ~/.config/gh, ~/.env, ~/.netrc, ~/.npmrc, ~/.pypirc and ~/.git-credentials, and o4’s own ~/.o4/settings.json, ~/.o4/.mcp.json, ~/.o4/trusted_workspaces.json, ~/.o4/managed and ~/.o4/plugins/.plugin-sources.json. Package installs (such as npm install, pip install or cargo add) get a narrower profile instead. They can write only to the workspace’s dependency folders and lock files (node_modules, target, .venv, venv, package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lock, bun.lockb, Cargo.lock and uv.lock), plus the tool caches in guarded, and in guarded they can reach only the package registries, not github.com, gitlab.com or bitbucket.org. In guarded this applies only when no config file has a [sandbox] table; in airlock it always applies.

Change the tier

Press Alt+S to cycle the tier: open, guarded, airlock, then back to open. The change applies to new commands right away, and a toast shows it, for example Sandbox: airlock (new commands). Moving from airlock to open removes the sandbox, so o4 asks you to confirm it first (“Switch sandbox to open?”). Press Y or Enter to switch, or N or Esc to keep airlock. You can rebind Alt+S with the cycle_sandbox_tier action in [keybindings]. o4 saves the tier you pick as default_tier in the workspace’s .o4/config.toml, so the next session in that workspace starts with it, as long as the workspace is trusted. In an untrusted workspace o4 doesn’t read that file, so the next session starts in the tier from ~/.o4/config.toml, or guarded. You can also set it yourself:
default_tier accepts open, guarded or airlock. Put it in ~/.o4/config.toml to change the default for every workspace. Project config files are read only in a trusted workspace. You can also change the tier in /config general: selecting the Sandbox tier row and pressing Enter cycles it the same way Alt+S does, with the same confirmation and the same saved default_tier. The row’s value always reads guarded, whatever the current tier; the toast and the Sandbox policy details show the real one. The Sandbox policy row below it is read-only. Press Enter on it to open a details view with the policy mode (the current tier), the paths commands can write to (or unrestricted in open), and a note on how your platform enforces the sandbox. Press Backspace to go back to the list. It describes the tier you pick with Alt+S, not a fixed --sandbox mode. When you start o4 in an untrusted directory whose .git folder was created in the last 24 hours, such as a fresh clone, o4 shows the suggestion “Fresh clone: use airlock sandbox?”. Accepting it switches this session to airlock without saving it as default_tier. The suggestion appears once per workspace.

Fixed sandbox with --sandbox

In print mode without --sandbox, commands always run in guarded: o4 doesn’t apply default_tier there. It also starts no egress proxy, so commands have no network unless a [sandbox] table without allowedDomains lifts the limit (see the warning under Network access). The same is true for commands you run yourself with ! in the prompt and for the checks o4 watch runs: they use the current tier but no egress proxy, so in guarded and airlock they have no network at all, not even to the registries on the allowlist. Use --sandbox to fix the sandbox for a whole run, for example in scripts or print mode:
With read-only or workspace-write, o4’s file tools also refuse paths outside the workspace and the --add-dir directories. default_tier and escape_hatch_binaries are ignored, and Alt+S has no effect on the fixed sandbox.

Extra writable directories

--add-dir makes another directory writable alongside the workspace. Shell commands can write there only with --sandbox workspace-write; without --sandbox, it affects o4’s file tools only. Repeat it for more than one:
-C (--cd) sets the working root instead of the directory you start in. o4 refuses a -C or --add-dir directory that is inside one of the protected credential locations listed above, or that contains one. So --add-dir ~ is refused, because your home folder contains ~/.ssh. Passing -C or --add-dir also limits o4’s file tools to the working root and the added directories, even without --sandbox.

Network access

In guarded, o4 starts a local egress proxy for each session and routes sandboxed commands through it with the HTTP_PROXY, HTTPS_PROXY and ALL_PROXY variables. Requests to allowed hosts go through. When a command connects through the proxy to any other host, o4 shows a prompt with three choices: You can also move with the arrow keys (or j and k) and press Enter. If you don’t answer within 5 minutes, the request is denied. In print mode and other runs without a TUI, requests to unlisted hosts are denied without a prompt. The same host rules apply to o4’s own fetch and web_search tools while the sandbox is guarded or airlock. In airlock they fail with a message telling you to press Alt+S.
The web_fetch tool doesn’t go through these host rules. It can reach any public host in every tier, including airlock, once you approve it (it asks once per session in ask mode). If you rely on airlock or guarded to keep data from leaving your machine, deny it with a permission rule:
To allow more hosts without being asked, list them in allowedDomains:
An entry matches the host and its subdomains: example.com also covers api.example.com. *.example.com matches subdomains only, and * matches every host. Only programs that honor the proxy variables can reach the network. On macOS, the sandbox allows network connections only to the proxy, so a program that ignores the variables gets no network at all.
On Linux, a sandbox that limits the network cuts it off completely, and the proxy can’t be reached from inside. So on Linux, commands in guarded and airlock have no network. o4’s own fetch and web_search tools still follow the host rules. Use open, or --sandbox danger-full-access, for commands that need the network.
If the proxy fails to start, or you turn it off with [sandbox.proxy] enabled = false, o4 shows a warning at startup and commands in guarded get no network. o4 starts the proxy only when a session begins in guarded or airlock, so after you switch from open to guarded with Alt+S, commands have no network until the next session.
These network limits need a host list to enforce. If a config file has a [sandbox] table that doesn’t set allowedDomains, commands in guarded get unrestricted network on Linux, and on macOS whenever the proxy isn’t running. Pressing Alt+S creates such a table, with only default_tier, in the project’s .o4/config.toml, and it takes effect from the next session in a trusted workspace. When a [sandbox] table exists, set allowedDomains in it.

How it’s enforced

  • macOS: o4 runs each command under sandbox-exec with a profile that denies writes outside the allowed paths and denies network except to the egress proxy.
  • Linux: o4 runs each command under bubblewrap (bwrap), which it looks for only at /usr/bin/bwrap and /bin/bwrap. The root filesystem is mounted read-only, allowed paths are mounted writable, and /tmp and /run are private and empty. If bubblewrap isn’t installed, o4 refuses to run commands in guarded or airlock and tells you to install it or switch to the open tier.
  • Other platforms: o4 has no sandbox backend and refuses to run commands in a sandboxed tier.
Inside the sandbox, commands see O4_SANDBOXED=1 on Linux.

Checks before a command runs

In a sandboxed tier, o4 also looks at the command text before running it:
  • If the command contains an absolute path outside the writable paths, o4 refuses it, even if the command only reads that path. For example, in guarded, cat /etc/hosts fails with “Path ‘/etc/hosts’ is outside the sandbox allowed paths”. Paths under /dev/ are allowed. Use a relative path, or a tool such as read, instead.
  • If the command contains an http:// or https:// URL whose host isn’t allowed, o4 refuses it. Without a [sandbox] table, the allowed hosts are the tier’s defaults (none in airlock). With one, in guarded, only the hosts in allowedDomains count for this check, and if you leave allowedDomains out, URLs aren’t checked. So when you set allowedDomains, also list any default host, such as github.com, that commands name directly.
  • If the bash call sets a working directory outside the writable paths, o4 refuses it.
Before 0.2.54, any [sandbox] config, even just default_tier, kept commands sandboxed when the tier was open. In 0.2.54 and later, open is really unsandboxed.

Configure the sandbox

The [sandbox] table can go in any o4 config file. List settings add up across files; default_tier and proxy come from the highest-precedence file that sets them.
o4 drops any allowedPaths entry that overlaps a protected credential location, even if you list it explicitly, and shows a warning at startup.
A program listed in escape_hatch_binaries runs with your full permissions whenever the model calls it by its absolute path. List only programs you trust with anything the model asks them to do.
See the configuration reference for types and defaults.