secrets
Scopes showed the folder tree that gates who can see whom. Secrets live in that same tree — the credentials an agent needs to reach out (a GitHub token, a model API key), with one hard rule: the agent uses them without ever seeing the plaintext in its prompt. arizuko stores them in the secrets table in routd.db and hands them to the right caller at the right moment — either as container env at spawn, or brokered as a tool-call argument on the host. Two things vary: what kind of credential it is (three types, each with its own rule) and which scope owns it (folder or user). The delivery path follows from the type, and it’s what keeps a leaked transcript from leaking a key.
secrets table, the three credential types below, both delivery paths, and the operator CLI. Folder secrets inject as container env at spawn; a user's own key overrides the folder default. Capability tokens resolved for a connector call are brokered on the host and never enter the container. Values are encrypted at rest — routd requires a SECRETS_KEY (see SECRETS_KEY). Model keys have their own dashboard section at /dash/me/env; capability tokens live at /dash/me/secrets, or arrive via OAuth at /dash/me/connections (GitHub today).three types
The key name decides the type, and the type decides the rules:
- Env-profile keys — the model credentials:
ANTHROPIC_API_KEY,CLAUDE_CODE_OAUTH_TOKEN,OPENAI_API_KEY,CODEX_API_KEY. These pick which model your spawn talks to, so they are user-scoped only — the store rejects them at folder scope. A user sets their own in a dedicated dashboard section at/dash/me/env, and their key overrides the operator default for their spawns. - Capability credentials — per-service tokens the agent uses to reach an external API:
GITHUB_TOKEN, a Cloudflare key, and the like. Set at/dash/me/secrets(user scope) or by the operator at folder scope. Grant-gated. When a connector call needs one, the broker resolves the triggering user's value on the host; the token never enters the container. - Infra / operator anchors — instance-wide values that live in the host
.env, not in this table:CHANNEL_SECRET, bot credentials,SECRETS_KEYitself. They belong to the operator running the box.
two scopes
For rows that live in the table (env-profile and capability), the scope_kind column says who owns the value:
- folder — tied to a group path like
atlas/eng. Owned by the operator; shared by every user that interacts with that folder. Env-profile keys are rejected here. - user — tied to one
auth_users.sub. Owned by the user (typed into/dash/me/envor/dash/me/secrets) or seeded by the operator as a fallback.
The primary key is (scope_kind, scope_id, key). Keys are uppercase env-style ids: ^[A-Z][A-Z0-9_]*$.
folder secrets reach the container
At spawn time, container/runner.go resolves the folder's secrets, merges them with the base env, and writes the map into the container as env vars. The agent process inside reads them like any other env var.
Resolution walks from the folder up to a synthetic root row, deepest wins. So atlas/eng/sre sees its own keys overlaid on atlas/eng, on atlas, and on root. A key set at atlas reaches every child folder under atlas/ unless a child overrides it.
user secrets stay on the host
User-scoped secrets never enter the container. They are resolved at tool-call time inside the host MCP dispatch chain. When a tool declares requires_secrets: ["GITHUB_TOKEN"], routd looks up the calling user’s value and passes it as an argument to the handler running in the host process. The handler makes the outbound HTTP call. The agent sees only the handler’s response.
This is why a leaked agent transcript can’t leak a user’s GitHub token — the token was never in the prompt and never in the container env.
getting a capability token without pasting one
Pasting a GITHUB_TOKEN at /dash/me/secrets works, but it means minting a long-lived personal access token and copying it by hand. Connect GitHub at /dash/me/connections runs the OAuth dance instead: you approve the arizuko OAuth app on GitHub, and the access + refresh token it hands back land in the same secrets row a pasted PAT would — same key, same broker, same delivery path. The broker refreshes the token near expiry, so you never see it again after the first approval.
This is surrogate OAuth: arizuko authenticates as you to GitHub's API, which is a different thing from the OAuth login you use to sign in to arizuko itself. Today it covers GitHub; more providers register the same way as they're added.
operator CLI
# folder-scoped
arizuko secret <instance> set <folder> KEY --value V
arizuko secret <instance> list <folder>
arizuko secret <instance> delete <folder> KEY
# user-scoped (fallback for users who haven't logged in yet)
arizuko user-secret <instance> set <user_sub> KEY --value V
arizuko user-secret <instance> list <user_sub>
arizuko user-secret <instance> delete <user_sub> KEY
Logged-in users manage their own user secrets through the dashboard at /dash/me/secrets — the CLI is the operator fallback for seeded values.
storage at rest
Secret values are encrypted at rest in the secrets table (AES-256-GCM). routd requires a SECRETS_KEY to seal and unseal them. The key, the keyring, and rotation are documented in SECRETS_KEY.
rotation
A second set with the same key upserts the value and bumps created_at. The next container spawn picks up the new folder value; the next broker call picks up the new user value. There is no restart-the-world step — the old value lives in the env of running containers until the agent process restarts.
what doesn’t happen
- No plaintext secret in audit logs. The
secret_use_logtable records key, scope, status, and latency — never the value. - No user secret in container env. Per-user values reach only the host-side broker.
- No skill or agent prompt can enumerate the table. Tools declare the keys they need; the broker resolves only those.
go deeper
The credential model — three types, resolution chain, write paths: specs/5/14. The surrogate OAuth write path: specs/5/15. Where secrets sit in the trust map: SECURITY.md. The folder hierarchy that drives resolution: scopes. External REST tools that inject a capability token: MCP reference.