Security

Botpasses is a grant-vault. Credential values stay inside the vault process and are decrypted only at an approved inject: the moment Botpasses attaches the value to an outbound API call, hands it to a trusted runtime over POST /runtime/resolve, or (on the local CLI) places it in a child process environment. No MCP tool, HTTP route, console screen, or email returns a value. There is no get_secret.

Trust model

This is not zero-knowledge. To attach a key to a request the hosted process has to decrypt it, so the hosted process (and anyone holding both the AWS KMS role and the database) can read values. The model, the transcript, logs, and staff without both of those cannot. See the design decision inADR 0006.

SurfaceSees secret value?
MCP toolsNo
Operator console / HTTP JSON (except trusted resolve)No after submit
CLI list / grant / auditNo
Audit table / email / inboxNo
Unauthenticated GET /collect/:idNo (shell only)
vault run child env / POST /runtime/resolve / connector originYes. That is the inject
Model context / chat transcriptMust not. Tests fail if a canary appears

Encryption

Each credential is encrypted with AES-256-GCM under a data key that belongs to its organisation. Additional authenticated data is the bound tuple orgId|itemId|allowed_hosts_json|inject, not the organisation id alone. Organisation keys are wrapped by a platform key. At boot, AWS KMS unwraps that platform key (VAULT_KEK_WRAPPED) through a role the Fly Machine assumes with Fly OIDC; the KMS key itself stays in AWS and does not handle a credential. Operator inboxes and live OAuth refresh, auth, device, and session tokens are stored the same way (email-at-rest and OAuth-token-at-rest): a keyed HMAC for lookup and an AES-GCM wrap under the identity key. A database dump without the platform KEK does not yield credential values, inboxes, or those bearer tokens. Item names, last four characters, allowed hosts, and internal Grant ids stay in the dump. Machine tokens are stored as SHA-256 hashes. The local CLI uses a master key you hold, with the credential name bound into the ciphertext.

Connector hardening

When an agent calls http_request, the outbound request is constrained before the value is attached:

Every call is written to the audit log by agent name and credential name. Approvals are per agent and credential; use one-call (prompt) approvals for anything sensitive.

Operator identity

Accounts are first-party: an 8-digit email code plus a required authenticator app (TOTP) with backup codes. There is no password and no third-party identity vendor. Sessions are HttpOnly cookies; state-changing requests need a signed CSRF token. Operator actions require the authenticator step; a session that skipped it is refused with mfa_required. The exception is a bootstrap bearer honoured only when VAULT_BOOTSTRAP_ALLOW_PLANE=1 is set on that plane.

OAuth for agents

botpasses.com is its own OAuth 2.1 authorization server for MCP clients. PKCE S256 is required; dynamic client registration accepts https, loopback, and desktop-app redirect schemes and rejectsjavascript:, data:, and file:. Access tokens are RS256 JWTs bound tohttps://botpasses.com/mcp with a 600 second lifetime and a unique jti; refresh tokens rotate. Clients that cannot do OAuth use a one-time bearer token issued in the console. Model tokens cannot call operator routes or resolve values; trusted runtime tokens cannot call MCP.

Access ledger and revoke

Every issued session, bearer token, and OAuth token is recorded before it is handed out, as a hash. The console Access panel lists who holds access, when it was first and last used, and which credential names were fetched. Revoking an agent marks its tokens denied so the next call is 401; revoking an approval stops one credential; revoking a session signs out one device. Revoke is operator-only. Nothing over MCP can approve or revoke.

Tests for secret exposure

The repository stores a canary value in tests and asserts that it never appears in an MCP tool result, a REST payload for a model, the inbox JSON, an email, a collect page, an audit row, or the SQLite file. Another test fails if the phrase "zero-knowledge" appears in the README or the source, since that is not a claim Botpasses can make. These run in CI on every push.

Reporting a vulnerability

Email [email protected]. The same contact is published in/.well-known/security.txt. What to include and what to expect: Security disclosure.

Decisions