# Inside a Sandbox

> What a managed sandbox has installed, how its disk and network behave, how secrets reach it, how to move files, and how to run an agent in it.

**URL:** https://inference.sh/docs/extend/sandbox-environment
**Last updated:** 2026-11-09

---

What a [managed sandbox](/docs/extend/managed-sandboxes) has installed, how its disk and network behave, how secrets reach it, how to move files, and how to run an agent in it.

→ [Sandbox quickstart](/docs/extend/sandbox-quickstart) · [Limits and pricing](/docs/extend/sandbox-limits) · [Sandboxes API](/docs/api/rest/sandboxes)

---

## The box

Each sandbox is a Firecracker VM on a platform engine. Its image is set by the platform; you cannot choose another one. The sandbox's `image` field names the one it boots.

| Item | Value |
|------|-------|
| OS | Ubuntu 24.04, packages upgraded when the image is built |
| User | `agent` (uid 1000), shell `/bin/bash`, passwordless `sudo` |
| Home and working directory | `/home/agent` |
| Hostname | `sbx-<sandbox id>` |
| Architectures | `linux/amd64` and `linux/arm64` images |

### Pre-installed

The image is small on purpose. It has:

- `belt` (the version is in the image tag, for example `v1.28.9-20261107.21`)
- `git`, `curl`, `openssh-client`, `ca-certificates`, `sudo`, `util-linux`

There is no Python, Node.js, Docker, build toolchain, browser, or coding-agent CLI. Install what you need with `apt` or the tool's installer:

```bash
belt sandbox exec dev -- sudo apt-get update
belt sandbox exec dev -- sudo apt-get install -y python3 python3-pip build-essential
```

