Concepts

Security model

The isolation, authentication, and storage guarantees Prometheus makes — and the threat models they address.

Prometheus protects three things, in this order: other tenants' data, your secrets, and your source. This page lays out the mechanisms, what they assume, and where the boundaries are.

Authentication

Two surfaces, two mechanisms:

SurfaceCredentialLifetimeNotes
Worker / MCP (api.prom.codes)API key (bearer)until revokedHashed with Argon2id; cleartext shown once.
Dashboard (app.prom.codes)Magic link → session cookie60 days, slidingCookie is HttpOnly, Secure, SameSite=Lax, scoped to .prom.codes.

There are no passwords. The dashboard uses a managed magic-link provider for the sign-in flow; the session cookie it issues is distinct from the prom_tenant cookie that tracks the active tenant.

Authorisation — row-level security

Every domain table (workspaces, files, symbols, references, embeddings, index_runs, api_keys, tenant_members, tenant_invitations, tenant_audit_log, ...) carries a tenant_id column and a Postgres CREATE POLICY enforcing it. Reads and writes go through a connection that has set:

SET LOCAL request.jwt.claim.tenant_id = '...';

A key scoped to tenant T cannot read a row tagged T' — not via the worker, not via the MCP server, not via direct Postgres if the key ever leaked. Integration tests in CI round-trip an insert under two distinct tenant identifiers and assert cross-tenant reads return zero rows. The suite breaks the pipeline before deploy on a regression.

Inside a tenant, the dashboard adds a coarser role check (owner / admin / member) for mutating UI actions; see Members & invitations.

Secrets at rest

SecretStorageReversible?
API key cleartextnever persistedn/a
API key hashapi_keys.hash (Argon2id)no
Invitation token cleartextonly in the emailn/a
Invitation token hashtenant_invitations.token_hash (SHA-256)no
Session cookiessigned JWT, expires after 60 daysno
prom_tenant cookiesigned payloadtampering is rejected
Source filestmpfs on the worker, deleted after the runn/a

The worker disk is tmpfs. Clones never touch persistent storage.

Data in transit

  • All public endpoints are HTTPS-only (HSTS). Plain HTTP redirects to HTTPS at the edge.
  • The indexer worker talks to Postgres over TLS using the managed certificate of the EU Frankfurt cluster.
  • The embedding hop goes out over TLS to the configured provider. Region mode controls whether that provider is US-default or EU-strict — see Region mode.

Data sovereignty

StageRegion
Web + dashboard (prom.codes, app.prom.codes)EU Frankfurt edge
Worker (api.prom.codes)EU Frankfurt
Postgres (symbols, embeddings, audit)EU Frankfurt
Embedding hop — default and eu-permissiveUS (provider)
Embedding hop — eu-strictEU Frankfurt (operated by us)

Storage is always EU. The single optional out-of-region hop is the embedding call, which carries chunk text only — never symbol names, file paths, or commit metadata. Switch to eu-strict to eliminate that hop entirely.

Audit trail

Every meaningful mutation writes a row to tenant_audit_log:

  • Tenant create / rename
  • Workspace create / rename / delete
  • API key create / revoke
  • Member invite / invite-revoke / invite-accept / remove
  • Tenant switch (cookie level)

The audit log is append-only at the application layer; there is no UI to edit or delete rows. The same surface backs the Audit page and the CSV export, both filtered by tenant via RLS.

What we do not store

  • Commit history or author identity.
  • The plaintext of any secret — API keys, magic-link tokens, invitation tokens.
  • Embedding-provider responses beyond the resulting vectors and a request-scoped audit row in eu-permissive mode.
  • Files outside the configured include / exclude globs.

Reporting a vulnerability

Mail security@prom.codes with the details. We acknowledge within one business day (CET) and aim for a remediation timeline within three business days for high-severity issues. We do not currently run a paid bug bounty, but we publicly credit reporters in /changelog with their consent.