Secrets rotation
Orimora’s secrets do two different jobs, and they rotate in two different ways:
- Encryption keys (
*_ENCRYPTION_KEY, AES-256-GCM) encrypt stored secrets — they are reversible, so rotation means re-encrypting the data with the new key (a script). - HMAC secrets (
*_SECRET) hash tokens at rest — they are one-way, so rotation either uses a dual-key window or requires reissuing the affected tokens.
At a glance
Section titled “At a glance”| Secret | Protects | Rotation |
|---|---|---|
IDENTITY_ENCRYPTION_KEY | SSO provider configs/secrets | Re-encrypt script |
MFA_ENCRYPTION_KEY | TOTP secrets | Re-encrypt script |
LLM_ENCRYPTION_KEY | Stored AI provider API keys | Re-encrypt script |
PUBLISHING_ENCRYPTION_KEY | Webhook/publishing secrets, off-site config | Re-encrypt script (+ re-save off-site) |
API_KEY_SECRET | REST API keys (long-lived) | Dual-key window (API_KEY_SECRET_PREVIOUS) |
SCIM_TOKEN_SECRET | SCIM provisioning tokens | Reissue tokens |
MAGIC_LINK_SECRET | Magic links (15 min), invites, fallback root | Brief window; reissue invites if needed |
SESSION_SECRET | Step-up (10 min) + MFA-pending (5 min) cookies | Rotate anytime — sessions are unaffected |
Rotating an encryption key (AES)
Section titled “Rotating an encryption key (AES)”Encryption keys are rotated with scripts/rotate-encryption-key.mjs, which decrypts every affected row with the old key and re-encrypts it with the new one, atomically. Each row is round-trip self-checked before anything is written, and any row that fails to decrypt with the old key aborts the whole run — there is no partial state.
-
Generate a new 32-byte key:
Terminal window node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -
Dry-run (reads + validates everything, writes nothing):
Terminal window node scripts/rotate-encryption-key.mjs --key llm \--old "$OLD_KEY" --new "$NEW_KEY" --dry-run--keyis one ofidentity,mfa,llm,publishing. It prints a per-table row count. -
Apply (single transaction):
Terminal window node scripts/rotate-encryption-key.mjs --key llm --old "$OLD_KEY" --new "$NEW_KEY" -
Swap the env var to the new value (e.g.
LLM_ENCRYPTION_KEY=$NEW_KEY) and restart the app. -
Verify the relevant feature works (e.g. open an AI/LLM config, or a webhook), then discard the old key.
The ciphertext format has no embedded key version, so rotation re-encrypts the full data set rather than lazily per row. A future enhancement (key id in the payload) could enable rolling rotation; until then, the script’s atomic full re-encrypt is the supported path.
Rotating API_KEY_SECRET (seamless, dual-key)
Section titled “Rotating API_KEY_SECRET (seamless, dual-key)”REST API keys are HMAC-hashed and cannot be re-hashed (the plaintext token isn’t stored). To rotate without breaking integrations, run a dual-key window:
- Set the old value as
API_KEY_SECRET_PREVIOUSand the new value asAPI_KEY_SECRET, then deploy. Existing keys keep validating (Orimora accepts either secret). - Reissue API keys over your chosen window (users regenerate them under Settings → Developers). New keys are hashed under the new secret.
- Once old keys are retired, remove
API_KEY_SECRET_PREVIOUSand redeploy. Any key still on the old secret stops working.
Rotating the other HMAC secrets
Section titled “Rotating the other HMAC secrets”SCIM_TOKEN_SECRET— reissue the team’s SCIM token from the SSO/SCIM admin page and update your IdP. (Set it to an independent value first if it currently falls back toAPI_KEY_SECRET.)MAGIC_LINK_SECRET— invalidates in-flight magic links (15 min) and pending invites. If it isn’t set independently, it is also the fallback forAPI_KEY_SECRET/SCIM_TOKEN_SECRET/EMAIL_CHANGE_SECRET— set those independently before rotating it, or you will invalidate API keys too. Rotate during low traffic; reissue invites if needed.SESSION_SECRET— only signs short-lived step-up (10 min) and MFA-pending (5 min) cookies; sessions themselves are random database ids and are not affected. Rotate anytime — at worst, a user mid-step-up re-verifies.
After any rotation
Section titled “After any rotation”- Confirm the affected feature works end to end.
- Keep the previous key/secret available (offline) until verification passes, then destroy it.
- Record the rotation (date, which secret) in your change log for the audit trail.