# How Do I Pass Secrets to a Sandbox Safely?

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

Source: https://sandboxer.com/guides/pass-secrets-to-a-sandbox-safely. Updated 2026-10-27.

**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](/docs/environments) (`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](/docs/environments): 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.

```bash
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)"'
```

```text
"environment":null
"holdsCreatorLogins":false
"noEnv":true
```

Inside, the only variables Sandboxer set are its own four, plus the one we passed:

```bash
sbx exec sb_vq3kb9cr4w 'env | grep -E "^(FAKE|SANDBOXER)" | sed -E "s/(SANDBOXER_TOKEN=).*/\1<redacted>/"'
```

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

```text
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 403
```

It 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](/docs/api-keys#the-in-sandbox-key).

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

```bash
sbx exec sb_vq3kb9cr4w 'ls -la /etc/sandboxer/'
sbx exec sb_vq3kb9cr4w 'cat /etc/profile.d/sandboxer.sh'
```

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

```bash
pid=$(sbx exec sb_vq3kb9cr4w --detach 'sleep 300' | jq -r .processId)
sbx exec sb_vq3kb9cr4w "tr '\0' '\n' < /proc/$pid/environ | grep FAKE"
```

```text
FAKE_TOKEN=not-a-real-secret
```

So 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](/docs/long-running#command-history)). That includes inline variables:

```bash
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-argv
```

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

```bash
sbx exec sb_vq3kb9cr4w 'ps -eo pid,user,args | grep -v grep | grep not-a-real'
```

```text
    581 user     python3 -c import time; time.sleep(300) --token=not-a-real-secret-argv
```

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

```bash
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 commands
```

```text
read 23 chars from stdin
EXEC_TOKEN has 24 chars
```

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

```bash
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_t04alf2wbk
```

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

```bash
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'
```

```text
loaded 22 chars
```

Or skip the local copy step and write it from stdin, so it never appears in an argument:

```bash
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'
```

```text
-rw------- 1 user user 40 Oct 27 09:14 /home/user/.fake2.env
removed
```

Neither 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](/docs/security#secrets)).

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

```bash
# 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'
```

```text
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](/guides/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](/docs/api-keys#scopes).

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

- [Environments and secrets](/docs/environments)
- [API keys](/docs/api-keys)
- [Give every user their own sandbox](/guides/give-every-user-their-own-sandbox)
- [Run Claude Code without permission prompts, safely](/guides/claude-code-skip-permissions-safely)
