BYOK & credential custody
read as.mdGuuey holds several kinds of secrets on your behalf: your model provider keys, the platform’s own managed model keys, the secrets your hosted MCP servers need upstream, and your users’ OAuth tokens for third-party services. Each class has a different owner and a different blast radius, so each gets its own custody path. This page explains the machine behind those paths — Security & isolation has the one-screen inventory; here is the why underneath it.
The design rule is the same everywhere: a secret’s plaintext exists in exactly one process, at the moment of use. Everything else — including your agent’s own code — gets a reference or a short-lived stand-in.
One envelope model, many locks
Section titled “One envelope model, many locks”Every stored credential is AWS KMS ciphertext — plaintext never touches a database row. But encryption alone isn’t the interesting part. Each ciphertext is sealed with an encryption context: a set of claims naming what the secret is and who owns it — a BYOK key is bound to its app, a user’s OAuth token to that user and that service, a hosted-MCP secret to its server.
KMS enforces the context at decrypt time: presenting a ciphertext with any other context is refused by AWS itself, not by Guuey application code. That turns the context into a structural guarantee — a ciphertext lifted from one owner’s row cannot be unsealed as another app, another user, or another credential class, even by code that holds decrypt permission on the key. The partitions are the isolation; the encryption is just the envelope.
Decryption happens only in the one backend process that needs the plaintext, with the owner-bound context, at the moment of use. No read API returns a secret: key listings return a prefix and validation status, secret listings return names only, and OAuth tokens are never returned at all.
BYOK model keys: your key, your pods, still not your agent’s env
Section titled “BYOK model keys: your key, your pods, still not your agent’s env”When you bring your own provider key (Anthropic, OpenAI, or Google — added in the Console under your app’s Behavior → Model or at workspace level under Settings → Workspace → your workspace → Provider keys; OpenRouter keys ride the app environment via guuey env set OPENROUTER_API_KEY=…, and a dedicated guuey byok command is on the roadmap), it is sealed to your app or workspace before storage. At deploy time, Guuey unseals it with your app’s own context and delivers it only into your app’s pod — and nowhere else. A different app’s deploy presenting your ciphertext would be refused by KMS.
Inside the pod, custody continues. Your agent’s code runs in a jail whose environment is built by allowlist (Runtime isolation); the provider-key slots are on a refuse list by name, so the pod supervisor keeps your key on its side of the wall and your agent’s model calls leave through the same loopback proxy managed agents use. Your own key never sits in your agent’s process environment.
Why bother, when it’s your key in your own app? Because the jail’s threat model is prompt injection: your agent runs code and reads untrusted input, and anything in its environment is one env away from exfiltration. Keeping your key out of the jail means a hijacked conversation can spend against your provider account only through the metered proxy, for the duration of its own invoke — it can never read the key and walk away with it.
BYOK invokes draw none of your plan’s managed-model budget — the calls are billed by your provider, on your key. See Plans & billing.
Managed model keys: never in your pod’s reach
Section titled “Managed model keys: never in your pod’s reach”Agents without a BYOK key run on Guuey’s managed provider keys — credentials shared across tenants, which makes them the platform’s most guarded class. Your agent’s process never holds one, and the guarantee is structural rather than policy:
- The jail’s environment is allowlist-by-construction: it starts empty and receives only a short list of named platform paths and settings (your app’s own declared variables reach your colocated MCP servers, not the model-calling worker). There is no “everything minus the secrets” subtraction that a new secret could slip past — the managed key isn’t removed from the environment; it was never a candidate.
- In place of a key, your agent’s SDK receives a loopback base URL and an opaque per-invoke token — random bytes, bound to one provider and one invoke, revoked when the turn ends, and expiring on its own a couple of minutes after the longest turn a pod will run, even if revocation never runs.
- The proxy on the other side strips whatever authorization headers arrive, validates the token, and injects the real key into the upstream call. The key exists only in the supervisor process, outside the jail.
The worst a fully compromised agent can do with this arrangement is make model calls that are metered and billed to its own app until its token expires. It cannot read a credential, because there is no credential to read.
Hosted MCP server secrets: write-only
Section titled “Hosted MCP server secrets: write-only”Secrets your hosted MCP server needs for its upstream calls (guuey mcp secrets set NAME=VALUE --server <id>) are sealed per server. The plaintext is never baked into a server image and never returned by any API — guuey mcp secrets list shows names only. The MCP gateway unseals a secret per invocation and hands it to your server for that call. Agent pods calling the server see nothing: they authenticate to the gateway with a short-lived federation token, and the secret stays on the server’s side of the gateway.
OAuth tokens: broker custody
Section titled “OAuth tokens: broker custody”When an end user connects a third-party service to an agent, the OAuth dance happens at the provider — Guuey receives tokens, never passwords. Custody then follows the claude.ai model, detailed in The MCP proxy:
- The token is sealed to that user and that service — authorize once, then each app’s access is granted per app via an explicit consent card. The grant is a database row, not a copy of the token.
- The agent pod never holds the token. It carries a short-lived, KMS-signed federation token addressed to the gateway; the gateway checks the per-app grant and injects the real credential into the upstream call. Plaintext exists only inside the gateway process for the duration of that forward.
- Disconnecting a service deletes the ciphertext, drains every app grant on the connection, and makes a best-effort revocation call to the provider.
The same envelope model also seals the platform’s identity credentials — per-app signing keys (The identity model) and an optional BYO OIDC client secret (Sign-in on the agent’s page).
The custody table
Section titled “The custody table”| Credential class | Sealed to | Unsealed by | Ever in your agent’s process? |
|---|---|---|---|
| BYOK model key | your app / workspace | Guuey at deploy time, into your pod’s supervisor | No — jail refuses the key slots; calls ride the loopback proxy |
| Managed model key | the platform | the pod supervisor’s egress proxy | No — agent gets a per-invoke token instead |
| Hosted MCP secret | the server | MCP gateway, per invocation | No — stays on the server’s side of the gateway |
| End-user OAuth token | the user × service | credential broker, per forwarded call | No — pod carries only a federation token |
Two properties fall out of the design rather than being enforced case by case:
- No secret survives contact with your agent. Everything your agent’s process can see is either a path, its own declared configuration, or a stand-in that expires with its one invoke and is useless outside its one scope.
- A breach of one partition stays in that partition. Ciphertext is owner-bound at the KMS layer, plaintext is process-bound at the runtime layer, and the network walls in Security & isolation close the paths between them.
For the pod model itself — one pod per app, sizes, pause and resume — see How hosting works; the jail and the wall between it and the supervisor are on Runtime isolation.