Reference Implementation v0.4
Please note that this content is under development and is not ready for implementation. This status message will be updated as content development progresses.
Back up the Reference Implementation database, and record the encryption key the
deployment currently runs with (SERVICE_ENCRYPTION_KEY in earlier versions). This
release encrypts credential decryption keys at rest under that key, so from this
version onwards losing the key means losing access to every privately stored
credential. Store the key with the same care as the database backups: a database
backup without the matching key cannot be recovered.
Overview
Version 0.4 encrypts the per-credential decryption key before it is persisted, closing the gap where anyone with read access to the database could recover keys for privately stored credentials. Alongside the change, the encryption key environment variable is renamed, and a one-off operator-run backfill encrypts the keys of credentials created by earlier versions.
Environment variable rename
SERVICE_ENCRYPTION_KEY is renamed to DATA_ENCRYPTION_KEY to reflect its broadened
role (it now protects service instance configurations and credential decryption keys).
- Keeping only
SERVICE_ENCRYPTION_KEYset continues to work and logs a deprecation warning. Rename the variable at your convenience; removal of the fallback is tracked in #721. - Setting both names to the same value works and logs a reminder to remove the old name.
- Setting both names to different values fails before anything encrypted is
written (on the standard Docker path this surfaces at startup, when the seed
resolves the key). Two different keys have no valid meaning (key rotation is not
yet supported), and proceeding would split the encrypted data across keys. In
particular, do not copy the placeholder
DATA_ENCRYPTION_KEYfrom.env.exampleinto an existing deployment that still has its realSERVICE_ENCRYPTION_KEY; rename your existing variable instead, keeping its value.
Decryption-key backfill for existing credentials
Credentials created by earlier versions still hold their decryption key in plaintext. They keep working unchanged after the upgrade (the read path recognises legacy plaintext keys), so the backfill is not required for continuity. Run it once to bring existing rows under encryption at rest; until it has run, the confidentiality improvement only covers newly issued credentials.
Unlike the automatic data backfills that run when the container starts, this backfill
is deliberately manual: wrapping keys under a wrong DATA_ENCRYPTION_KEY would be
unrecoverable, so a human confirms the key before anything is rewritten.
Before running it:
- Back up the database.
- Confirm
DATA_ENCRYPTION_KEYis exactly the key the running application uses. - Stop any older application instances that might still write plaintext keys during a rolling upgrade (or plan to re-run the backfill after they are gone; re-running is safe and converges).
From a source checkout, in packages/reference-implementation:
pnpm backfill:decryption-keys
Inside the published Docker image (which ships no pnpm):
docker compose exec -w /app ri node_modules/.bin/tsx scripts/backfill-decryption-keys.ts
# with the force flag, after out-of-band key verification and a backup:
docker compose exec -w /app ri node_modules/.bin/tsx scripts/backfill-decryption-keys.ts --force
The run protects itself before writing anything: it decrypts every existing encrypted
value it can find (credential keys and service instance configurations) and aborts on
any failure, naming the failing row. When the database holds nothing encrypted to
check against, the run refuses to proceed and asks for --force; only pass it after
verifying the key out of band, with a backup in hand:
pnpm backfill:decryption-keys -- --force
Rows whose stored value looks like a corrupted envelope are skipped and listed rather
than re-encrypted; inspect any such rows manually. After the run, confirm a wrapped
key still round-trips by retrieving a credential through the API
(GET /api/v1/credentials/{id}) and checking its decryptionKey verifies as before.
Rollback
After the upgrade has written any encrypted key (a newly issued credential, or a completed backfill), rolling back to an earlier application version returns the raw envelope JSON in place of those credentials' decryption keys, because earlier versions have no unwrap step. Plan the upgrade as forward-only once credentials have been issued, or restore the paired database backup when rolling back.
Key rotation
Rotating DATA_ENCRYPTION_KEY is not yet supported: existing envelopes are readable
only under the key that wrote them. Support for rotation is tracked in
#720. Until it lands, treat the
key as immutable and recoverable.