Files you write on the writable disk survive a normal stop and start on the same engine while `disk_kept` is true (see [Disk](#disk)). They are gone when the disk is new: you asked for `fresh_disk` on start, the kept disk expired, the platform gave it up, or the box restarted on another engine. Put `apt` installs and other setup in a script you re-run when the disk is fresh.

### What runs on boot

The box starts `belt remote start --agents --exec --name <sandbox name>` as `agent`. That daemon is the sandbox's [remote](/docs/extend/remote): it runs your commands (`--exec`) and harness agent sessions (`--agents`). The box ends when the daemon exits, and the platform restarts it (see [Lifetime](/docs/extend/sandbox-limits#lifetime)).

### Environment

Commands get the daemon's environment, plus any `--env` (CLI) or `env` (API) you pass to that command.

| Variable | Value |
|----------|-------|
| `INFERENCE_API_URL` | The API the box talks to |
| `INFERENCE_API_KEY` | A placeholder for the box key (see [Secrets](#secrets)) |
| `INFSH_SANDBOX_ID`, `INFSH_SANDBOX_NAME` | This sandbox |
| `BELT_NON_INTERACTIVE` | `1` |
| `INFSH_NO_AUTOUPDATE` | `1`; belt in the box does not update itself |
| `NODE_EXTRA_CA_CERTS` | The gateway's CA certificate, for Node.js tools |

---

## Disk

Each sandbox has a writable disk layered over the read-only image. `disk_mib` is its size.

When you stop a box, the engine that ran it **keeps** that writable disk. The next start (or an automatic restart that still has the same kept disk) boots back onto it, so files and installs from the last run are still there. While the sandbox is stopped, `disk_kept` and `disk_kept_until` on [`GET /sandboxes/{id}`](/docs/api/rest/sandboxes#get-sandbox) tell you whether the disk is still held and when it expires (`stopped_at` plus seven days). The engine may drop it earlier if its host runs short of disk.

To start with a clean disk instead, call `POST /sandboxes/{id}/start` with `{"fresh_disk":true}` while the sandbox is stopped. A new generation still gets a new box key; the remote id does not change.

If the kept disk is gone when you start, you get a new empty disk, as when the sandbox was first created. When the box fails to start on its kept disk 3 times in a row, the platform gives the disk up and `status_reason` starts with `its disk was lost:`. A disk the engine dropped for lack of space leaves no reason; `disk_kept` is false. Automatic restarts that cannot return to the keeping engine also start on a new disk.

Keep work you need outside the box when disks can be lost: push to git or upload (see [Files](#files)). There are no snapshots.

---

## Network

The box has outbound internet. All its traffic goes through the engine's egress gateway: TCP is redirected to the gateway, DNS goes to the gateway's resolver, and UDP is relayed. There is no `HTTP_PROXY` to set; tools work unchanged.

- Private, loopback and engine-host addresses are refused. The inference.sh API is the one exception.
- Sandboxes cannot reach each other.
- Nothing can connect in. There is no public URL or port forwarding.
- Platform deny rules refuse crypto-mining pools and the stratum protocol. Repeated attempts stop the sandbox (see [Network limits](/docs/extend/sandbox-limits#network)).

Traffic to hosts with no bound secret passes through as an opaque tunnel. For hosts with a bound secret, the gateway terminates TLS with its own CA so it can add the credential. The box trusts that CA: it is in the system store, and `NODE_EXTRA_CA_CERTS` points Node.js at it.

---

## Secrets

Secret values never enter the box. The box holds placeholders (`infsh_ph_...`). When a request to a bound host leaves the box, the gateway swaps the placeholder for the real value. It places values only in credential positions: the `Authorization` header (Basic auth too), or the header or query parameter the provider uses. Never in the path or body. A placeholder sent to a host it is not bound to is refused.

### The box key

Each start mints an API key for the box, bound to the API host. `INFERENCE_API_KEY` in the box is its placeholder. The key holds `remotes:read` and `remotes:write`, and only for this sandbox's own remote. It lets the daemon stay connected. It cannot run apps, read files, use secrets, or create sandboxes. See [Sandboxes API](/docs/api/rest/sandboxes).

To call inference.sh from inside the box, pass a key of your own to the command. A short-lived token limits the damage if the box leaks it:

```bash
TOKEN=$(belt auth token --ttl 1h)
belt sandbox exec dev --env INFERENCE_API_KEY=$TOKEN -- belt app run <namespace/app> -i '{...}'
```

Anything running in the box can read a value you pass this way. The API returns only the names of `env` entries, never their values.

### Provider logins

To give the box a GitHub (or other provider) login, add it at create with `credentials` in the API or `--credential` in the CLI. The login stays in the workspace; the box gets a placeholder bound to the provider's hosts. See [Sandboxes API](/docs/api/rest/sandboxes) for `auto` and `ask` modes.

```bash
belt sandbox create repo --credential github-app --wait
belt sandbox exec repo -- git clone https://github.com/your-org/private-repo
```

Git is set up for this. `/etc/gitconfig` answers HTTPS credentials for `github.com` and `gist.github.com` with user `x-access-token` and the password `$GITHUB_TOKEN`, or the marker `infsh-fill` when it is unset. The gateway replaces the marker with the bound login.

Use `infsh-fill` the same way for any tool that asks for a key on a bound host. The tool sends the marker, and the gateway puts the login in.

### Plain environment variables

You cannot attach environment variables or workspace secrets to a sandbox at create. Pass them per command with `--env KEY=value` (CLI) or `env: ["KEY=value"]` (API).

---

## Files

There is no file upload or download route for a sandbox. Move files with commands:

| Direction | How |
|-----------|-----|
| Into the box | `git clone`, `curl` a URL, or `belt file download` a [file](/docs/api/rest/files) you uploaded, with your own key |
| Small text in | Pass it in a command: `belt sandbox exec dev -- bash -c 'cat > notes.txt <<EOF ... EOF'` |
| Out of the box | Print it and read the command output, push it to git, or upload it from the box with your own key (`belt file upload`, with `--env INFERENCE_API_KEY=...`) |

```bash
# Upload a local file to inference.sh, then fetch it in the box
TOKEN=$(belt auth token --ttl 1h)
url=$(belt file upload ./data.csv --jq .uri)
belt sandbox exec dev --env INFERENCE_API_KEY=$TOKEN -- belt file download "$url" -o data.csv

# Read a file back
belt sandbox exec dev -- cat report.md > report.md
```

Command output streams in chunks of base64 through [`GET /execs/{id}/output`](/docs/api/rest/exec-runs#read-output). Large binaries are better uploaded from the box.

Files attached to a harness chat on the sandbox are downloaded into the box for that chat (https only, up to 50 MB and 60 seconds per file). See [Attachments](/docs/extend/remote#attachments).

---

## Agents in the box

There are three ways to put an agent to work on a sandbox.

**1. A workspace agent drives the box.** Turn on the agent's **Remotes** internal tools. The agent runs commands on the sandbox's remote with `remote_exec` (through `sh -c`, default timeout 600 s, the last 30000 characters of output). See [Remote tools](/docs/api/agent/internal-tools#remote-tools).

**2. A harness runs in the box.** The daemon runs with `--agents`, so harness sessions work once a harness is installed and signed in. The image has none. Install one with `exec`, sign it in, and the daemon finds it within 5 minutes. Then launch it from **Resources** → **Remotes** or with [`POST /remotes/{id}/sessions`](/docs/api/rest/remotes#launch-harness-chat). See [Agent harness sessions](/docs/extend/remote#agent-harness-sessions). The harness and its sign-in are gone on the next start.

**3. Your own agent calls the box.** From your machine or CI, an agent uses `belt sandbox exec` or the [Exec runs API](/docs/api/rest/exec-runs). Use `--json`, `--no-input` and the [exit codes](/docs/extend/cli-setup#exit-codes) for a stable contract.
