Docs/Security & Compliance/Secrets & Key Management

Secrets & Key Management

Scope
Where secrets live per deployment type, how tenant-supplied credentials are encrypted at rest, rotation mechanics, and the traps that have bitten before
Rule of this page
Names and locations only. No values, fragments or connection strings appear here or should ever be added.
Related
Security Architecture, ADR-0010, ADR-0005, Threat Model

1. Where secrets live

DeploymentStoreNotesEvidence
Docker Compose (Lite / Full)deploy/compose/.env (gitignored)Generated by scripts/vegaduta secrets with random values; existing values are never overwritten. Compose refuses to start without the required ones (${...:?}), including APP_ENCRYPTION_KEY.deploy/compose/.env.example, deploy/compose/docker-compose.yml, .gitignore
Kubernetes (Helm)Kubernetes Secretsglobal.core.appSecret.existingSecret holds APP_ENCRYPTION_KEY (and any other core env such as SMTP or OAuth secrets) for core, core-jobs, core-worker and the migrate Job; left empty, the chart generates <release>-core-app once on helm install. Components have their own existingSecret values. Use an external-secrets operator if you keep secrets in a vault.deploy/helm/charts/vegaduta/values.yaml
Local core processYour shell environment (or core/.env.local, gitignored)Must carry the same APP_ENCRYPTION_KEY as the database it points at.scripts/dev-up.sh header, .gitignore

The original hosted deployment kept these values in a cloud key vault; that setup is not part of this repository (ADR-0010).

2. Secret inventory (names only)

VariableConsumersConsequence if wrong or lost
APP_ENCRYPTION_KEY (+ APP_ENCRYPTION_KEY_PREVIOUS during rotation)core, core-jobs, core-workerEvery stored provider key and channel secret becomes undecryptable
KEYCLOAK_ADMIN_PASSWORD, KEYCLOAK_CLIENT_SECRET, KEYCLOAK_DB_PASSWORDKeycloak, coreLogin, admin API (signup), Keycloak boot
POSTGRES_PASSWORDpostgres, pgbouncer, coreCore cannot reach Postgres
VALKEY_PASSWORDvalkey, core (as REDIS_PASSWORD)Sessions, rate limiting, caches
SIDECAR_SHARED_SECRET, MCP_STDIO_SIDECAR_SHARED_SECRET, TOOL_SANDBOX_INTERNAL_TOKEN, CODE_INTEL_INTERNAL_TOKEN, APP_BUILDER_*, RL_TRAINER_INTERNAL_TOKENcore plus the named serviceIngestion/transcription; STDIO MCP; custom code tools; code intelligence; app builder
STORAGE_ACCESS_KEY, STORAGE_SECRET_KEYcore, seaweedfsUploads and artifacts
LANGFUSE_*Langfuse web/worker, core (OTEL auth header)Tracing
PLATFORM_MASTER_KEY, PLATFORM_ALERTS_WEBHOOK_SECRETcore, AlertmanagerTenant hard-delete control; alert webhook
LIVEKIT_API_SECRET, TEMPORAL_DB_PASSWORD, SMTP_PASSWORD, KEYCLOAK_SMTP_PASSWORD, social *_CLIENT_SECRETrespective servicesVoice/video; workflow persistence; transactional email; social login

3. Encryption of tenant-supplied credentials

Standing rule: one key everywhere core boots
APP_ENCRYPTION_KEY must be identical everywhere core boots: deploy/compose/.env, a locally run core, and your Kubernetes Secret. A different key does not error on write; it makes every previously stored credential undecryptable (GCM tag failure). Never regenerate it on an existing database, and restore it together with any database backup.

4. Rotation runbook (encryption key)

  1. Generate a new 32-byte base64 key.
  2. Set APP_ENCRYPTION_KEY_PREVIOUS to the current key; set APP_ENCRYPTION_KEY to the new key; restart core.
  3. New and updated secrets encrypt under the new key; old rows still decrypt via the fallback.
  4. There is no bulk re-encryption job (documented scope limit). Re-save each credential through its normal update endpoint, then remove APP_ENCRYPTION_KEY_PREVIOUS and restart to retire the old key.

Rotation of other secrets (Keycloak client secret, shared secrets, DB passwords): change the value in deploy/compose/.env or the Kubernetes Secret, update the other side where the secret is shared (for example the Keycloak client and KEYCLOAK_CLIENT_SECRET; the realm reconcile re-syncs it), and recreate the consumers. No rotation schedule is prescribed by the repository.

5. Separation rules seen in the code

Open items in this area (status only)
  • Envelope/KMS encryption not wired; no cloud KMS provider.
  • No documented rotation cadence or evidence of past rotations of the encryption key.
  • The plaintext deploy/compose/.env file on a Compose host is an operator risk: restrict its permissions and its backups; see the Threat Model.

Evidence & references