Appearance
Secrets
Store credentials scoped to your SSH identity, and they appear as environment variables in your boxes — like a PaaS's config vars. Set an API key, a database password, or a deploy token, without ever putting the value on a command line or in a file.
sh
# value is read from stdin — never in argv, shell history, or logs
printf '%s' "$MY_TOKEN" | ssh cli@box.hopbox.dev secret set GITHUB_TOKEN
ssh cli@box.hopbox.dev secret ls # names + scope, never values
ssh cli@box.hopbox.dev secret rm GITHUB_TOKENThen, in any of your boxes:
sh
ssh dev@box.hopbox.dev 'echo "$GITHUB_TOKEN"' # → your tokenScope — account, workspace, or box
A secret lives at one of three scopes. A box sees them layered, with the most specific tier winning a name collision — box > workspace > account:
| Scope | Set with | Reaches |
|---|---|---|
| Account | secret set NAME | every box you own, in every workspace |
| Workspace | secret set -w acme NAME | every box in workspace acme |
| Box | secret set -w acme -b web NAME (or --box acme/web NAME) | only box web in acme |
sh
printf … | ssh cli@box.hopbox.dev secret set OPENAI_API_KEY # everywhere
printf … | ssh cli@box.hopbox.dev secret set -w acme DATABASE_URL # acme's boxes
printf … | ssh cli@box.hopbox.dev secret set --box acme/web DEPLOY_KEY # one box
ssh cli@box.hopbox.dev secret ls # NAME + SCOPE (account / workspace:acme / box:acme/web)
ssh cli@box.hopbox.dev secret ls -w acme # just that workspace's secrets
ssh cli@box.hopbox.dev secret rm -w acme DATABASE_URL # remove that tierSo a shared credential goes on the workspace (every box in the project gets it), a sensitive one is pinned to a single box, and a personal default sits at the account tier. Running untrusted code? Do it in a workspace with no secrets — none of your credentials are in it.
How it's kept secure
- Encrypted at rest. Each secret is sealed with AES-256-GCM under a key derived from the server's host key (or
--secrets-key). The database stores only ciphertext and a per-secret nonce — a stolen database reveals nothing. - Delivered over the authenticated channel. A secret reaches a box through the same trusted provisioning path that carries the box's own bootstrap identity — not the box metadata API (which trusts only source IP). Another tenant can't request or spoof their way to your secrets.
- Scoped to you. Only your boxes ever receive your secrets. A different SSH key's boxes see nothing of yours.
- Never logged, never in argv. The value is read from stdin and is never printed by
ls, never written to logs. - The box's own bootstrap token stays hidden — hopbox's internal
HOPBOX_*secrets are still stripped from your shell; only your own secrets are exposed. (For that reason, secret names can't start withHOPBOX_.)
Lifetime — what to expect
| Event | Your secret in the box? |
|---|---|
Reconnecting (ssh again) | Yes — every session reads it. |
| Auto-suspend → resume | Yes — it's in the box's snapshot, restored intact. |
| Removing + re-creating a box | Yes — re-injected from the store on the next spawn. |
| Daemon restart | Yes — the store is durable (encrypted on disk). |
| Setting/changing a secret | Takes effect on a box's next spawn. An already-running or suspended box keeps the value it booted with — respawn it (or ssh <name>+@…) to pick up the change. |
So a secret is shared across all your boxes and durable — set it once and it follows you. It is injected at box startup, so a change propagates to a box when that box is next created, not retroactively into a live one.
Names must look like an environment variable (
[A-Z_][A-Z0-9_]*) and can't use the reservedHOPBOX_prefix. Values are byte-safe up to 64 KB.