Skip to content

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_TOKEN

Then, in any of your boxes:

sh
ssh dev@box.hopbox.dev 'echo "$GITHUB_TOKEN"'   # → your token

Scope — 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:

ScopeSet withReaches
Accountsecret set NAMEevery box you own, in every workspace
Workspacesecret set -w acme NAMEevery box in workspace acme
Boxsecret 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 tier

So 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 with HOPBOX_.)

Lifetime — what to expect

EventYour secret in the box?
Reconnecting (ssh again)Yes — every session reads it.
Auto-suspend → resumeYes — it's in the box's snapshot, restored intact.
Removing + re-creating a boxYes — re-injected from the store on the next spawn.
Daemon restartYes — the store is durable (encrypted on disk).
Setting/changing a secretTakes 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 reserved HOPBOX_ prefix. Values are byte-safe up to 64 KB.

Instant isolated compute — for humans and AIs