Keep the encryption key across restarts instead of inventing one

FixKBService
Shipped
August 19, 2026 at 6:38 PM UTC
Author
Kamo
Commit
e64cc8b

Every note body in production was unreadable. NOTES_MASTER_ENCRYPTION_KEY was never set anywhere - not in the deployment, and the deployed ConfigMap carried no notes: block at all - so notes.master-encryption-key resolved to empty and getUserEncryptionKey generated a random AES key into the heap on first use. That key lived exactly as long as the pod. Every deploy, OOM or node move orphaned every note written before it: decryption threw, content was nulled, and since a content save deliberately NULLs contentPlain there was nothing readable left. The note kept its title and colour and lost its body. An absent key now stops the service at construction. Inventing one is the failure mode, not a fallback: a notes service that cannot read yesterday's notes is worse than one that does not come up, because the first destroys data quietly. The key itself comes from the new notes-encryption-key secret, wired through both the ConfigMap and the deployment, and must not be rotated without re-encrypting. Per-member keys now come from HKDF-SHA256 rather than XOR against a repeating "user-<id>" string, which was reversible - one leaked member key plus a known id recovered the master key and with it every other member's notes. Keys are also scoped to the membership now, matching how notes themselves are scoped. Neither change costs a migration: no existing ciphertext was readable to begin with. decryptDTO stops reporting a failure as an empty note and sets decryptionFailed, so a client can refuse to write over what it could not read.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing