> ## Documentation Index
> Fetch the complete documentation index at: https://docs.open4rena.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox

> Limit what shell commands from the model can read, write, and reach.

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](/safety/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`.

| Tier | Writes | Network |
| - | - | - |
| `open` | Anywhere you can write | Unrestricted |
| `guarded` | The workspace, temp directories and tool caches | Package registries and code hosts, plus hosts you allow |
| `airlock` | The workspace only | None |

* **`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](/extend/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](#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](/safety/workspace-trust). 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:

```toml theme={null}
[sandbox]
default_tier = "airlock"
```

`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](/safety/workspace-trust).

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](/guides/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](#network-access)).

The same is true for commands you run yourself with `!` in the prompt and for the checks [`o4 watch`](/guides/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:

```bash theme={null}
o4 --sandbox workspace-write --print -p "Run the tests and fix any failures"
```

| Value | Effect |
| - | - |
| `read-only` | Commands can't write anywhere and have no network. The file-writing tools (`write`, `edit` and the like) are refused too. |
| `workspace-write` | Commands can write to the workspace and any `--add-dir` directories. Network works as in `guarded`, with the default hosts plus `allowedDomains`, and requests to other hosts are denied without a prompt. If `[sandbox].allowedPaths` is set, commands can write only where its entries overlap those directories, so an `allowedPaths` that doesn't cover the workspace leaves the workspace read-only. |
| `danger-full-access` | No sandbox, like `open`. |

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:

```bash theme={null}
o4 --sandbox workspace-write --add-dir ../shared-lib --add-dir /tmp/scratch
```

`-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:

| Choice | Key | Effect |
| - | - | - |
| Allow once | `1` or `y` | Allow this request. |
| Always allow this domain | `2` or `a` | Allow the host for the rest of the session and add it to `allowedDomains` in `~/.o4/config.toml`. |
| Deny | `3`, `n` or `Esc` | Block the request. The command gets `403 Forbidden`. |

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`.

<Warning>
  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](/safety/permissions#permission-rules):

  ```toml theme={null}
  [[permissions.rules]]
  pattern = "web_fetch"
  action = "deny"
  ```
</Warning>

To allow more hosts without being asked, list them in `allowedDomains`:

```toml theme={null}
[sandbox]
allowedDomains = ["api.example.com", "*.internal.example.com"]
```

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.

<Note>
  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.
</Note>

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.

<Warning>
  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.
</Warning>

## 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.

<Note>
  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.
</Note>

## 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.

```toml theme={null}
[sandbox]
default_tier = "guarded"
allowedDomains = ["api.example.com"]
allowedPaths = ["~/scratch", "../shared-lib"]
escape_hatch_binaries = ["/usr/local/bin/docker"]

[sandbox.proxy]
enabled = true
```

| Key | Effect |
| - | - |
| `default_tier` | Tier a session starts in: `open`, `guarded` (default) or `airlock`. |
| `allowedDomains` | Extra hosts commands can reach in `guarded`. |
| `allowedPaths` | Extra writable paths in `guarded`. Paths can be absolute, start with `~/`, or be relative to the workspace. |
| `escape_hatch_binaries` | Absolute paths of programs that run outside the sandbox in `guarded`. A command runs unsandboxed only if its first word is the same absolute path. |
| `proxy.enabled` | Set to `false` to turn off the egress proxy. Commands in `guarded` then get no network. Default `true`. |

o4 drops any `allowedPaths` entry that overlaps a protected credential location, even if you list it explicitly, and shows a warning at startup.

<Warning>
  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.
</Warning>

See the [configuration reference](/reference/configuration#sandbox) for types and defaults.
