# Which Environment Variables Does a Sandbox Have?

> Every sandbox gets SANDBOXER_ID, SANDBOXER_SUBDOMAIN, SANDBOXER_API_URL and SANDBOXER_TOKEN, a key limited to that one sandbox. Add your own with sbx new -e KEY=value. Variables live in /etc/sandboxer/env, reach login shells and sbx exec, and reach systemd services that load that file.

Source: https://sandboxer.com/guides/environment-variables-inside-a-sandbox. Updated 2026-10-27.

**Short answer:** Every sandbox gets SANDBOXER_ID, SANDBOXER_SUBDOMAIN, SANDBOXER_API_URL and SANDBOXER_TOKEN, a key limited to that one sandbox. Add your own with sbx new -e KEY=value. Variables live in /etc/sandboxer/env, reach login shells and sbx exec, and reach systemd services that load that file.

## The variables Sandboxer sets

| Variable | Value | Normal sandbox | `--no-env` sandbox |
| - | - | - | - |
| `SANDBOXER_ID` | The sandbox id, e.g. `sb_zj7dc0vlg3` | Yes | Yes |
| `SANDBOXER_SUBDOMAIN` | Its stable slug, e.g. `exact-sunny-tundra` | Yes | Yes |
| `SANDBOXER_API_URL` | `https://sandboxer.com/api/v1` | Yes | Yes |
| `SANDBOXER_TOKEN` | A 48-character key that starts with `sbx_sbk_` and works only on this sandbox | Yes, sandbox-scoped | Yes, narrower: read and host only |
| Your environment's variables | From `sbx env set-var` on the sandbox's environment | Yes, if the environment passes secrets | No |
| Your `-e KEY=value` values | Set per sandbox at create | Yes | Yes |

Commands started with `sbx exec` also get `HOME=/home/user`, `USER` and `LOGNAME` (`user`), `SHELL=/bin/bash`, `LANG=en_US.UTF-8`, `LC_ALL` and a `PATH` that includes `~/.local/bin`, `~/.bun/bin`, `~/.cargo/bin`, `~/go/bin` and `/usr/local/go/bin`.

You can't set these names yourself: `SANDBOXER_ID`, `SANDBOXER_TOKEN`, `SANDBOXER_API_URL`, `SANDBOXER_SUBDOMAIN`, `SANDBOXER_CLI_TOKEN`, `AGENT_ID`, `ENVIRONMENT_ID`, `PRODUCT_MODE` and `SERVICE_PREVIEW_TOKEN`. Sandboxer sets the first four for you. The other five are reserved and were not set in either sandbox we tested.

## What `env` shows in a real sandbox

We created a normal sandbox with two variables of our own, then listed every variable. The script prints every name, but values only for names on an allowlist, so it can't leak a secret:

```bash
sbx new --type small --name aeo-environment-variables-inside-a-sandbox-1 -e TENANT_ID=acme -e FEATURE_X=1
```

```bash title="envdump.sh"
# Every name; values only for names on an allowlist
env | cut -d= -f1 | sort | while read n; do
  case "$n" in
    SANDBOXER_ID|SANDBOXER_SUBDOMAIN|SANDBOXER_API_URL|HOME|USER|LOGNAME|SHELL|PWD|LANG|PATH|SHLVL|TENANT_ID|FEATURE_X|GREETING)
      echo "$n=${!n}";;
    *) echo "$n=<redacted>";;
  esac
done
```

```bash
sbx exec $id "$(cat envdump.sh)"
```

```text
FEATURE_X=1
HOME=/home/user
LANG=en_US.UTF-8
LC_ALL=<redacted>
LOGNAME=user
PATH=/home/user/.local/bin:/home/user/.bun/bin:/home/user/.local/bin:/home/user/.cargo/bin:/home/user/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/snap/bin
PWD=/home/user
SANDBOXER_API_URL=https://sandboxer.com/api/v1
SANDBOXER_ID=sb_zj7dc0vlg3
SANDBOXER_SUBDOMAIN=exact-sunny-tundra
SANDBOXER_TOKEN=<redacted>
SHELL=/bin/bash
SHLVL=1
TENANT_ID=acme
USER=user
_=<redacted>
```

Then a sandbox for someone else, with none of the account's secrets:

```bash
sbx new --type small --name aeo-environment-variables-inside-a-sandbox-2 --no-env -e GREETING=hello
```

The same listing showed the same four `SANDBOXER_*` names, `GREETING=hello`, and nothing from the account's environment. The token was still there, with a narrower scope (see below). Our test account's `base` environment held no secrets, so the normal sandbox showed none either. On your account, a normal sandbox also lists your environment's variables, and a `--no-env` one never does.

Never print the token itself. To check it is present without leaking it:

```bash
sbx exec $id 'echo ${SANDBOXER_TOKEN:0:8}… length ${#SANDBOXER_TOKEN}'
```

```text
sbx_sbk_… length 48
```

## Where the variables live

The guest agent writes three files. They are rewritten whenever the configuration changes, so don't edit them:

