Encryption¶
Envelope Encryption¶
All sensitive data (API keys, tokens, saga state) encrypted at rest using libsodium secretbox.
from features.secrets import encrypt_json, decrypt_json
# Encrypt
blob = encrypt_json({"token": "secret", "created_at": 1234567890})
# blob = nonce(24) + ciphertext (binary)
# Decrypt
data = decrypt_json(blob)
# data = {"token": "secret", "created_at": 1234567890}
Key Resolution¶
- OS keychain (keyring) — production
- config.yaml (
crypto.master_key_hex) - .env file (
MCP_MASTER_KEY=...) — development - Environment variable
- Auto-generate (saves to .env)
Decrypt-First Pattern¶
Auth modules (APIKeyAuth, BearerAuth) use a decrypt-first pattern:
# Try decrypt first (handles nonce collision where is_encrypted_blob returns False)
try:
return decrypt_json(blob)
except Exception:
pass
# Not encrypted — parse as legacy JSON and rotate
legacy = json.loads(blob.decode("utf-8"))
This eliminates flaky tests caused by libsodium nonce first byte coincidentally being { or [ (~0.78% probability).