opbox

The secrets a box holds are the parts most worth protecting and the parts most often left in a drawer somewhere: the API keys that let an agent call a model, the credentials that reach an outside system, the bearer tokens that grant a client a link, the box-local keys that encrypt data at rest. This back-office is where all of those live, governed by the same single front door as everything else. Every secret is encrypted before it touches a column, never returned by a read, and inventoried so you always know what exists, what it can reach, and what it is allowed to spend.

What it does

Every secret is encrypted at rest and never read back. Credentials and connection secrets are sealed with AES-256-GCM before they are written, bound to the row they belong to, and stored only as ciphertext (INV-7). A read never returns the plaintext: credential.list hands back metadata and a masked ****<last4> hint, never the secret itself. The mask is computed by decrypting first and slicing the plaintext, never by slicing the ciphertext, so even the hint carries no leak. The same rule holds for tokens: token.list and token.get return scope, status, and expiry, never the secret or its hash.

Bring-your-own-key credentials, per box. Store an AI credential against a connection with credential.set and the box runs on a key you own rather than a shared one. Every credential is verifiable with a dry-run (credential.verify), which decrypts the stored secret to prove it is intact, without ever exposing it, stamping keyVerifiedAt on success so you can see at a glance which keys have been checked. Rotating a credential in place (credential.rotate) replaces the secret and resets that verification stamp, so a freshly rotated key shows as unverified until you check it.

A per-key spend cap, distinct from the org budget. Each credential can carry its own spend_cap_minor via credential.setCap. This is a cap on one bring-your-own key, not the org-wide budget set elsewhere, and it moves no money: it is configuration that the spend invariant (INV-12) enforces when the key is used. So a single model key can be ring-fenced to a ceiling without touching anything else the box is allowed to spend.

Connections are a guarded lifecycle, not a free-form config blob. A connection to an outside system is authored as a Connection row in DRAFT (connection.create), with any inbound-webhook HMAC secret encrypted before write. From there it moves through a guarded state machine: enable it, disable it (which preserves its sync cursor), or check its reachability. Deleting a connection (connection.delete) is an Owner-tier teardown with an explicit decision about its synced tables: the integration tables are retained, not silently dropped, so a teardown never destroys data by surprise. The credentials a connection uses are never stored on the connection row itself; one connection can carry several labelled credential.set secrets.

Box-local keys rotate on a single owner-tier verb. secret.rotate rotates a secret that belongs to this one box: the database role, an app role, an LLM key, or a signing key. It is scoped to a single box (there is no fleet-wide key rotation here), and it sits at Owner tier because rotating a box’s own keys is among the most sensitive moves it can make.

Tokens are scope-bound, hashed, and revocable two ways. A token is one capability-scoped credential. token.mint issues a scope-bound secret, returns the plaintext exactly once, and persists only the hash. Expiry is a fixed substrate constant per token kind, not tenant configuration: signing and escalation grants are short-lived and hard-capped at 24 hours, while webhook, API, and portal tokens default to 30 days. A mint may always request a shorter expiry, but it can never widen the cap. Revocation works at two grains: token.revoke kills one token, while token.revoke-scope bumps a scope-version counter so every outstanding token in that scope is invalidated at its next use. A token presented after its scope was revoked is a hard, fail-closed deny that leaks no distinction between “no such token” and “wrong scope”.

Outbound traffic runs through an egress allow-list, default-deny. egress.policy.set authors the per-workspace outbound allow-list that the dispatch chokepoint enforces. Each rule pairs a rail (connector, model, webhook, or document engine) with an allow-listed target. Any outbound call that is not on the list is a fail-closed deny at the chokepoint. This is the line that keeps a box from talking to anywhere it was not explicitly told it could, and it is what an org lockdown collapses to deny-all in an incident.

How you use it

Inventory what the box holds. List the credentials, tokens, and connections to see exactly what exists and what each can reach. You see masked hints, scopes, statuses, expiries, and spend caps, never a secret, so the inventory is safe to put on a screen.

Store and verify a key. Set a bring-your-own credential against the right connection, then run a dry-run verify before you rely on it. A successful verify stamps the key as checked; an unverified key tells you to check it before it is load-bearing.

Cap a key’s spend. Put a per-credential spend cap on a model key so it can never run past a ceiling, independent of the org budget. Adjust it later in place.

Wire up an outside system. Author the connection as a draft, attach the credentials it needs, check it is reachable, then enable it. Disable it later without losing its sync state, or delete it cleanly when its synced tables should be torn down.

Rotate when something needs replacing. Rotate a credential’s secret in place when a key leaks or ages out, or rotate a box-local secret (a database role, an app role, a signing or model key) when the box’s own keys need turning over. A rotated credential shows as unverified until you re-check it.

Cut off access fast. Revoke a single token when one link must die, or revoke a whole scope to kill every outstanding token for a portal, a share, or a gate at once. In an incident, the egress allow-list is what an org lockdown empties to stop all outbound traffic.

The kernel verbs behind it

Each of these goes through the one front door - permission-checked and audited before it runs (see Security & permissions). These verbs sit at Admin tier or above, and the secret-bearing ones are kept out of the AI’s hands. These are the verbs most specific to credentials, secrets, connections, tokens, and egress.

Credentials and box secrets:

  • credential.set - store a bring-your-own AI credential, encrypted at rest and masked on read.
  • credential.list - inventory credentials with a masked hint, never the secret.
  • credential.verify - dry-run a stored credential and stamp keyVerifiedAt on success.
  • credential.rotate - replace a credential’s secret in place and reset its verification stamp.
  • credential.setCap - set a per-credential spend_cap_minor, distinct from the org budget.
  • credential.revoke - terminally disable a credential.
  • secret.rotate - rotate a box-local secret (database role, app role, LLM key, or signing key), at Owner tier.

Connections to outside systems:

Tokens and outbound egress:

  • token.mint - issue a scope-bound token, returning the plaintext once and persisting only the hash.
  • token.list / token.get - inventory outstanding tokens by scope and status; the secret is never returned.
  • token.revoke - revoke a single token.
  • token.revoke-scope - fan-kill every outstanding token in a scope by bumping the scope-version counter.
  • token.consume - present a token for use; revoked, wrong-scope, or stale-version is a fail-closed deny.
  • egress.policy.set - author the per-workspace outbound allow-list (by rail and target) the dispatch chokepoint enforces.

See the full set across the credential, connection, token, secret, and egress verb references.