Skip to content

Roles & access

Roles

RoleDescription
analystread-only on own tenant
adminadministrator of own tenant — agents, rules, billing, identity
ownerfull control of own tenant + role management
auditorexternal accountant; bundle export download via Auditor Workbench (TOTP-prod)
msp_operatorcross-tenant operator — exclusively /api/v1/msp/overview
(is_superadmin)platform superadmin; all MSP endpoints + tenant-suspend

Endpoint gates

Cookie auth + tenant scope is the baseline for every user.* route. On top of that:

EndpointExtra gate
POST /api/v1/trust-score/weightsrole=admin|owner + TOTP-prod
POST /api/v1/auditor/bundlerole=admin|owner|auditor + TOTP-prod
GET /api/v1/auditor/bundles/:t/downloadatomic one-shot, 24h expiry
GET /api/v1/msp/overviewrole=msp_operator OR is_superadmin
POST /api/v1/agents/:id/console/startrole=admin|owner + TOTP-prod + reason ≥20 chars

All agentAuth.* routes (ingest, register, whoami, heartbeat) go through the SHA-256 bearer-token check; no role check needed.

TOTP purpose buckets

Every TOTP-gated action has its own purpose so cross-feature replay is impossible:

PurposeUsed by
console_sessionconsole start
trust_score_weightsweights override POST
auditor_bundlebundle generation POST
force_updateforce-update push to agents
unlock_evidenceAI evidence pack content unlock

A 6-digit code consumed for console_session stays invalid for that same purpose for 30 seconds, but IS usable for auditor_bundle within that same 30-second window — different row in consumed_totp_codes.

RLS

Every tenant table has ENABLE ROW LEVEL SECURITY with a tenant_isolation policy. Important: this is defense in depth. set_config('app.current_tenant', …, true) does not survive pgxpool’s connection recycling, so every production query MUST contain an explicit WHERE tenant_id = $1.

Exception: hub-side workers (TrustScoreWorker, IdentityHygieneWorker, SupplyChainWorker, CorrelationWorker, AuthGeoWorker) intentionally run cross-tenant — they emit/aggregate per tenant via the inner-loop scope.

Audit log

Every role change, weights update, bundle creation/download, link creation, and delete operation goes through audit_log with event_type = <feature>.<action>. Filterable in the Audit page (/audit).