ADR 0004 — Token encryption key separated from the database
Status
Accepted
Context
MAIL-74 must store provider OAuth tokens so later connect/refresh/revoke work can persist credentials. NFR §7 and Technical Architecture §§58–60 require tokens never to appear in git, logs, client storage, or plaintext at rest. Deployment §45 requires application-level protection beyond database access: the database holds ciphertext; the encryption key is managed separately.
TOKEN_ENCRYPTION_KEY is already a fail-closed boot requirement (MAIL-72 / packages/config/src/env.ts). Sprint 01 must not invent a cloud KMS.
Decision
- Encrypt provider tokens with AES-256-GCM in
@inbox-agent/security. - Derive the 32-byte AES key in-process from
TOKEN_ENCRYPTION_KEY(SHA-256 of the env secret). The secret is never written to git or to PostgreSQL. - Persist only a versioned ciphertext envelope (
v1.aes256gcm.<base64url(iv || tag || ciphertext)>) ininbox_agent.provider_tokens. Storekey_id(env:TOKEN_ENCRYPTION_KEY), not the key. - Bind GCM additional authenticated data to
account_id,provider, andtoken_typeso a ciphertext row cannot be copied onto another connection. - Expose one
TokenSecretStoreinterface. Local and test resolve the key from the environment. Preview and production use the same interface today (env); a later vault adapter can replace the env implementation without changingcreateTokenStorecallers. - Do not add AWS KMS, HashiCorp Vault, or another cloud secret manager in this ticket.
Alternatives considered
- Database-native
pgcryptowith a key stored beside the ciphertext: rejected. A database dump would include both. - AWS KMS or a cloud vault in Sprint 01: rejected. Same interface is reserved for a future adapter; env is the current backend in every
APP_ENV. - Envelope encryption with a per-row data key: deferred. AES-256-GCM with a process key meets the acceptance criteria and keeps rotation as a
key_idchange later.
Consequences
- Reading
inbox_agent.provider_tokensis not enough to recover a refresh token. Decrypt requires the process env key. - Losing
TOKEN_ENCRYPTION_KEYmakes stored tokens unrecoverable; operators reconnect mailboxes rather than back up the key in the database. - MAIL-73 user rows are intentionally not referenced.
account_idis text so later tickets can align identity without a Sprint 01 FK. - Log redaction (MAIL-69) treats tokens, ciphertext, and
tokenEncryptionKeyas sensitive. MAIL-145 asserts they do not appear in logger output.
