Secret rotation
seekrit can replace a stored secret's value on a schedule — a database password, an API key, a signing secret — and, for databases, change the credential on the system it authenticates to in the same step. Configure it once and the value is never older than your cadence.
This is the mirror image of temporary access. There, seekrit mints a credential nobody stored, and it expires. Here, seekrit replaces a long-lived credential you do store. Same targets, same admin credential, same verifier trick — opposite lifecycle. If you can use a short-lived credential instead, prefer it; rotation is for the credentials that have to be long-lived.
What rotation does
A rotation policy attaches to one secret. When it comes due, seekrit:
- generates a fresh high-entropy value,
- for a database kind, installs it on the target account, and
- writes the new value back as a new version of the secret — encrypted.
Consumers do nothing. The next seekrit run, GET /v1/resolve, or SDK call
returns the new value, decrypted with the same environment key as always. Every
rotation appends a secret_versions row, so the history shows exactly which
versions rotation produced.
Kinds
| Kind | What it rotates | Reaches out to |
|---|---|---|
generated | A random value seekrit is the source of truth for | nothing |
postgres | An existing Postgres role's password | your cluster |
mysql | An existing MySQL/MariaDB account's password | your cluster |
redis | An existing Redis ACL user's password | your instance |
The database kinds are what Vault calls static roles: the account is never created or dropped, only re-keyed. You name an account that already exists and seekrit changes its password in place, so every grant, ownership, and reference attached to it survives.
generated contacts nothing — it is for credentials whose consumer reads them
from seekrit (internal API keys, webhook signing secrets, an encryption
pepper). Rotating one changes the stored value; anything that validates it must
read it from seekrit rather than hold its own copy.
The verifier trick, again
The database kinds keep the same property temporary access has: your database never receives the new password, only a one-way verifier of it.
- Postgres —
ALTER ROLE … WITH PASSWORD '<SCRAM-SHA-256$…>'stores the verifier verbatim. It can't authenticate: SCRAM login needsClientKeyand the verifier holds onlySHA256(ClientKey). - MySQL/MariaDB —
ALTER USER … IDENTIFIED WITH mysql_native_password AS '*<hash>'stores the double-SHA1 hash verbatim. It can't authenticate either (auth proves knowledge of theSHA1(password)preimage). - Redis —
ACL SETUSER … on resetpass #<sha256-hex>stores the digest verbatim.resetpassclears the account's previous passwords first, so the old credential stops working the moment the new one lands, andSETUSERmerges, so the user's key and command permissions are untouched.
So a dump of pg_authid, mysql.user, or the Redis ACL file still can't log in
after a rotation — exactly as before one.
The trust boundary
Rotation is the one feature where something server-side must be able to encrypt into one of your environments, and therefore to decrypt it. There is no way around that: writing a new secret value means producing ciphertext under the environment's data key. We make the boundary as narrow and as visible as possible.
The rotator is just another principal. Every organization has a broker
Durable Object — the same one that mints temporary credentials — with its own
P-256 keypair whose private half never leaves its storage. Enabling rotation
creates an ordinary key grant to that public key: an environment_keys row with
principal_type = rotator. That grant is what lets the broker rotate, and it has
the properties every other grant has.
What follows from that:
- You establish it, not us. The wrapped key is computed on your machine (the CLI or your browser tab) from your own copy of the environment key, and uploaded already-wrapped. The API cannot produce it — enabling rotation requires someone who already holds the key, and it is audited when they do.
- It is per environment. The broker can decrypt exactly the environments where you enabled rotation, and nothing else. An environment with no rotation has no rotator grant.
- It ends when the feature does. Disabling the last rotation policy in an environment (or deleting the last rotating secret) drops the grant.
- A database dump is still only ciphertext. The grant stores the key wrapped to a public key whose private half lives in Durable Object storage, not in D1.
- The plaintext lives in one function. The new value is generated inside the
broker, used to derive a verifier, encrypted, and discarded. What leaves the
broker is an
sc1.blob; what reaches your database is a verifier. Nothing writes it to storage or logs. - Revoking is immediate. The grant is re-read from the database on every rotation rather than cached, so removing it stops rotation at the first step.
This is a deliberate, scoped exception to the zero-knowledge invariant, in the
same family as the admin credential a lease target holds (in_do execution) —
and it is opt-in per environment. If you would rather seekrit never hold the
ability to decrypt an environment at all, don't enable rotation for it: rotate
by writing a new value yourself (seekrit secrets set), which stays fully
client-side.
Scheduling, failure, and the honest caveat
The schedule runs on a one-minute sweep, so a policy rotates within a minute of coming due. Cadence is anything from 5 minutes to a year.
Rotation changes your database before it stores the new value, which is the safer of the two orders: the only failure window leaves the stored value stale rather than storing a password that was never installed. It is worth being precise about what that means:
- If the write-back fails after the database was re-keyed, consumers hold a password that no longer works until the retry succeeds. seekrit records the failure, emails your admins, and retries with a fresh value on a backoff (15 minutes, doubling, capped at 6 hours). The retry re-keys and re-stores together, so the state converges.
- After five consecutive failures the policy stops retrying and goes
failed, so a broken target surfaces instead of retrying forever. Fix the cause and resume it — a successful rotation clears the streak. - Long-lived connections already authenticated with the old password are not affected by a rotation; new connections need the new one. Applications that resolve secrets once at boot should be able to re-resolve (or be restarted) after a rotation. Choose a cadence your deployment can absorb.
Every rotation writes a synchronous audit row (secret.rotated), as does every
failure (secret.rotation_failed) and every configuration change
(secret.rotation_configured / _updated / _disabled). Rotation also purges
the cached resolve for the environment, so nothing serves the previous
ciphertext.
Rotating the key, not the value
Rotation replaces a secret's value. That is different from rotating an environment's data key — issuing a new key, re-encrypting every secret under it, and re-wrapping it for the remaining principals — which is what cuts off a principal that may have cached the old key. See access & key grants for that.
See the rotation guide to set it up, the
CLI reference (seekrit rotation), and the
API reference for the endpoints.