Olympus Docs
SecurityEncryption & keys

Secrets management

Where Olympus secrets come from, where they live, how they rotate

Olympus uses many cryptographic secrets across its eight repos. This page is the inventory and lifecycle reference.

The secret catalog

SecretUsed bySource in prodRotatable?
ENCRYPTION_KEYSDK, settings encryptionGitHub Secrets → compose envyes, runbook
SESSION_SIGNING_KEYAthena, session HMACGitHub Secrets → compose envyes, runbook
CIAM_RELOAD_API_KEY, IAM_RELOAD_API_KEYKratos schema reload sidecarGitHub Secrets → compose envyes, runbook
Kratos secrets.cookieKratos, session cookie HMACGitHub Secrets → compose envyes, rotate Kratos config
Kratos secrets.cipherKratos, recovery / verification HMAC tokensGitHub Secrets → compose envyes, rotate Kratos config
Hydra secrets.systemHydra, JWT signing key store encryptionGitHub Secrets → compose envyes, rotate Hydra config
OAuth2 client secretsHydra OAuth2 clientsCreated via Athena, stored in Hydra DB (encrypted at rest by Hydra)yes, via Athena UI
Social IdP client secrets (Google, Apple, …)Hera, OIDC flowsCreated in the IdP, stored in Olympus settings (encrypted by SDK with ENCRYPTION_KEY)yes, via Athena social-connections UI
Email provider API key (Resend, etc.)Kratos courierGitHub Secrets → Kratos envyes, in the provider account
TURNSTILE_SECRET_KEYHera, captcha validationGitHub Secrets → Hera envyes, in the Cloudflare dashboard
Cloudflare API token (if Cloudflare DNS)Caddy DNS-01 ACMEGitHub Secrets → Caddy envyes, in the Cloudflare dashboard
DATABASE_URL (incl. password)Every serviceGitHub Secrets → compose envyes, via DB admin
Postgres CA certEvery service, SSL verify-fullGitHub Actions artifact or static fileyes, runbook
Daedalus secret material (DigitalOcean PAT, etc.)Daedalus deploy wizardStored in daedalus.json locally, never committedrotated per provider
GHCR pull token (if private images)VPS pull during deployGitHub Secrets → compose envyes, GitHub PAT rotation
SSH host keyVPS accessGenerated once during VPS initrare, replace VPS if compromised
Daedalus MCP, n/a(localhost only, no auth, see ADR 0022)-,

Where secrets live

GitHub repository secrets

The canonical store for production secrets is GitHub Actions Secrets in the OlympusOSS/platform repo. The deploy.yml workflow injects them into the compose stack at deploy time.

To list current secrets:

gh secret list -R OlympusOSS/platform

To rotate:

gh secret set ENCRYPTION_KEY --body "$(openssl rand -base64 32)" -R OlympusOSS/platform
gh workflow run deploy.yml -R OlympusOSS/platform

Compose environment

The deploy workflow renders a .env.prod from GitHub Secrets, scp's it to the VPS, and starts the compose stack with --env-file. The file is not persisted long-term, it's regenerated each deploy.

Database (Hydra-stored secrets)

OAuth2 client secrets created via Athena are stored in iam_hydra / ciam_hydra databases. Hydra encrypts them at rest using secrets.system (which is in turn loaded from compose env).

Database (SDK settings)

Social IdP secrets, SMTP credentials, etc. are stored in the olympus database as encrypted settings. The SDK encrypts using ENCRYPTION_KEY (compose env) with HKDF-derived per-record keys. See Security, Encryption at Rest.

Daedalus

When using Daedalus to deploy, the wizard collects secrets during the Secrets wizard step and writes them to daedalus.json on the operator's laptop. Daedalus then gh secret set's them into the GitHub repo Secrets store. The local daedalus.json is not committed (gitignored).

What's NEVER in source

The platform CI workflow verify-prod-config.yml enforces:

  • No literal secret strings in compose files.
  • No literal secret strings in Kratos / Hydra configs (only ${VAR} references).
  • No secrets in identity schemas.

The gitleaks step (when properly licensed) scans git history for secrets, see the platform CI for the current state.

Audit cadence

Every quarter, run the Secrets Audit runbook. Checklist:

  • For each secret in the catalog, verify it has rotated within the last N days (where N depends on the secret type, ENCRYPTION_KEY quarterly, OAuth2 secrets yearly unless suspected, etc.).
  • Verify GitHub Secrets has only the expected secrets and no orphans.
  • Verify no secret material is in repo source.
  • Verify the verify-prod-config.yml workflow has been passing.
  • Confirm offboarded employees no longer have access to any secret material (clean up Daedalus instances, rotate any secret they had access to).

When a secret is compromised

  1. Rotate immediately (don't wait for the quarterly audit).
  2. Revoke or invalidate the leaked value where possible (e.g. for OAuth2 client secrets, change them in Athena → the old one stops working).
  3. Audit what the compromise reach: which services used this secret, what data could have been read or modified.
  4. Document in the incident retrospective, what happened, what we changed, what's the long-term mitigation.

For ENCRYPTION_KEY compromise specifically: every encrypted settings value should be considered compromised. Rotate the key, but also assume the leaked plaintexts (OAuth2 client secrets, SMTP credentials, etc.) are compromised, rotate those too.

On this page