Overview
Tenancy, credential model, value-checked DNS, one-time provider tokens, and the unauthenticated surfaces.
Tenancy & isolation
Every object belongs to a tenant. Tenant-scoped endpoints filter by the caller's
credential, and all /applications/{id}/* mutations verify the application
belongs to the caller before acting. Cross-tenant access returns 404 — never
another tenant's data, and never a 403 that would confirm an object exists.
Credential model
Three credential types with escalating authority (see Authentication):
- Widget JWTs are short-lived, scoped to one application (and optionally one domain), and accepted only on the browser-facing Connect endpoints. This is the only credential that reaches the browser.
- API keys carry full tenant authority; they are stored as hashes and shown once at creation, and revocation takes effect on the next request.
- The management key and provision secret gate operator/signup routes; in production the control-plane refuses to start with provisioning left open.
Human authorization is by member role (owner/admin/member/viewer),
not per-key scopes — the matrix is served at GET /v1/rbac.
DNS verification is value-checked
Propagation and drift checks require the exact desired value: a CNAME must
resolve to the expected target and an A/AAAA must contain the exact address.
"The name resolves to something" is never accepted, because the record set is
controlled by whoever is being verified.
Provider credentials are never stored
The automatic rails use a customer's DNS-provider credential exactly once and discard it:
- OAuth (Rail C) exchanges a code for a one-time access token, writes the
records, and drops the token. The flow uses PKCE (S256) and an
HMAC-signed, single-use
state; the callback only postMessages its outcome to a return origin on the server's allowlist (OAUTH_ALLOWED_RETURN_ORIGINS). - API token (Rail B) uses the scoped token you pass on a single write; it is never persisted.
- Domain Connect (Rail A) uses no server-to-server credential; its apply URLs
are RS256-signed from
DC_SIGNING_KEY_FILE.
Secrets handling
- API keys and application
client_secrets are stored only as hashes; the plaintext is shown once and never returned again. - Widget JWTs are signed per application (HMAC over the app's secret hash), so a token is only valid for its own application.
- The console never exposes the tenant's control-plane API key to the browser: it
is sealed at rest (AES-256-GCM under
WEB_SECRET_KEY) and opened server-side per request behind a proxy — see Architecture. - Internal errors return a generic
title; the specifics (including any driver errors) are logged server-side under a request id, never returned.
Unauthenticated surfaces
GET /healthz— liveness + DB readiness; no secrets, no tenant data.POST /internal/ask— the edge host-authorization gate, authenticated by a shared secret (EDGE_ASK_SECRET) and meant to be network-isolated to the edge. It answers only{ approved }and never reveals the tenant.GET /v1/connect/oauth/callback— authenticated by its signed single-usestate, not a bearer token.
CORS
The API is bearer-authenticated with no ambient cookies, so it reflects a
wildcard origin (Access-Control-Allow-Origin: *) — safe because there are no
cookies to abuse. Per-application embed control lives at the widget layer.
Exactly-once side effects
The live transition (and its connection.live webhook + usage metering) is
guarded by a compare-and-swap so the API path and the background poller can't
double-fire.
Reporting
This is an open-source project; report suspected vulnerabilities privately to the maintainers before public disclosure.