Provider token encryption
MAIL-74 stores provider OAuth tokens as ciphertext. The encryption key is never in git or in PostgreSQL. See ADR 0004.
Secret store
| Environment | Implementation |
|---|---|
local, test | createEnvTokenSecretStore reads TOKEN_ENCRYPTION_KEY |
preview, production | Same interface and env backend today |
A future vault adapter implements TokenSecretStore (keyId + getKeyMaterial). Do not add AWS KMS here.
loadRequiredEnv already refuses to boot without a 32+ character TOKEN_ENCRYPTION_KEY. The secret store fails closed on the same rule if called without a boot-validated env.
Algorithm
- AES-256-GCM
- 12-byte random IV, 16-byte auth tag
- AES key = SHA-256(
TOKEN_ENCRYPTION_KEY) - Envelope:
v1.aes256gcm.<base64url(iv \|\| tag \|\| ciphertext)> - Additional authenticated data:
account_id,provider,token_type
Helpers live in @inbox-agent/security. @inbox-agent/persistence createTokenStore encrypts before write and decrypts after read. The ciphertext column is the only secret-bearing column.
Schema
database/migrations/0003_encrypted_provider_tokens.sql creates inbox_agent.provider_tokens with RLS enabled. anon / authenticated are revoked when those roles exist. There is no foreign key to users (MAIL-73) and no MailMessage table.
key_id records which process key encrypted the row (env:TOKEN_ENCRYPTION_KEY). It is not the key.
Logging
Do not pass plaintext tokens, ciphertext, or TOKEN_ENCRYPTION_KEY to a logger. If a caller does, @inbox-agent/observability redacts those fields (MAIL-69). tests/privacy/token-secret-redaction.test.ts (MAIL-145) asserts the needles are absent from serialized log output.
