Sigillo encrypts every secret, but the key lives in the Worker that decrypts them. Whoever controls your Cloudflare account, your deploy file, who can sign in or an admin role can get at the secrets. The steps below narrow each of those, most important first.
Who can reach your secrets
Who
What they can do
What limits it
Anyone who can edit Workers on the Cloudflare account
Cloudflare hides a secret's value once it's set, in the dashboard and in Wrangler. Code running in the Worker can still read it, so anyone who can deploy Workers on the account can read every secret. Anyone who can edit its D1 databases can make any account that signs in an org admin.
Deploy into an account that holds nothing but Sigillo: npx @kldzj/sigillo self-host --account <id>.
Keep its members few. Under Manage Account → Members, a Super Administrator can turn on 2FA Enforcement, so every member needs two-factor authentication.
Give people who only need to look a read-only role, such as Administrator Read Only or Workers Platform (Read-only). Administrator and Workers Platform Admin can deploy Workers.
2. Protect the deploy file
npx @kldzj/sigillo self-host saves everything it needs to update your instance in ~/.sigillo/selfhost.json on the machine that ran it:
BETTER_AUTH_SECRET, which signs every session and the history
ENCRYPTION_KEY, which decrypts every secret
the login provider's secret and your Google OAuth client
the Cloudflare login, if you signed in with the browser
It is the only copy outside the Workers: Cloudflare doesn't hand secrets back. The file is encrypted with a passphrase you choose on the first run, and each run asks for it once. Keep the passphrase in your password manager and a backup of the file somewhere else: without both, a lost worker means lost secrets. Runs without a terminal read the passphrase from SIGILLO_SELFHOST_PASSPHRASE. A file from before encryption is encrypted on the next run, if you agree.
Deploy with an API token instead of the browser login, and the file holds no Cloudflare login:
When you create the token under My Profile → API Tokens, set a TTL so it stops working on its own, and Client IP Address Filtering for the machine you deploy from. A browser login you saved before stays in the file, encrypted, and is used again unless you pass a token.
3. Decide who can sign in
By default anyone with a Google account can create an account on your instance. Pass the addresses and domains that may sign in:
The app and its login provider both check the list, and someone taken off it is signed out on their next request. See Limit who can sign in.
If your team is a Google Cloud organization, through Google Workspace or Cloud Identity, create the Google OAuth client in a project that belongs to it and set its audience to Internal (Google Auth Platform → Audience). Google then only signs in members of your organization, before Sigillo's own list is checked.
Everyone can see and end their own sessions under user menu → Sessions: every browser and CLI login, with its device, IP address and sign-in time. Seeing them takes a login from the last day; End all other sessions works from any login. A login ends after 7 days without use, and 30 days after its sign-in however often it's used; the CLI then asks you to run sigillo login again.
4. Limit access inside Sigillo
Few admins. An org admin reaches every project and environment of the org, and manages its members.
Project access. On a project's Access tab, limit a member to selected projects. Invitations can be limited to projects too.
Admin-only environments. On the Environments tab, set Min Role to Admin for production. Members then can't open it, and an API token for it works only while its creator is still an admin.
Protected environments. On the Environments tab, set Protected to On. Reading or changing its values then takes a passkey, see step 5. Every read is recorded before the values leave the server, with who, when, which secrets, how and from which IP, and a read that can't be recorded fails. Org admins see the reads on the Read Log tab. Turning protection off takes a passkey as well, and is recorded too. Protection covers the values: which secrets an environment has is visible to everyone who can open it.
API tokens. Anyone who can open a project's environments can create tokens for them. Only a token's creator or an org admin deletes it. A token belongs to one project, optionally only some of its environments, and expires after 7, 30, 90 or 365 days. The Tokens tab shows when and from which IP each was last used, to the hour. Tokens made before expiry existed never expire and show Never: replace them. Prefer one token per machine or pipeline, scoped to the environment it needs. An API token can't read or change a protected environment; a machine token can.
The secrets page never loads values up front: a value reaches the browser when someone reveals, downloads or copies it, and in a protected environment that is what the Read Log shows.
5. Require a passkey for production
A protected environment doesn't trust a session on its own. Reading or changing its values takes a passkey, so someone who copied a browser cookie or a CLI login from a laptop can neither read it nor swap a value, such as a signing key or a webhook URL, for one of their own. Deleting or renaming a protected environment, or the project it belongs to, is up to an org admin with their passkey, so nobody else can move it aside and let an unprotected one take over its slug or name, or delete it along with its read log.
In the browser, revealing, copying, downloading, saving or deleting a value asks for your passkey on the spot.
In the CLI, sigillo run, secrets set and the other commands print a link and a code. Open /approve on your instance, type the code your terminal shows, and approve with your passkey. The CLI then continues. The page shows which environments the request is for, and the device, IP address and country it came from. The code is typed, never part of the link, so a link someone sends you approves nothing. A passkey on your phone works through the browser's own Use a phone or tablet option, which shows a QR code.
An approval lets that one browser or CLI login read and change those environments for 15 minutes. Other logins of the same person still have to ask.
Logins and tokens. Once you have a passkey, approving a CLI login on /device and creating an API token take it too. Both keep working after the session that made them ends, so a stolen browser session could otherwise turn itself into one. The /device page takes the code typed from your terminal, never from the link, like /approve.
Admin actions. Once an organization has a protected environment, every admin action in it takes a passkey as well: inviting someone, changing a role or a member's projects, removing a member, auto-join, an environment's Min Role or protection, deleting or renaming a protected environment or its project, resetting passkeys, making or deleting a machine token, and deleting the organization. Otherwise someone with an admin's session could invite an account of their own, or open production to one. One approval covers admin actions for 5 minutes. Organizations without a protected environment work as before.
Passkeys. Everyone adds theirs under user menu → Passkeys: a laptop's fingerprint reader, a phone, or a security key. Add two, so losing one doesn't lock you out. The first passkey takes a Google sign-in from the last 5 minutes, in that browser; a CLI login doesn't count, since any session can approve one. Once your organization has an admin with a passkey, an admin also approves each member's first passkey on a project's Access tab, where they see the device and IP address it comes from; nobody approves their own. A member of several organizations with protected environments needs an approval from an admin of each, since a passkey works in all of them. Leaving doesn't get around that: someone who left an organization or was removed still needs its approval for a first passkey, and its admins see the request marked as from someone who left. An invite link made before they left doesn't bring them back, and neither does auto-join. So someone who got into a member's Google account still can't add a passkey unnoticed. Adding or removing one after that takes an approval with a passkey you already have: in the same browser, or, for a new device such as your phone, with a code you type on /approve on a device that has one, like approving the CLI. Asking for that code also takes a fresh Google sign-in. Someone who only has your session can't swap in their own. On a project's Access tab, org admins see how many passkeys each member has and every passkey added or removed. Reset removes a member's passkeys, for someone who lost theirs, and signs them out everywhere; they add new ones after signing in again. Passkeys belong to the person, so only an admin of every organization they're in can reset them.
If you are the only admin and lost every passkey, remove them from the machine that deployed Sigillo:
Machine tokens. CI and servers can't use a passkey. For them, an org admin creates a token on the Tokens tab with Machine token checked. Making it takes the admin's passkey, also before the organization has a protected environment, so a stolen admin session can't leave one behind that reads environments protected later. It expires after 90 days at most, reads and changes protected environments without a passkey, and stops working when its creator is no longer an admin. The Tokens tab marks it Machine. Keep it on the machine it's for, and scope it to the environments it needs: it also reads environments of its scope that are protected after it was made.
6. Check the history
The secret changes and the reads of each environment form hash chains, and the Worker signs every row. Once sigillo audit verify has seen a row, editing, removing or reordering it in the database shows up there. Check an environment as an org admin:
The CLI keeps the newest row of each chain in ~/.sigillo/audit.json, so the next check also notices rows removed or rewritten since. Run it regularly, from more than one admin's machine: each one is a witness the database can't change.
What it can't catch: the signing key comes from BETTER_AUTH_SECRET, so someone who can deploy code to the Worker, or who has selfhost.json and its passphrase, can sign new rows or stop writing them. And the chain only vouches for rows a check has already seen: someone who can edit the database can give an account of theirs access, read a protected environment through the Worker, and delete the newest rows of its read log before the next check. The more often you check, the shorter that window. Changes from before the history existed join it on their environment's next change, and audit verify counts them: the server signed them as it found them in the database. The history makes tampering by a database writer visible; it doesn't replace steps 1 and 2.
7. When someone leaves
On any project's Access tab, remove them. That removes them from the organization: every project at once, the API tokens and machine tokens they created there, and their open invitations. Auto-join doesn't add them back, and neither does an invite link made before; only a new invitation does.
Take their address off the allowlist if it's listed, and remove them from your Google Workspace.
Rotate the secrets they could read. They may have copies. In protected environments, the Read Log shows which values they read.
If they could reach the Cloudflare account, or knew the passphrase of selfhost.json, remove their account membership, choose a new passphrase (self-host --change-passphrase), and rotate the secrets of every environment. A new passphrase doesn't lock them out of a copy of the file they already have, so the secrets have to change. self-host can't rotate BETTER_AUTH_SECRET or ENCRYPTION_KEY yet.
8. Know what else keeps data
Workers Logs. Both workers have Cloudflare's Workers Logs on, and self-host turns them on again with every deploy. A request's log holds its method, URL and headers: the name of a secret fetched on its own, and the client's IP address. Cloudflare replaces credentials such as the Authorization header with REDACTED, as wrangler tail shows. Logs are kept for 3 days on the Free plan or 7 on Workers Paid, and anyone who can read logs on the account can see them.
workers.dev. Both workers keep their workers.dev URLs, also with a custom domain (--domain): self-host uses them, and the login provider only has that one.
Backups. Cloudflare keeps a point-in-time history of each D1 database for 30 days on Workers Paid, 7 on Free. wrangler d1 time-travel restore sigillo-db --timestamp=<unix time> overwrites the database with that point and prints a bookmark that undoes it. Restored data is only readable with the keys in selfhost.json. If the last sigillo audit verify came after the point you restored, it then reports the history as shorter.