Which Environment Variables Does a Sandbox Have?
Updated October 27, 2026 .md
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:
sbx new --type small --name aeo-environment-variables-inside-a-sandbox-1 -e TENANT_ID=acme -e FEATURE_X=1# 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
donesbx exec $id "$(cat envdump.sh)"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:
sbx new --type small --name aeo-environment-variables-inside-a-sandbox-2 --no-env -e GREETING=helloThe 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:
sbx exec $id 'echo ${SANDBOXER_TOKEN:0:8}… length ${#SANDBOXER_TOKEN}'sbx_sbk_… length 48Where the variables live#
The guest agent writes three files. They are rewritten whenever the configuration changes, so don't edit them:
sbx exec $id 'cut -d= -f1 /etc/sandboxer/env; cat /etc/sandboxer/sandbox.json'# 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:
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]"'sbx exec $id "$(cat shells.sh)"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:
[Service]
Type=oneshot
EnvironmentFile=/etc/sandboxer/env
ExecStart=/bin/sh -c 'echo "with EnvironmentFile: id=$SANDBOXER_ID tenant=$TENANT_ID" > /tmp/with.txt'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:
sbx exec $id "TENANT_ID=globex printenv TENANT_ID"globexThe 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:
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:
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). 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).
Call the API about the sandbox itself#
Code inside the VM can look itself up without any key of yours:
curl -sS "$SANDBOXER_API_URL/sandboxes/$SANDBOXER_ID" \
-H "Authorization: Bearer $SANDBOXER_TOKEN" \
| jq -c '.sandbox | {id,state,type,environment,noEnv}'{"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:
(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 listhttps://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). 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-envsandbox is always--no-env. - Resuming with
--no-envis one way: your secrets are removed from the restored disk and don't come back (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 aftersbx 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#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.