Encryption & Secrets
Audience: Engineers configuring encryption keys, secret backends, or TLS; security reviewers evaluating data protection.
What you will accomplish: Understand the encryption-at-rest model, key hierarchy, secret loading, and PII scrubbing — so you can configure, rotate keys, or audit the system.
Prerequisites: Read Security overview and Audit & Logging first.
Estimated time: ~8 minutes.
All sensitive data is encrypted in transit and at rest. No credentials live in source code.
Encryption at rest
Database
- PostgreSQL — TLS connections enforced (
sslmode=require) - Column-level encryption — OAuth refresh tokens and KEK-encrypted values use AES-256-GCM
- Audit log — PII-scrubbed before storage; no raw A-numbers, SSNs, or passport numbers
Object store
Generated documents are stored in an S3-compatible object store with:
- Server-side encryption (SSE-S3 or SSE-KMS)
- Bucket policies restricting access to the application role
- No public read access
Key hierarchy
Data Encryption Key (DEK) ──► encrypts document content / sensitive fields
│
▼
Key Encryption Key (KEK) ──► encrypts the DEK
│
▼
Master Key ──► stored in secret store (Vault / KMS)- DEKs are unique per document / per sensitive field
- KEK is configured via
CAM_ENCRYPTION_KEK(base64-encoded 32-byte key) - Master key is managed by the secret store backend
Secret store backends
| Backend | Status | Use case |
|---|---|---|
env (default) | Ready | Development / testing |
vault (HashiCorp Vault) | Stub | Production, secret rotation |
kms (AWS KMS / Azure Key Vault) | Stub | Production, cloud-native |
Configuration
# Development
CAM_SECRET_BACKEND=env
CAM_ENCRYPTION_KEK=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
# Production (Vault)
CAM_SECRET_BACKEND=vault
VAULT_ADDR=https://vault.example.com
VAULT_TOKEN_PATH=/run/secrets/vault-token
CAM_OTEL_EXPORTER_ENDPOINT=https://otel-collector.example.com:4317 # optional
# Production (KMS)
CAM_SECRET_BACKEND=kms
AWS_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789:key/abc123
CAM_OTEL_EXPORTER_ENDPOINT=https://otel-collector.example.com:4317 # optional.env is in .gitignore. The CI uses a zero-filled test KEK explicitly labeled "test only". Production must use a real 256-bit key from your secret store.
Secret loading at runtime
Secrets are loaded once at startup and cached in memory:
from cam.config.settings import Settings
settings = Settings()
kek = settings.encryption_kek # Loaded from configured backendObservability encryption
The observability stack encrypts data in transit:
| Signal | Transport | Configuration |
|---|---|---|
| Structured logs (structlog) | TLS to log aggregator | CAM_OTEL_EXPORTER_ENDPOINT (OpenTelemetry collector) |
| OpenTelemetry traces | gRPC + TLS | CAM_OTEL_EXPORTER_ENDPOINT |
| Prometheus metrics | HTTP + TLS (scraped) | Standard Prometheus TLS config |
PII is scrubbed before any value enters these signals — the encryption layer protects the transport, not the content.
Feature flag security
Feature flags (CAM_FEATURE_FLAGS) act as a security control: workflows are inert until their flag is explicitly enabled in the JSON blob. This prevents a misconfigured deployment from accidentally enabling a workflow that hasn't been approved for production. Flags are read at startup and cached; changing a flag requires a service restart.
The VaultSecretLoader and KMSSecretLoader stubs are in src/cam/config/settings.py. Both currently raise NotImplementedError — completing them is a Phase 1 priority.
TLS in transit
All external communications use TLS 1.2+:
- MCP server ↔ client — stdio (local) or HTTPS/SSE (remote)
- Sidecar ↔ webhooks — HTTPS with certificate verification
- Connectors ↔ vendor APIs — TLS with
httpxdefault verification
PII scrubber
Before any value enters logs, traces, or metrics, it passes through the shared PII scrubber:
from cam.obs.pii import scrub
scrubbed = scrub("Client A12345678 has SSN 123-45-6789")
# → "Client [[REDACTED:A-NUMBER]] has SSN [[REDACTED:SSN]]"Scrubbing is applied automatically by:
structlogprocessor chain- OpenTelemetry
PIISpanProcessor - Prometheus label sanitiser
Key rotation
KEK rotation plan
- Generate new KEK
- Re-encrypt all DEKs with new KEK
- Update
CAM_ENCRYPTION_KEKenv var - Restart services
- Old KEK can be retired after grace period
This is a manual process in v1. Automated rotation is a Phase 3 roadmap item.
Security testing
| Test | Coverage | Tool |
|---|---|---|
| Bandit SAST | Weekly | bandit -r src/cam |
| pip-audit dependencies | Weekly | pip-audit |
| Secret scan | Every PR | trufflehog filesystem src/cam or gitleaks detect --source src/cam |
| Encryption tests | 90% | pytest (test_crypto.py) |
Common mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Using the test KEK in production | Data encrypted with a known key — no real protection | Generate a real 256-bit key from Vault or KMS; never use the zero-filled test key |
| Skipping TLS verification on connector HTTP clients | Man-in-the-middle exposure | httpx verifies TLS by default; never set verify=False |
Committing .env with real keys | Secrets in git history (even after deletion) | .env is in .gitignore; use trufflehog or gitleaks in CI to catch accidents |
| Not rotating KEK after a suspected compromise | Compromised key decrypts all historical data | Follow the KEK rotation plan: generate new → re-encrypt DEKs → update env → restart |
Verification
After reading this page, verify your understanding:
- Explain the DEK → KEK → Master key hierarchy and why DEKs are unique per document
- Show how to switch from
envtovaultsecret backend - List the three observability signals where PII scrubbing is applied
- Describe the KEK rotation process and why it requires a service restart
Next steps
- Audit & Logging — How every action is recorded and verified
- RBAC & Privilege Gates — Who can access what, and how privilege is enforced
Last updated: 2026-06-01