seekrit

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 is designed to run entirely on a Cloudflare account.

ComponentCloudflare productRole
APIWorkersThe Hono API worker
DatabaseD1 (SQLite)Orgs, users, secrets (ciphertext), grants, audit
CacheKVCached identity-provider JWKS (and future session/rate-limit state)
Web dashboardWorkers (via OpenNext)Next.js app serving the browser client

The API and web app deploy as separate Workers. D1 and KV are provisioned as bindings; the CLI wrangler handles migrations and deploys.

Request lifecycle

  1. A request arrives at the API worker with a credential — a Stytch session JWT, a service token, or (locally) a dev-user header.
  2. Auth middleware resolves the actor (a user or a service token) and, for org-scoped routes, checks membership and role.
  3. The handler reads or writes ciphertext in D1. It never handles plaintext secrets.
  4. 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). 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).
  • CLI / machines — service tokens (skt_…) sent as bearer tokens.
  • Local dev — an x-seekrit-dev-user header, enabled only when the API runs with AUTH_MODE=dev.

The first time a valid session proves access to a Stytch organization, seekrit provisions the matching org and membership just-in-time.