Now domain-agnostic — configure any practice with Domain Packs (immigration is the reference pack)
Encryption & Secrets

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

BackendStatusUse case
env (default)ReadyDevelopment / testing
vault (HashiCorp Vault)StubProduction, secret rotation
kms (AWS KMS / Azure Key Vault)StubProduction, 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
Never commit real keys

.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 backend

Observability encryption

The observability stack encrypts data in transit:

SignalTransportConfiguration
Structured logs (structlog)TLS to log aggregatorCAM_OTEL_EXPORTER_ENDPOINT (OpenTelemetry collector)
OpenTelemetry tracesgRPC + TLSCAM_OTEL_EXPORTER_ENDPOINT
Prometheus metricsHTTP + 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 httpx default 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:

  • structlog processor chain
  • OpenTelemetry PIISpanProcessor
  • Prometheus label sanitiser

Key rotation

KEK rotation plan

  1. Generate new KEK
  2. Re-encrypt all DEKs with new KEK
  3. Update CAM_ENCRYPTION_KEK env var
  4. Restart services
  5. 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

TestCoverageTool
Bandit SASTWeeklybandit -r src/cam
pip-audit dependenciesWeeklypip-audit
Secret scanEvery PRtrufflehog filesystem src/cam or gitleaks detect --source src/cam
Encryption tests90%pytest (test_crypto.py)

Common mistakes

MistakeConsequenceFix
Using the test KEK in productionData encrypted with a known key — no real protectionGenerate a real 256-bit key from Vault or KMS; never use the zero-filled test key
Skipping TLS verification on connector HTTP clientsMan-in-the-middle exposurehttpx verifies TLS by default; never set verify=False
Committing .env with real keysSecrets 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 compromiseCompromised key decrypts all historical dataFollow the KEK rotation plan: generate new → re-encrypt DEKs → update env → restart

Verification

After reading this page, verify your understanding:

  1. Explain the DEK → KEK → Master key hierarchy and why DEKs are unique per document
  2. Show how to switch from env to vault secret backend
  3. List the three observability signals where PII scrubbing is applied
  4. Describe the KEK rotation process and why it requires a service restart

Next steps


Last updated: 2026-06-01