swap-secrets-encryption
Take the keys that encrypt secrets in the database from a KMS (self-hosted OpenBao Transit, or a cloud KMS) instead of ENCRYPTION_KEYS. Use when compliance requires KMS-held keys, or the user asks abo
Files1
View on GitHubSKILL.md.claude/skills/swap-secrets-encryption/SKILL.md
GitHubSwap the encryption key source
- Interface:
KeyProvider(active(),get(id)) inpackages/nest-common/src/crypto.ts. - Today:
keysFromEnv(ENCRYPTION_KEYS):id:base64keyentries, first active.SecretBox(AES-256-GCM, ciphertextv2.<keyId>.<iv>.<data>, with the row it belongs to as additional data;v1values, from before that, still decrypt) uses it. - Used in:
apps/api/src/modules/webhooks/webhooks.service.ts(writes endpoint signing secrets) andapps/webhooks/src/outbound/delivery.service.ts(reads them), bothnew SecretBox(keysFromEnv(env.ENCRYPTION_KEYS)). - Env:
ENCRYPTION_KEYSinapps/api/src/env.tsandapps/webhooks/src/env.ts.
Swap
- A
KeyProviderwhose data keys are wrapped by the KMS: store the wrapped keys (with their ids) in configuration, unwrap them once at boot, and serve them from memory.active()andget()are synchronous, so unwrap before the services start using them. Ciphertexts andSecretBoxdon't change, so existing rows keep decrypting as long as their key ids are still provided. - Build it in one place (a factory next to
keysFromEnv) and use it in both services above; they must resolve the same key ids. - New variables (key ARN or name, the wrapped keys) in both services'
src/env.ts,.env.exampleanddocs/environment.md; cluster access to the KMS: OpenBao's Kubernetes auth in the cluster (the self-hosted default; check with the user before a cloud KMS, which also needs credentials in the services' SOPS Secrets). - Keep
keysFromEnvfor development and tests.
Rotating keys is the rotate-secrets skill.
Tests
packages/nest-common/src/crypto.test.ts (round trip, rotation, unknown key) with a
fake KMS; apps/webhooks/test/webhooks.integration.test.ts signs deliveries with
secrets the API encrypted.
Finish
- The verify skill.
- Ask the
revieweragent to review the change, and thesecurity-revieweragent: a new implementation brings its own credentials and sends data somewhere new. If you wrote a migration, themigration-revieweragent too. - Update the seam's row in the README's "Scaling path" table if what's "Now" changed.