```bash
sbx exec $id 'cut -d= -f1 /etc/sandboxer/env; cat /etc/sandboxer/sandbox.json'
```

```text
# Managed by sbx-guest. Changes are overwritten by the next /init.
FEATURE_X
SANDBOXER_API_URL
SANDBOXER_ID
SANDBOXER_SUBDOMAIN
SANDBOXER_TOKEN
TENANT_ID
{
  "apiUrl": "https://sandboxer.com/api/v1",
  "id": "sb_zj7dc0vlg3",
  "subdomain": "exact-sunny-tundra"
}
```

| File | Format | Read by |
| - | - | - |
| `/etc/sandboxer/env` | `KEY="value"` lines | `sbx exec`, systemd `EnvironmentFile=` |
| `/etc/profile.d/sandboxer.sh` | `export KEY='value'` lines | Login shells (`bash -l`, SSH, `sudo -i`) |
| `/etc/sandboxer/sandbox.json` | id, API URL, subdomain (no token) | The in-sandbox `host` command |

In sandboxes created from 16:07 UTC on October 27, 2026, the two files that hold the token are mode `0640`, owned by `root:user`: the sandbox user and root can read them, other users in the VM get "Permission denied". `sandbox.json` holds no token and stays readable. Sandboxes created before that have both files at `0644`, so any user in the VM can read the token there.

## Which processes see them

We checked each case in the normal sandbox:

```bash title="shells.sh"
env -i /bin/bash -c 'echo "env -i bash -c:  [$TENANT_ID]"'
env -i /bin/bash -lc 'echo "env -i bash -lc: [$TENANT_ID]"'
sudo sh -c 'echo "sudo sh -c:      [$TENANT_ID]"'
sudo -i sh -c 'echo "sudo -i:         [$TENANT_ID]"'
```

```bash
sbx exec $id "$(cat shells.sh)"
```

```text
env -i bash -c:  []
env -i bash -lc: [acme]
sudo sh -c:      []
sudo -i:         [acme]
```

A clean, non-login shell and plain `sudo` don't see the variables. A login shell does, because it sources `/etc/profile.d/sandboxer.sh`.

A systemd service sees them only if it loads the file:

```ini
[Service]
Type=oneshot
EnvironmentFile=/etc/sandboxer/env
ExecStart=/bin/sh -c 'echo "with EnvironmentFile: id=$SANDBOXER_ID tenant=$TENANT_ID" > /tmp/with.txt'
```

```text
with EnvironmentFile: id=sb_zj7dc0vlg3 tenant=acme
without: id=[] tenant=[]
```

The second line came from an identical unit without the `EnvironmentFile=` line.

## Per-command variables

To set a variable for one command, put it in front of the command:

```bash
sbx exec $id "TENANT_ID=globex printenv TENANT_ID"
```

```text
globex
```

The REST API takes an `env` object on `POST /sandboxes/{id}/commands`. A per-command value wins over the sandbox's own value for the same name, whether you send the command as a string (which runs in a login shell) or as an argv array:

```bash
curl -sS -X POST https://sandboxer.com/api/v1/sandboxes/$id/commands \
  -H "Authorization: Bearer $SANDBOXER_API_KEY" -H "Content-Type: application/json" \
  -d '{"command":"echo tenant=$TENANT_ID run=$RUN_ID","env":{"TENANT_ID":"globex","RUN_ID":"42"}}'
# {"exitCode":0,"stdout":"tenant=globex run=42\n"}

curl -sS -X POST https://sandboxer.com/api/v1/sandboxes/$id/commands \
  -H "Authorization: Bearer $SANDBOXER_API_KEY" -H "Content-Type: application/json" \
  -d '{"command":["printenv","TENANT_ID","RUN_ID"],"env":{"TENANT_ID":"globex","RUN_ID":"42"}}'
# {"exitCode":0,"stdout":"globex\n42\n"}
```

Until a fix on October 27, 2026, a string command re-read the profile file, so the sandbox's value won and the first call printed `tenant=acme`. A sandbox created before the fix keeps that behavior until it boots from disk (for example after a resize); a resume that restores memory keeps the old behavior.

Names you pass at create are checked before anything starts:

```text
sbx new -e SANDBOXER_ID=mine   ->  SANDBOXER_ID is reserved. (invalid_env, 400)
sbx new -e 1BAD=x              ->  Invalid environment variable name "1BAD". (invalid_env, 400)
```

Names match `[A-Za-z_][A-Za-z0-9_]{0,127}`, with at most 100 variables and 64 KB in total. On a name clash, a per-sandbox `-e` value beats the environment's value. Account-wide variables are set with `sbx env set-var` ([Environments](/docs/environments)). That changes every new sandbox, so it's out of scope here.

## What the in-sandbox token can do

We called the API from inside each sandbox with `SANDBOXER_TOKEN`. Same calls, same moment:

