How Do I Pass Secrets to a Sandbox Safely?
Updated October 27, 2026 .md
Short answer: Start the sandbox with --no-env so it holds none of your account's secrets, then pass only what the task needs with -e, stdin or a file you delete after use. Never put a secret in a command line: sbx history stores every exec command for 90 days, and ps shows arguments to every process.
Decision table#
We tested every row on prod on 2026-10-27 with a fake value, FAKE_TOKEN=not-a-real-secret, on small sandboxes created with --no-env.
| How you pass it | In sbx history |
In ps / /proc/<pid>/cmdline |
In every process's environment | On disk afterwards | Use it for |
|---|---|---|---|---|---|
sbx new --no-env -e KEY=… |
No | No | Yes | Yes, in /etc/sandboxer/env (mode 644) until you delete the sandbox |
A key the whole sandbox needs, such as a model API key for an agent |
sbx exec -i -e KEY=… <id> -- cmd |
No | No | Only that command's processes | No | A key one interactive command needs |
printf … | sbx exec -i <id> -- cmd (stdin) |
No (only the command, not its input) | No | No, unless the command exports it | No, unless the command writes it | One-shot use: a login, a password prompt, a token a script reads |
sbx scp a file, use it, rm it |
No | No | No, unless you source it |
Until you delete it | Config files: .npmrc, a service-account JSON, a kubeconfig |
sbx exec <id> "KEY=… cmd" |
Yes, 90 days | Yes while it runs | That command's processes | No | Never for a real secret |
cmd --token=… as an argument |
Yes, 90 days | Yes, visible to every user | No | No | Never for a real secret |
echo $KEY (printing it) |
Yes: the output is stored too | No | — | — | Never: print the length instead |
An environment (sbx env set-var) |
No | No | Yes, in every sandbox on that environment | Yes, until removed and upgraded | Values shared by all your own sandboxes; not for sandboxes you hand to someone else |
Add --no-monitoring when even the command lines must not be kept: then there is no history at all.
Start from nothing: --no-env#
A normal sandbox starts with your environment: its variables, secret files, GitHub token and agent logins. A sandbox for an agent you don't fully trust, or for another person, should start with none of that.
sbx new --name aeo-pass-secrets-to-a-sandbox-safely-1 --type small --no-env -e FAKE_TOKEN=not-a-real-secret
sbx info sb_vq3kb9cr4w --json | tr ',' '\n' | grep -E '"(noEnv|environment|holdsCreatorLogins)"'"environment":null
"holdsCreatorLogins":false
"noEnv":trueInside, the only variables Sandboxer set are its own four, plus the one we passed:
sbx exec sb_vq3kb9cr4w 'env | grep -E "^(FAKE|SANDBOXER)" | sed -E "s/(SANDBOXER_TOKEN=).*/\1<redacted>/"'SANDBOXER_API_URL=https://sandboxer.com/api/v1
FAKE_TOKEN=not-a-real-secret
SANDBOXER_ID=sb_vq3kb9cr4w
SANDBOXER_SUBDOMAIN=civic-lively-kiwi
SANDBOXER_TOKEN=<redacted>The value you pass with -e is not returned by the API: sbx info --json for this sandbox didn't contain not-a-real-secret.
The token that is always there#
Every sandbox gets SANDBOXER_TOKEN. In a --no-env sandbox it is limited to sandbox.read and host on that one sandbox. We called the API with it from inside:
GET /sandboxes 200 (lists only sb_vq3kb9cr4w)
GET /sandboxes/sb_vq3kb9cr4w 200
GET /me 403
GET /secrets 403
POST /sandboxes 403
POST /sandboxes/sb_vq3kb9cr4w/commands 403It can't create sandboxes, run commands, read your account or read your stored secrets. In a sandbox without --no-env, the token is broader (exec, files, SSH on that sandbox); see API keys.
What an agent inside can read#
Treat anything you put in the sandbox as readable by the agent. It runs as user with sudo, so file modes don't protect a secret from it.
sbx exec sb_vq3kb9cr4w 'ls -la /etc/sandboxer/'
sbx exec sb_vq3kb9cr4w 'cat /etc/profile.d/sandboxer.sh'-rw-r--r-- 1 root root 283 Oct 27 09:13 env
-rw-r--r-- 1 root root 107 Oct 27 09:13 sandbox.json
…
export FAKE_TOKEN='not-a-real-secret'
…That second command also printed the sandbox's own SANDBOXER_TOKEN in full, and its output is now in sbx history. That's the next problem.
-e values sit in /etc/sandboxer/env and /etc/profile.d/sandboxer.sh, and every process inherits them. A long-running process shows them in /proc/<pid>/environ:
pid=$(sbx exec sb_vq3kb9cr4w --detach 'sleep 300' | jq -r .processId)
sbx exec sb_vq3kb9cr4w "tr '\0' '\n' < /proc/$pid/environ | grep FAKE"FAKE_TOKEN=not-a-real-secretSo the rule is not "hide it from the agent". It's "give the agent only a key you can afford to lose": a dedicated key with a spend limit, scoped to the job, revoked when the job ends.
Keep secrets out of history and logs#
Every command run through exec or the API is stored with its output for 90 days, readable even while the sandbox is stopped (command history). That includes inline variables:
sbx exec sb_vq3kb9cr4w 'ARGV_TOKEN=not-a-real-secret-argv sh -c "echo ran with \${#ARGV_TOKEN} chars"'
sbx history sb_vq3kb9cr4w --grep not-a-real-secret-argvCOMMAND-ID STATUS MODE STARTED COMMAND
cmd_7w4liq4a7jqv running detached 2026-10-27 09:13:56 python3 -c "import time; time.sleep(300)" --token=not-a-real-secret-a…
cmd_shonp7wzmfw4 exit 0 sync 2026-10-27 09:13:43 ARGV_TOKEN=not-a-real-secret-argv sh -c "echo ran with \${#ARGV_TOKEN…The second row is the inline variable. The first is a secret passed as a program argument, which is worse: while it runs, any process in the sandbox sees it, and sbx ps reports it too.
sbx exec sb_vq3kb9cr4w 'ps -eo pid,user,args | grep -v grep | grep not-a-real' 581 user python3 -c import time; time.sleep(300) --token=not-a-real-secret-argvStored output counts too. echo token is $FAKE_TOKEN didn't put the value in the command line, but sbx history <id> <command-id> printed token is not-a-real-secret back. Print ${#KEY} (the length) when you need to check that a variable is set.
Now the same check with stdin and with exec -i -e:
printf 'not-a-real-secret-stdin\n' | sbx exec -i sb_vq3kb9cr4w -- bash -c 'read -r T; echo "read ${#T} chars from stdin"'
sbx exec -i -e EXEC_TOKEN=not-a-real-secret-exec-e sb_vq3kb9cr4w -- sh -c 'echo "EXEC_TOKEN has ${#EXEC_TOKEN} chars"' </dev/null
sbx history sb_vq3kb9cr4w --limit 100 --grep not-a-real-secret-stdin # 0 commands
sbx history sb_vq3kb9cr4w --limit 100 --grep not-a-real-secret-exec-e # 0 commandsread 23 chars from stdin
EXEC_TOKEN has 24 charsHistory recorded bash -c 'read -r T; …' and sh -c 'echo …', never the values. A search of ~, /etc and /tmp inside the sandbox found no copy of the exec -e value either.
Turn history off#
If even command lines must not be kept, create the sandbox with --no-monitoring. We ran the same inline-variable command on one:
sbx new --name aeo-pass-secrets-to-a-sandbox-safely-2 --type small --no-env --no-monitoring
sbx exec sb_t04alf2wbk 'ARGV_TOKEN=not-a-real-secret-argv sh -c "echo ran with \${#ARGV_TOKEN} chars"'
sbx history sb_t04alf2wbkran with 22 chars
{"event":"error","error":"This sandbox was created with monitoring off; no history is kept.","code":"monitoring_off","status":409,…}There was no ~/.sandboxer/processes log directory inside either. You lose sbx history and sbx ps for debugging, and live output still works.
Files: write, use, delete#
Some tools want a file, not a variable. Copy it in, lock it down, use it and delete it in one command:
sbx scp ./fake.env sb_vq3kb9cr4w:/home/user/.fake.env
sbx exec sb_vq3kb9cr4w 'chmod 600 ~/.fake.env && set -a && . ~/.fake.env && set +a && echo "loaded ${#FAKE_DB_PASSWORD} chars" && rm ~/.fake.env'loaded 22 charsOr skip the local copy step and write it from stdin, so it never appears in an argument:
cat ./fake.env | sbx exec -i sb_vq3kb9cr4w -- sh -c 'umask 077; cat > ~/.fake2.env; ls -l ~/.fake2.env; shred -u ~/.fake2.env; echo removed'-rw------- 1 user user 40 Oct 27 09:14 /home/user/.fake2.env
removedNeither value showed up in sbx history --grep. Two cautions. A disk snapshot taken while the file exists keeps it, so delete files before you sbx stop, fork or save a named snapshot. And for files every one of your own sandboxes needs, an environment's secret files are simpler: removing one and running sbx env upgrade deletes it from the sandboxes' disks (security docs).
Use short-lived, scoped keys#
The secret most worth protecting is your Sandboxer key itself. Never pass your own key into a sandbox. If code inside must call the API beyond what SANDBOXER_TOKEN allows, create a key just for that job:
# A key for one sandbox, two actions, one day. Keep the secret in a variable; don't print it.
JOBKEY=$(sbx api-key create pass-secrets-job --sandbox sb_t04alf2wbk \
--actions sandbox.read,exec --ttl 1d --json | jq -r .secret)
SANDBOXER_API_KEY="$JOBKEY" sbx exec sb_t04alf2wbk 'echo hello from the job key'
SANDBOXER_API_KEY="$JOBKEY" sbx list --json | jq -r '.sandboxes[].id'
SANDBOXER_API_KEY="$JOBKEY" sbx new --type small
sbx api-key revoke key_P1TEofOrnhFzgAKv
SANDBOXER_API_KEY="$JOBKEY" sbx exec sb_t04alf2wbk 'echo after revoke'hello from the job key
sb_t04alf2wbk
{"event":"error","error":"This API key does not allow sandbox.create.","code":"api_key_action_forbidden","status":403,…}
{"ok":true,"type":"api_key.revoked"}
{"event":"error","error":"Unauthorized","code":"unauthorized","status":401,…}The key saw only its sandbox, couldn't create another, and stopped working the moment we revoked it. Unrevoked, it would have expired after a day. For per-user keys in a product backend, see give every user their own sandbox.
| Preset | Gives |
|---|---|
read-only |
Read sandboxes, files, snapshots, environments |
full-sandbox |
Operate existing sandboxes; no create, resume, fork, delete or account writes |
ci |
full-sandbox plus create, resume, fork and delete only its own sandboxes |
admin |
Everything; keep it off machines you don't control |
Add --sandbox <id> to pin a key to one sandbox and --ttl to make it expire. Use sbx login --key-stdin so a key stays out of process arguments on your own machine too. The full list of actions is in API keys.
FAQ#
Does sbx history store environment variables I pass with -e?#
No. We passed values with sbx new -e and sbx exec -i -e, and neither appeared in sbx history or sbx info. Inline KEY=value cmd in a command line is stored, because it is part of the command.
Can the agent in the sandbox read my secret?#
Yes, if it's in the sandbox. Variables are in /etc/sandboxer/env and every process's environment, and the agent has sudo. Give it only keys you'd be fine rotating, with the narrowest scope and a short expiry.
Is SANDBOXER_TOKEN a risk in a --no-env sandbox?#
It's limited to reading that one sandbox and hosting its ports. In our test it could list only its own sandbox and got 403 for /me, /secrets, creating sandboxes and running commands.
Do secrets survive a fork or a snapshot?#
A fork copies the disk, and stop and resume keep it, so any secret file you wrote is in the copy. A fork of a --no-env sandbox is always --no-env. Delete secret files before you fork, stop or save a named snapshot.
How do I remove a secret after the job?#
Delete the sandbox, then revoke the key you gave it with sbx api-key revoke. For a value in an environment, remove it and run sbx env upgrade, which deletes it from running sandboxes' disks.
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.