ChainLaunch Pro encrypts every secret stored in its database using a single master encryption key. This protects you from cold-storage attacks — if someone exfiltrates a copy of chainlaunch.db, they get nothing useful without the master key. This page explains how the system works, what's encrypted, how to manage the key safely, and how to rotate it.
What gets encrypted
The encryption service (pkg/encryption) is used to seal every long-lived secret before it touches SQLite. Specifically:
Data
Where it lives
Why it's encrypted
SAML SP private keys
sso_providers.saml_sp_key_encrypted
Used to sign AuthnRequests; theft = forge ChainLaunch logins
OIDC client secrets
sso_providers.oidc_client_secret_encrypted
Allows impersonation of the ChainLaunch app at the IdP
Unset — ChainLaunch refuses to start. The empty-key case is treated as a fatal misconfiguration, not a silent fallback.
Form (1) is preferred because it gives you the full 256 bits of entropy. Form (2) is only as strong as the passphrase you pick.
Setting it safely
Environment
How to inject the key
systemd
EnvironmentFile=/etc/chainlaunch/secrets.env (chmod 600, owned by chainlaunch user)
Docker
--env-file secrets.env or Docker secrets (/run/secrets/chainlaunch_master_key)
Kubernetes
envFrom: secretRef: name: chainlaunch-master-key, with a sealed-secret or external-secrets operator
AWS
Pull from Secrets Manager at startup, never bake into the AMI
Hetzner / bare VM
Cloud-init pulls from your secret store on first boot, writes to /etc/chainlaunch/env, chmod 600
Never:
Hard-code the key in docker-compose.yml, chart values, or git.
Echo it into logs (ChainLaunch redacts KEY_ENCRYPTION_KEY in its own log output, but your wrappers might not).
Use a value derived only from the hostname — any attacker who reads /etc/hostname recovers your key.
Reuse the master key across environments. Dev, staging, and prod must each have their own.
Manual encrypt/decrypt CLI
Pro ships three CLI helpers for ops work — useful when migrating credentials in/out of ChainLaunch:
# Encrypt a value (reads KEY_ENCRYPTION_KEY env or -k flag)chainlaunch encrypt --plaintext "vault-token-xxxx"# → "AAA...base64..."# Decryptchainlaunch decrypt --ciphertext "AAA...base64..."# → "vault-token-xxxx"# Generate a new master keychainlaunch crypto generate-key# → "ji5G6w4P7y3KqXr8N2QzL5M0V/uWp9Y7B+T1c4D6E0Q="
These never hit the database — they're pure stdin/stdout. Safe to use in pipelines.
Key rotation
Rotating the master key is not a KEY_ENCRYPTION_KEY=newvalue restart. Every encrypted column on disk was sealed under the old key; flipping the env without re-sealing yields decryption errors at the first read.
The supported rotation procedure:
Take a database backup. This is non-negotiable. See Backups.
Stop ChainLaunch.
Export and re-encrypt every secret with the migration helper:
The helper iterates every encrypted column, decrypts with the old key, re-encrypts with the new key, and writes it back in a single transaction. Crash-safe.
Update KEY_ENCRYPTION_KEY in your environment to the new value.
Start ChainLaunch. Verify by hitting any endpoint that touches a secret (e.g. GET /api/v1/sso/providers).
Securely destroy the old key.
For most teams, rotate annually or immediately on suspected compromise. The key never expires automatically.
✅ See network topology, user emails, audit log content.
For most threat models, the network topology and audit log are also sensitive. Encryption at rest doesn't replace database file permissions, full-disk encryption, and access controls — it adds a layer.
With the database and the master key
✅ Game over. The attacker now has every secret.
This is why the master key must live in a different security domain than the database file. Use a secrets manager, KMS, or sealed-secret system that requires its own credential to retrieve.
With access to the running process
✅ The decrypted master key is in process memory and reachable.
Mitigate by running ChainLaunch as a non-root user, isolating with systemd unit hardening (ProtectHome=true, PrivateTmp=true, NoNewPrivileges=true), and limiting SSH/console access.
Disaster recovery
If you lose the master key, every encrypted column is unrecoverable. There is no backdoor. Concrete consequences:
All SSO providers must be reconfigured from scratch (re-paste secrets/upload IdP metadata).
Vault and AWS KMS integrations must be re-bound (existing key references survive — only the credentials are gone).
SCIM tokens must be reissued; IdPs need the new tokens.
Backup targets must be re-credentialed.
Network and node data is not lost — ChainLaunch can keep operating networks while the integrations are restored.
Mitigation: treat the master key like the root password — store at least two copies in independent secret managers (e.g. AWS Secrets Manager + 1Password Vault), document the recovery flow, run a recovery drill annually.
Verification
# Confirm the key is settest -n "$KEY_ENCRYPTION_KEY" && echo "Key is set (length: ${#KEY_ENCRYPTION_KEY})"# Smoke test: encrypt+decrypt round-tripecho "$(chainlaunch encrypt --plaintext hello | chainlaunch decrypt --stdin)"# → "hello"
Limits & gotchas
No HSM-backed master key (yet). The master key is a symmetric secret in the host's environment. KMS-wrapping the master key is on the roadmap; if you need it today, wrap secrets externally and feed pre-encrypted values via the chainlaunch encrypt CLI.
KEY_ENCRYPTION_KEY cannot be hot-rotated. Restart is required after rotation.
Backup files are not separately encrypted by ChainLaunch — encrypt your backup target (S3 SSE, EBS encryption, restic encryption). See Backups.
Per-tenant keys are not currently supported — one key per ChainLaunch instance. Run separate instances if you need cryptographic separation between tenants.
Next steps
SAML SSO — uses the master key to seal the SP private key.