Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

How Sigillo protects your secrets

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. This page explains where each of those is; the hardening checklist narrows them, most important first.

Where everything runs

Every instance is two Cloudflare Workers on your account, each with its own D1 (SQLite) database:
┌─────────────────────────────────────────────────────────────────┐ │ Your Machine │ │ │ │ sigillo run -- next dev │ │ │ │ │ │ device flow login (RFC 8628) │ │ │ or bearer token │ │ ▼ │ │ ┌──────────┐ │ │ │ Sigillo │ │ │ │ CLI │ │ │ └────┬─────┘ │ │ │ │ └───────┼─────────────────────────────────────────────────────────┘ │ REST API ▼ ┌──────────────────────┐ ┌──────────────────────┐ │ App Worker │ │ Provider Worker │ │ (self-hosted) │────────▶│ (self-hosted) │ │ │ OAuth │ │ │ • Secrets CRUD │ PKCE │ • Google login │ │ • AES-256-GCM │ │ • OAuth2 / OIDC │ │ • Audit log │◀────────│ • Dynamic client │ │ • API tokens │ token │ registration │ │ • Device flow │ │ │ │ ┌────────────┐ │ │ ┌────────────┐ │ │ │ D1 (app) │ │ │ │ D1 (auth) │ │ │ └────────────┘ │ │ └────────────┘ │ └──────────────────────┘ └──────────────────────┘
  • The app is the secret manager: encryption, organizations, projects, environments, the web UI and the REST API.
  • The provider is the instance's own OAuth provider, deployed next to the app as <name>-auth. People sign in with Google through it. The app registers itself with it on its first request via RFC 7591 dynamic client registration, as a public PKCE client that needs no client secret.
Nothing depends on anyone else's servers.

Who can reach your secrets

WhoWhat they can doWhat limits it
Anyone who can edit Workers on the Cloudflare accountDeploy code that reads every secretA dedicated account
Anyone who can edit the D1 databasesMake any account that can sign in an org admin, change the historyA dedicated account, protected environments, the signed history
Anyone with ~/.sigillo/selfhost.json and its passphraseDecrypt a database export or backup and use the sessions in an export, sign history rows, and with a saved login deploy to the accountThe passphrase
Anyone who can sign inWhat their org role and project access allowThe sign-in allowlist
A CI job or pod that a trust rule acceptsWhat the rule grants, with a token of one hourNarrow trust rules
Anyone with a stolen browser session or CLI loginWhat that person can read and change, except protected environments, and admin actions in an org that has onePasskeys
Org adminsEvery project and environment of their orgFew admins

Encryption

Every secret value is AES-256-GCM encrypted before it's stored. Each write gets a random 12-byte IV, and binds the value to its environment and name, so a copy elsewhere in the database doesn't decrypt.
  • Key 0 is the Worker's ENCRYPTION_KEY, 32 random bytes. Deployments made before there was one derive it from BETTER_AUTH_SECRET with SHA-256.
  • After a key rotation, the Worker's ENCRYPTION_KEYS holds the newer keys and names the current one. Every value names the key it was encrypted with.
  • The keys are never in the database, and a backup doesn't hold them either: they are in the Worker and in ~/.sigillo/selfhost.json.
Secrets are stored as a log of changes, and a secret's value is its latest one. That gives the full history of every change, with who made it, and the signed history makes an edited or removed row visible. The web UI never loads values up front: a value reaches the browser when someone reveals, downloads or copies it.

Signing in

People sign in with Google through your provider. The CLI signs in with the device flow: the terminal shows a code, and you approve it in the browser.
CLI/Agent App (self-hosted) Provider (self-hosted) │ │ │ │ POST /api/auth/device/code │ │ │─────────────────────────────▶│ │ │ { user_code, device_code } │ │ │◀─────────────────────────────│ │ │ │ │ │ User opens /device │ │ │ and enters user_code │ │ │ ┌────────────────────┼────── redirect ───────────────▶│ │ │ │ │ │ │ │ Google sign-in ──▶│ Google │ │ │ ◀── callback ─────│ │ │ │ │ │ │ │◀── auth code (PKCE) ───────────│ │ └────────────────────┼────── approved ───────────────▶│ │ │ │ │ Poll /api/auth/device/token │ │ │─────────────────────────────▶│ │ │ { access_token } │ │ │◀─────────────────────────────│ │
A login lasts until it ends, or you end it on your sessions page. Machines use tokens or workload identity instead.

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.
  • Old values. The history keeps every value a secret ever had, until an admin purges 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.
  • Cloudflare's time travel keeps the last 30 days of each database (7 on the Free plan): see back up and restore.