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

EnvironmentImplementation
local, testcreateEnvTokenSecretStore reads TOKEN_ENCRYPTION_KEY
preview, productionSame 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

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.