Config options
| Option | Default | Meaning |
|---|---|---|
registry | {} | Your secrets, keyed by name. Edited by hand — not settable from the command line |
rotationIntervalDays | 30 | How often the at-rest key rotates. The manager re-encrypts every stored secret on this cadence |
An empty registry here does not mean an empty registry in the Worker: every capability contributes its own slice at runtime.
A registry entry
Declaring one is the walkthrough; writing its value is pithy secrets; a dev value lives in the config directory.
| Field | Values | Decides |
|---|---|---|
backend | d1, cf-secrets-store | Where the value is stored. A storage decision, not a read-time one |
scope | environment, global | Per environment, or one value everywhere |
valueType | text, json with a schema | What shape it is |
devValue | random, or absent | Whether the kit can mint a dev value |
rotation | local, provider, manual | Who replaces it |
rotateEveryDays | a number | What makes overdue a fact the registry states rather than a number a dashboard invented |
keyed | a flag | A keyspace rather than a single name |
rotateEveryDays is independent of whether anything can rotate it automatically. A third-party key no automation will ever roll is exactly the one whose drift nothing else would surface.
Bindings
| Binding | Type | Written by |
|---|---|---|
SECRETS | d1 | pithy add |
SECRETS_ENCRYPTION_KEYS | secret | pithy secrets provision |
A secret binding has no array in the wrangler config until an account has been reached — the entry needs a store id and an entry name that do not exist yet. So add reports it rather than writing an entry wrangler would refuse.
Its own database, per environment
Separate from the app database, and a singleton per environment rather than per feature.
The reason: a deployed feature Worker still needs the signing keys, and a global secret must be present in every environment at once. The app database is provisioned and torn down per branch; this is not.
A feature gets no secrets manager, deliberately — a manager is a Worker with its own database and its own rotation cron, and one per open pull request is not a cost a branch should carry.
Storage format
Every stored value is one envelope carrying its current version and every still-valid one, sealed once.
The secret’s name is bound in as authenticated data — not encrypted, bound. A ciphertext moved between rows does not open.
The master key
Lives inside that environment’s manager Worker and never leaves it. Nothing reads it back out.
A stored value therefore cannot be copied between environments. A global secret split across environments cannot be completed by reading one of them.
It rotates on its own axis, inside the manager, on its own cron. It is the one secret nothing replaces in place — replacing it would leave everything sealed under a key nobody holds.
Command requirements
| Command | Needs |
|---|---|
ls | Nothing. Offline, no credentials |
edit | Nothing beyond a project name |
create, update, rm | Cloudflare credentials, and a deployed manager |
rotate | The same. --dry-run needs neither |
provision, deprovision | Credentials and a recorded store id |
--env here is the managed set — every environment the project declares — rather than the three --env takes elsewhere. dev is refused, pointing at the dev server.
--env on a global secret is refused, not ignored, and the re-run is the confirmation.
Exit codes
0, and 1 for an ordinary failure. 3 is its own status, for the one failure that cannot be undone: an issuer rolled a credential and the store write failed.
1 means the previous credential is still live and the command can be re-run. 3 means it is not. A script cannot tell those apart from a message, and the two need opposite reactions.
2 is deliberately unused — shells use it for usage errors, and a status that might mean you typed it wrong or a production credential is dead is no signal at all.
Peer capabilities
Required: none. It is the bottom of the graph.
Optional: controlplane — exposes reading the registry and triggering a rotation. audit — records every set, rotation and removal, naming the secret and the environments reached, never the value.
What no surface here can carry
A value. Not on stdout, not in a payload, not in an audit event, not in an error. The rotation result has no field that could hold one, so it is structural rather than a habit.