Adr
ADR-005: BYOK Encryption
Secure storage for tenant-provided API keys
ADR-005: BYOK Encryption
Status
Accepted
Context
Agent Studio supports BYOK (Bring Your Own Keys) - tenants provide their own API keys for providers like Deepgram, OpenAI, etc. These keys must be:
- Encrypted at rest in the database
- Decrypted only at runtime when needed
- Rotatable without service interruption
- Never logged or exposed in error messages
Options Considered
- Vault/Secrets Manager - External service (AWS Secrets Manager, HashiCorp Vault)
- Application-level encryption - Encrypt in Python before storing
- Database-level encryption (TDE) - PostgreSQL native encryption
- KMS envelope encryption - Use cloud KMS for key management
Decision
We will use application-level encryption with Fernet (symmetric encryption):
from cryptography.fernet import Fernet
from pydantic import SecretStr
class ProviderKeyService:
def __init__(self, encryption_key: SecretStr):
self.fernet = Fernet(encryption_key.get_secret_value().encode())
def encrypt(self, api_key: str) -> str:
"""Encrypt API key for storage."""
return self.fernet.encrypt(api_key.encode()).decode()
def decrypt(self, encrypted_key: str) -> str:
"""Decrypt API key for use."""
return self.fernet.decrypt(encrypted_key.encode()).decode()Key Management
# settings.py
class Settings(BaseSettings):
encryption_key: SecretStr # Fernet key from environment
# For key rotation, support multiple keys
encryption_key_previous: SecretStr | None = NoneDatabase Schema
class ProviderKey(Base):
__tablename__ = "provider_keys"
id: Mapped[UUID]
tenant_id: Mapped[UUID]
provider_type: Mapped[str] # stt, tts, llm
provider_name: Mapped[str] # deepgram, openai
api_key_encrypted: Mapped[str] # Fernet encrypted
key_version: Mapped[int] # Encryption key version
is_default: Mapped[bool]Key Rotation Strategy
- Generate new Fernet key
- Set as
encryption_key, move old toencryption_key_previous - Background job re-encrypts all keys with new key
- Remove
encryption_key_previousafter migration
Security Considerations
- Encryption key stored in environment (not database)
- Keys decrypted only when creating provider instance
- Decrypted keys never logged
- API responses never include decrypted keys
# Safe logging
logger.info("Using provider", provider=provider_name, tenant=tenant_id)
# Never: logger.info("API key", key=api_key)Consequences
Positive
- Simple implementation with standard library
- No external service dependencies
- Keys protected at rest
- Easy key rotation
Negative
- Encryption key in environment is a single point of failure
- Not hardware-backed (vs HSM/KMS)
- Application has access to all decrypted keys
Future Considerations
- Could migrate to AWS KMS or HashiCorp Vault for enterprise tier
- Envelope encryption for additional security layer