Skip to content

Admin guideSecurity

At-rest encryption

The key ring, rotating keys with toft rotate-key, and the KMS backends.

View as Markdown

Secrets Toft stores — app environment variables, provider client secrets, integration and held credentials — are encrypted at rest.

Variable Role
ENCRYPTION_KEY Encrypts every new write. Required unless ENCRYPTION_KEY_URI is set.
ENCRYPTION_KEY_OLD Comma-separated keys kept for reading only.
ENCRYPTION_KEY_URI Wraps the data key in a KMS: aws-kms://, hcvault:// or azurekeyvault://.

Every ciphertext records the ID of the key that wrote it. The ID is derived from the key itself, so it cannot be mislabeled. A record whose key is not in the ring is reported by key ID, not as a generic decryption failure. Unreadable secrets are counted at startup and never stop the server — sign-in does not depend on this key.

  1. Put the new key in ENCRYPTION_KEY and the previous one in ENCRYPTION_KEY_OLD.
  2. Restart. Everything still reads; new writes use the new key.
  3. Run the sweep with the server’s environment (--dry-run to survey first):
    Terminal window
    toft rotate-key
    It checks every record is readable before rewriting any, and refuses rather than half-finishing.
  4. When it reports nothing left under the old key, remove it from ENCRYPTION_KEY_OLD.

Setting ENCRYPTION_KEY_URI selects it; there is no mode flag and no runtime fallback. If the KMS is unreachable, Toft fails to start rather than using a local key. The local backend is a legitimate choice, not a degraded one.

Refused at startup:

  • an unknown scheme (the error lists the three);
  • an azurekeyvault:// URI without a key version — a vault-side rotation would otherwise silently change which key wraps yours.

Workloads receive decrypted values Toft chose to give them, never a key or the KMS URI.

The encryption row on Admin → System checks names the backend, the current key ID, the ring size and the KMS URI. A database restored without its key is a database of unreadable secrets.