| Call | Normal sandbox | `--no-env` sandbox |
| - | - | - |
| `GET /sandboxes/$SANDBOXER_ID` | 200 | 200 |
| `GET /sandboxes` | 200, lists only itself | 200, lists only itself |
| `POST /sandboxes/$SANDBOXER_ID/host` | 200 | 200 |
| `POST /sandboxes/$SANDBOXER_ID/commands` | 200 | 403 |
| `GET /sandboxes/$SANDBOXER_ID/files` | 200 | 403 |
| `GET /sandboxes/$SANDBOXER_ID/snapshots` | 200 | 403 |
| `GET /me` | 200 | 403 |
| `POST /named-snapshots` (save) | 403 | 403 |
| `POST /sandboxes/$SANDBOXER_ID/stop` | 403 | 403 |
| `POST /sandboxes` (create) | 403 | 403 |
| `GET /secrets` | 403 | 403 |
| `GET /sandboxes/<the other sandbox>` | 403 `api_key_sandbox_forbidden` | 403 `api_key_sandbox_forbidden` |

Every refusal was `api_key_action_forbidden` naming the missing action (for example `sandbox.create`, `environment.read`). A normal sandbox's token can read the sandbox, prompt its agent, exec, read and write files, use SSH and the desktop, host ports and read its own snapshots. A `--no-env` sandbox's token can only read the sandbox and host its ports (`sandbox.read`, `host`). Neither can stop, fork, delete or create sandboxes, save snapshots, or touch your account's settings ([API keys](/docs/api-keys)).

## Call the API about the sandbox itself

Code inside the VM can look itself up without any key of yours:

```bash
curl -sS "$SANDBOXER_API_URL/sandboxes/$SANDBOXER_ID" \
  -H "Authorization: Bearer $SANDBOXER_TOKEN" \
  | jq -c '.sandbox | {id,state,type,environment,noEnv}'
```

```text
{"id":"sb_zj7dc0vlg3","state":"ready","type":"small","environment":"base","noEnv":false}
```

In the `--no-env` sandbox the same call returned `{"id":"sb_xtqjxxxue1","state":"ready","type":"small","environment":null,"noEnv":true}`, so a program can tell which kind it's in.

The token can also give one of the sandbox's ports a URL. We started a web server and hosted it from inside:

```bash
(cd /tmp && nohup python3 -m http.server 8080 >/dev/null 2>&1 &)
curl -sS -X POST "$SANDBOXER_API_URL/sandboxes/$SANDBOXER_ID/host" \
  -H "Authorization: Bearer $SANDBOXER_TOKEN" -H "Content-Type: application/json" \
  -d '{"port":8080}' | jq -r '.url | sub("\\?_token=.*"; "?_token=<redacted>")'
host list
```

```text
https://exact-sunny-tundra-8080.box.sandboxer.com?_token=<redacted>
8080   https://exact-sunny-tundra-8080.box.sandboxer.com (gated)
```

`host` is the in-sandbox command that reads the same variables ([hosting](/docs/hosting)). The URL is private by default: in the `--no-env` sandbox, the same call worked and the URL returned `401` without its token. Treat the `?_token=` URL like a password.

## Notes from the run

- The token is minted on each start, so don't cache it outside the sandbox. A fork gets its own token. A fork of a `--no-env` sandbox is always `--no-env`.
- Resuming with `--no-env` is one way: your secrets are removed from the restored disk and don't come back ([Environments](/docs/environments)).
- Changing a per-sandbox value means resuming with a new `env`, which replaces the whole per-sandbox set. Variables added to the sandbox's environment reach running sandboxes after `sbx env upgrade`.

## FAQ

### Can code inside the sandbox read SANDBOXER_TOKEN?

Yes. Every process and user in the VM can read it from the environment or `/etc/sandboxer/env`. It only works on that one sandbox, so a leak can't reach your account or your other sandboxes.

### Why is my variable empty in a systemd service?

systemd starts services with a clean environment. Add `EnvironmentFile=/etc/sandboxer/env` to the `[Service]` section, run `sudo systemctl daemon-reload` and restart the service.

### How do I give a --no-env sandbox an API key?

Pass it per sandbox: `sbx new --no-env -e THEIR_API_KEY=…`. It appears in that sandbox only, alongside the four `SANDBOXER_*` variables, and nothing from your account comes with it.

### Why did my per-command env value get ignored?

The sandbox was probably running before the October 27, 2026 fix, and you sent the command as a string with a name the sandbox also sets. A resume that restores memory keeps the old behavior, so create a new sandbox, or send an argv array or put `KEY=value` in front of the command.

### Can I override SANDBOXER_ID or SANDBOXER_TOKEN?

No. Create rejects reserved names with `400 invalid_env` before anything starts. Inside the VM you can export a different value in your own shell, but the API still sees the real token's scope.

## Related

- [Environments and secrets](/docs/environments)
- [API keys: the in-sandbox key](/docs/api-keys)
- [What can reach a sandbox, and what it can reach](/guides/what-can-reach-a-sandbox-and-what-it-can-reach)
- [Give every user their own sandbox](/guides/give-every-user-their-own-sandbox)
