Runbook, key rotation
Applies to: Lantern signing key, Codex backend HMAC key, Keeper bot keys.
Praetor multisig and EOA founder hardware wallets are out of scope here, those rotate per their own physical-security procedure.
Lantern signing key (annual)
- Generate a fresh secp256k1 private key offline on an air-gapped machine.
- Derive the public address. Update
LanternAttestor.signing_keyviapraetor multisig schedule --target lantern-attestor --call <abi-encode setSigningKey(newAddress)>. - Encrypt the new private key on disk with Argon2id and a fresh passphrase.
- Split the new key 3-of-5 via Shamir secret sharing (
ssssCLI). Distribute shares to founders + 2 trusted advisors via separate physical channels. - After the 48h timelock, execute the multisig action.
- Run
praetor lantern publish-nowto confirm the new key produces a valid attestation. - Securely erase the previous key from the VPS using
shred -u. - Document the rotation in
/incidents/key-rotation-YYYYMMDD.md.
Codex HMAC key (quarterly)
- Generate a 32-byte random secret.
- Set the new secret via
wrangler secret put CODEX_HMAC_KEY. - Increment
CODEX_KEY_IDto the next version, e.g.v1→v2. - Deploy Codex (
pnpm --filter @atrium/codex deploy). - Keep the previous key valid for 24 hours via dual-verify in
sign-response.tsfor client compatibility. - Remove the previous key after 24 hours.
Keeper bot keys (on compromise only)
- Stop the affected keeper process.
- Run
praetor keepers slash --keeper <addr> --reason "compromised"to remove the keeper from the active set. - Generate a new keeper EOA.
- Run
praetor keepers stake --keeper <new-addr> --amount <wei>to onboard the replacement. - Update the VPS env files with the new private key.
- Restart the keeper process.
Chaos drill key (per drill)
The Chaos Mode (an earlier hardening cycle) uses an isolated EOA so a drill cannot accidentally leak the deployer or keeper key. Rotate before every demo + after every public rehearsal.
- Generate a fresh EOA:
cast wallet new. - Fund with ≤ 0.02 ETH from the deployer (drill-budget only).
- Add to the Praetor multisig as a chaos-only operator (cannot
schedule timelock txs, only invokes emergencyPause via the CLI).
- Update
CHAOS_PRIVATE_KEYenv in the GHA chaos workflow. - After the drill: zero the key on disk + rotate per this section.
Sumsub webhook secret (on compromise or quarterly)
The KYC webhook (an earlier hardening cycle) signs each callback with a shared secret. A compromise lets an attacker fabricate tier-upgrade events that move users into higher-leverage tiers without real KYC.
- Open the Sumsub dashboard → Integrations → Webhooks → Rotate.
- Copy the new secret.
- Update
SUMSUB_WEBHOOK_SECRETin:
- Vercel verify-app project env (production + preview)
- GHA repo secrets (for any cron that mirrors webhook events)
- Trigger a test webhook from the Sumsub dashboard. Confirm the
/api/edict/sumsub-webhook route returns 200 with a valid signature check.
- The old secret stops working immediately, confirm any in-flight
webhook retry from Sumsub is also re-signed.
Research signer key (on compromise or annual)
RESEARCH_SIGNER_KEY signs the weekly publishBacktest tx. A leaked key lets an attacker publish a counterfeit ResearchAttestation that the /research page would display as canonical.
- Generate a fresh secp256k1 key offline.
- Update
RESEARCH_SIGNER_KEYin.github/workflows/archive-weekly.yml
repo secrets.
- Call
ResearchAttestation.setSigner(newAddress)via the Praetor
multisig (48h timelock).
- After timelock execution, trigger
archive-weeklyvia
workflow_dispatch to confirm the new signer is accepted.
- Document the rotation in
/incidents/key-rotation-YYYYMMDD.md.
Notifier internal key (on compromise or quarterly)
ATRIUM_INTERNAL_KEY is the Bearer token the notifier service passes to the verify-app's /api/settings/notifications route (an earlier hardening cycle auth fix). A leak lets an attacker read or overwrite any user's notification prefs.
- Generate a 32-byte random secret:
openssl rand -hex 32. - Set in BOTH places at the same time so no in-flight tick is
rejected:
- GHA repo secret
NOTIFIER_INTERNAL_KEY(consumed by
.github/workflows/notifier-cron.yml as ATRIUM_INTERNAL_KEY)
- Vercel verify-app project env
ATRIUM_INTERNAL_KEY
- Trigger a notifier tick via
workflow_dispatch. Confirm the tick
log shows successful fetchPrefs (not 401).
- Old secret stops working at the next tick, no draining required.