Rotate the BYOK Encryption Key
Platform operators can rotate BYOK_ENCRYPTION_KEY without asking customers to re-enter their stored Anthropic keys. The maintenance CLI re-encrypts the BYOK fields for clients, MSP accounts, and sub-organizations in one PostgreSQL transaction. It preserves the credential values, NULL fields, and unrelated platform data.
This operation changes the encryption protecting stored keys. It does not replace or revoke the Anthropic credentials themselves. If those credentials were exposed, revoke and replace them with Anthropic separately.
Before starting
- Use the version of the application containing
scripts/rotate_byok_encryption_key.py, with its Python dependencies installed. - Have an authorized PostgreSQL connection, a verified database backup, the current encryption key, and a new Fernet key stored securely. Historical encrypted backups still require their corresponding old keys.
- Schedule a maintenance window. Stop and drain Web, API, workers, scheduler, and any other process that reads or writes BYOK records. Keep them stopped until the database rotation and every consumer's configuration change are verified.
- Keep the database private. Run from an authorized maintenance container or host with private connectivity; a public database proxy is not required.
The command reads the current key from BYOK_ENCRYPTION_KEY and the destination from the temporary BYOK_ROTATION_NEW_KEY environment variable. Inject these through your secret manager or a protected process environment. Do not paste keys into shell arguments or command history. Generate a new key using cryptography.fernet.Fernet.generate_key() inside your secure provisioning workflow, then save it before use.
It reads DATABASE_URL, or the POSTGRES_HOST, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, and optional POSTGRES_PORT variables when POSTGRES_HOST is set. The latter take precedence, matching the application's connection convention. It does not load .env files or require unrelated provider credentials. Confirm the intended environment in your deployment tooling before running it.
Normal rotation workflow
Run commands from the application root, with the protected maintenance environment configured.
-
Preview. This opens a read-only transaction and reports counts for each of the three tables; it does not change ciphertext:
python -m scripts.rotate_byok_encryption_keysourcecounts values that need rotation,destinationcounts values already using the new key, andnullcounts unset credentials. An unreadable record causes failure; it is never cleared or silently skipped. Preview is a point-in-time check, so apply revalidates everything. -
Stop and drain all application readers and writers if you have not already done so. Prevent automatic restarts during maintenance. Confirm they are stopped in your deployment tooling.
-
Apply the rotation:
python -m scripts.rotate_byok_encryption_key --apply --maintenance-confirmedThe acknowledgment is an operator confirmation, not automatic proof that containers are stopped. The command takes locks on all three tables, processes bounded batches in one transaction, verifies each stored replacement against its original plaintext, and checks the complete plaintext inventory before committing. It never makes partial batch commits. Lock contention times out after five seconds; individual statements time out after sixty seconds.
-
Verify using only the destination key:
python -m scripts.rotate_byok_encryption_key --verifyThis is read-only and requires
BYOK_ROTATION_NEW_KEYbut does not require the old key. It succeeds only when every non-NULL BYOK value can be read with the destination key. -
Set
BYOK_ENCRYPTION_KEYto the saved destination key in every application consumer. Remove the temporary rotation variable from maintenance configuration when finished. Restart the consumers, confirm their runtime configuration, and verify an actual application BYOK read before resuming analysis workloads. Do not print the credential to prove decryption. -
Retain a non-secret record of the command's status and counts. Retain old keys only as required by your backup/recovery policy; remove obsolete copies from active environments.
Failure, retry, and rollback
The command prints structured status and counts without keys, plaintext, ciphertext, database URLs, or data fingerprints. Exit code 0 means the requested operation succeeded; 1 means refusal/failure; 2 means invalid command usage; 3 means the commit outcome is unknown.
- Missing/invalid/wrong key or corrupt record: keep the applications stopped. Restore the correct key or investigate the affected data from a backup. The transaction rolls back changes made before a validation failure. Never delete credentials to make the command pass. A lost original key cannot be recovered by this tool.
- Lock timeout: another transaction still holds conflicting locks. Check for active application processes or another maintenance operation, resolve the contention, and retry. Do not kill unrelated database sessions blindly.
- Interrupted command or lost connection: do not infer rollback from a missing success message. Keep applications stopped and run preview with the same source/destination pair, followed by destination-only verification. Rows already encrypted with the destination key remain unchanged on retry. If verification fails, investigate or retry apply with the same pair; do not switch consumers until verification succeeds.
- Rollback after a successful commit: do not merely restore the old environment variable. Keep applications stopped, swap the protected source/destination inputs (current new key as source, former old key as destination), and run preview, apply, and verify again. Then set every consumer back to the old key before restarting. This preserves the credential values; ciphertext bytes will differ. Reusing a compromised old key is appropriate only as an explicitly assessed emergency recovery measure.
- Empty tables: verification can pass with zero credentials. This does not prove the chosen destination key matches your intended deployment configuration; verify the secret-manager value and consumers independently.
If the CLI is unavailable in a running image, use the reviewed application version in an authorized maintenance environment with private database access. Do not substitute ad hoc SQL that clears the BYOK columns.
Limits
Database locks protect only the transaction. An old-key process that resumes after commit can write obsolete ciphertext or fail to read rotated records. The coordinated maintenance stop and restart remain required; this is not an online rotation/keyring feature. Database schema and the application's encryption format are unchanged.
Re-encryption cannot protect a previously stolen ciphertext copy when an attacker also possesses its old encryption key. Rotate exposed upstream credentials as part of incident response.