Connect Domain
Security

Overview

Tenancy, RBAC, value-checked ownership, secrets handling, and the unauthenticated surfaces.

Tenancy & isolation

Every object belongs to a tenant. Tenant-scoped endpoints filter by the caller's tenant, 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.

Authorization (RBAC)

API keys carry scopes. Admin mutations require apps:admin. A key can only mint another key (or a widget token) with scopes it itself holds — no privilege escalation. Minting a widget JWT requires connections:write, so a read-only key can't gain write via the widget surface.

Ownership is value-checked

Ownership and propagation checks require the exact desired value (TXT equals the challenge; A/AAAA contain the desired IP). Existence alone is never accepted, because the DNS record set is controlled by whoever is being verified.

Secrets handling

  • API keys are stored as SHA-256 hashes; the secret is shown once and never returned again (not by list endpoints, /metrics, or /healthz).
  • Delegated DNS credentials are encrypted at rest (AES-256-GCM) under CREDENTIAL_ENC_KEY; production refuses to store them without a key.
  • The widget JWT signing secret (TOKEN_HMAC_SECRET) must be set to a non-default value in production or the service refuses to start.
  • Application secret refs and internal tenant IDs are never serialized into API responses.

Log hygiene

Key material is never logged (only last-4 for correlation). Ownership challenge values are never logged. Internal errors are logged server-side under a correlation ref; clients receive a generic message — raw driver/SQL errors are never returned, including on the pre-authentication path.

Unauthenticated surfaces

/healthz, /metrics, and /internal/ask require no auth and must be network-isolated in production:

  • /internal/ask answers only {allow, origin} — it does not expose the tenant id.
  • /metrics exposes only aggregate counts (no tenant identifiers or secrets).

CORS

The API is bearer-authenticated with no ambient cookies, so origins are reflected by default. Set CORS_ALLOWED_ORIGINS to restrict to a fixed allowlist as defense-in-depth. Per-application embed control is enforced at the widget layer via allowed_origins.

Exactly-once side effects

The live transition (and its webhooks + usage metering) is guarded by a compare-and-swap so the API and the background worker can't double-fire. Connect-quota reservation is atomic under a Postgres advisory lock.

Reporting

This is an open-source project; report suspected vulnerabilities privately to the maintainers before public disclosure.

On this page