Skip to content
View as .md

Secrets

Encrypted credentials stored in Supabase Vault, scoped to a house. Storing one is the whole job: at dispatch every house secret is decrypted and exported as an env var into the sandbox, for run_command and for the coding agent behind delegate_task. An environment can restrict its boxes to a named subset (secrets: { only: [...] }, see environments).

secrets {
house_id, name, # UNIQUE(house_id, name) where deleted_at is null
author_id, # who created it; an owner or this author may rotate or delete
vault_secret_id, # encrypted value lives in Vault, never in the table
funded_by, provider_key_hash # 'arbe' + key hash when arbe minted it (LLM keys)
deleted_at # soft delete
}

Secrets belong to the house, not the person. Any member can use any secret in the house, and a name means one value, whoever created it. author_id gates nothing but rotation and deletion. Names are immutable: set on an existing name rotates the value, and renaming is delete and recreate.

What reaches a sandbox. A bot with run_command can read the box’s entire environment with a bare env, so the injected set is the blast radius. buildBoxEnv (packages/core/dispatch/sandbox-shared.ts) is the one place deciding it, with two rules:

  1. Every house secret by default, or the environment’s only subset.
  2. House tier only, including arbe-funded keys. Operator infrastructure keys never enter a box.

That means the house’s model key, BYOK or arbe-funded, is readable inside the sandbox by design; the funded key is capped, so this is accepted rather than proxied (LLM keys). A house without a model key gets No model key for this house. Add OPENROUTER_API_KEY under house secrets. A name in an only list with no secret behind it is left out and reported by arbe env diagnose; dispatch never refuses over it.

Terminal window
arbe secret list # names, rotation age, funding provenance
arbe secret set <name> # upsert from stdin; same name = rotate
arbe secret delete <name> # soft delete
echo "sk-or-..." | arbe secret set OPENROUTER_API_KEY

GitHub. You don’t need to know the secret name. arbe connection create github (or Settings → Runtime → Connections) stores the token as GITHUB_TOKEN and also signs git and gh in inside every box (packages/core/dispatch/connection-env.ts).

Nothing ever returns a value. Values are read from stdin, never argv. The API (GET/POST /api/secrets, GET/DELETE /api/secrets/:id, PUT /api/secrets/:id/value) and arbe secret view are metadata only. Decryption happens only in resolve_secrets_for_scope, a service-role Postgres function the backstage calls in-process during a turn it already holds the fence for.

Storage. Supabase Vault (pgsodium AEAD, Supabase-managed key). Soft-delete keeps the metadata row and hard-deletes the Vault entry; a trigger cascades when a house is soft-deleted.

Code: packages/core/schemas/secret.ts, apps/www/src/routes/api/secrets/. See environments, dispatch, auth.