Architecture
Resource model
seekrit is multi-tenant. Resources nest from organizations down to individual secrets:
organizations ─┬─ members (users, via org_memberships with a role)
├─ groups ──────── environments ─┬─ secrets (+ version history)
├─ applications ─── environments ─┤ (shared, reusable secret bags)
│ └─ composes groups └─ key grants (wrapped DEKs)
├─ service tokens (bound to one app environment)
└─ audit log (append-only)
- Organization — a tenant. Has members, applications, groups, service tokens, and its own audit trail.
- Application — a deployable (a service, site, or worker) within an org.
- Group — a reusable secret bag shared across applications (e.g.
common-backend,auth-providers). Like an application, it owns environments keyed by slug. - Environment — a named context (
production,staging,dev, …) owned by either an application or a group. Each environment owns one data key. - Composition — an application environment pulls in one or more groups, matched by slug. At
read time the layers merge, lowest precedence first:
group secrets < app secrets. - Secret — a named, encrypted value in an environment. Every write appends a new version.
- Key grant — a wrapped copy of an environment's data key for one principal.
- Service token — bound to one application environment; that binding selects the org, app, and environment it resolves at runtime, plus the group slices composed into it.
Roles
Membership carries a role: owner > admin > member.
- Admin and owner manage structure (apps, environments), service tokens, key grants, and can read the audit trail.
- Members read and write secrets in environments they hold a key for.
The real access boundary is cryptographic: you can only decrypt an environment if you hold a key
grant for it. Roles gate the management API on top of that. Non-members receive 404s for an org
so its existence can't be probed.
Cloudflare building blocks
seekrit runs entirely on Cloudflare's edge.
| Component | Cloudflare product | Role |
|---|---|---|
| API | Workers | The Hono API worker |
| Database | Postgres (PlanetScale, via Hyperdrive) | Orgs, users, secrets (ciphertext), grants, audit |
| Cache | KV | Cached identity-provider JWKS (and future session/rate-limit state) |
| Web dashboard | Workers (via OpenNext) | Next.js app serving the browser client |
The API and web dashboard run as separate Workers, backed by Hyperdrive (database) and KV bindings.
Request lifecycle
- A request arrives at the API worker with a credential — a Stytch session JWT (web), a CLI session (a signed-in human at a terminal), or a service token (machines).
- Auth middleware resolves the actor (a user — whether from a browser session or a CLI session — or a service token) and, for org-scoped routes, checks membership and role.
- The handler reads or writes ciphertext in Postgres. It never handles plaintext secrets — including the ciphertext of a value that references another secret, which the client expands after decrypting.
- Any mutating action writes an entry to the append-only audit log before the response returns. High-volume environment resolves are metered for usage/billing instead of audited per call (denied resolves are still audited).
Authentication
- Web — Stytch B2B sign-in (Google/GitHub OAuth discovery, or email/password discovery). The browser holds the session; its JWT is sent as a bearer token and verified by the API against Stytch's JWKS (cached in KV). The auth method is opaque to the API — every method yields the same session JWT, so nothing server-side changes per method.
- CLI (you) —
seekrit loginopens a browser, you authorize the device, and the CLI saves a CLI session (skc_…) that authenticates as your user. The CLI generates that token itself and registers only its hash, so the credential never travels through the API. It carries no key material: decryption still unlocks your own private key with your passphrase, on your machine. - CLI / machines (unattended) — service tokens (
skt_…) sent as bearer tokens. - Agents — Stytch M2M client credentials (OAuth client-credentials), sent as HTTP Basic
auth or as a pre-fetched access token. Agents self-register at
/v1/signupand authorize against seekrit's own client→org mapping (never token claims). A machine credential holds no key material, so it bootstraps a service token for anything that touches ciphertext.
The first time a valid session proves access to a Stytch organization, seekrit provisions the matching org and membership just-in-time.