Skip to content

Access, roles and audit

Navigate: Administration → Users & Access (/administration/users), Governance → Elevations (/elevations), Governance → Audit Log (/audit).

Paridity's access model has three moving parts: a role says what you can do, a grant says where you can do it, and an elevation grants a role temporarily with a second person's approval.


Roles

Eight roles. Four of them — platform_admin, tenant_admin, operator, and auditor — carry a shared read set covering mappings, systems, runs, masked evidence, metrics, and the audit log; dba, steward, analyst, and viewer hold only their role-specific grants.

Role What it is for Notably can Notably cannot
Platform Admin The installation owner Everything, including granting tenant_admin, setting platform defaults, and the five privileged sub-gates (configure decryption, asymmetric transforms, broad anomaly policy, a notification channel's credential and destination, an identity provider's issuer / client secret reference / JWKS base) —
Tenant Admin Full authority within one tenant Everything a platform admin can, within the tenant: register and manage SAML sign-on, edit an identity provider's name, enabled state, audience, client id and email domains Grant tenant_admin; change platform settings; the five sub-gates - so a tenant admin cannot add an OIDC provider or change which secret it uses or which host the sign-in talks to (those pick a deployment-held secret and where its value is sent)
Operator Day-to-day reconciliation work Create/edit mappings, trigger and cancel runs, schema refresh, field config, schedules, export evidence See raw evidence values; manage users or grants
DBA Connection custodian Read and update systems and their connections, run discovery Touch mappings, evidence values, or governance
Steward Data owner for a domain Update mappings and classifications, read raw evidence, acknowledge and resolve anomalies Read the audit log — which is why restricted classification is closed to stewards
Analyst Reads results Read masked evidence and metrics, acknowledge anomalies, export Read raw values; read the audit log
Auditor Independent review Read-only everything, raw evidence, restricted/regulated exports, audit export Any write at all
Viewer Dashboard only Metrics Everything else

The audit log is broader than you might expect. operator inherits audit read access through the shared read set. If your control design assumes only admins and auditors can read it, verify that against your role assignments.

Permissions that are allowed but held

Seven permissions resolve to "allowed, pending approval" rather than a straight yes: mapping.bypass_readiness, mapping.mass_delete, evidence.retention_purge, evidence.legal_hold, evidence.export_restricted, principal.create_service_account_broad, and grant.manage_tenant_admin.

Holding the role is not enough — an active approved elevation must clear the gate. This is the break-glass boundary.


Grants — where a role applies

A grant binds a role to a scope. Resolution, in order:

  1. A tenant-scope grant covers everything in the tenant.
  2. An exact scope match covers that resource.
  3. Business domains inherit: a grant on a parent domain covers every descendant domain.
  4. A business-domain grant narrows list endpoints instead of denying them, and covers an individual resource only when that resource's own domain is at-or-under the granted domain. This is enforced: opening a mapping, system, schedule, replication profile, mapping group, run, evidence page or evidence bundle outside your domain by direct link returns "outside your business-domain scope" rather than the object. Creating a mapping, system or replication profile in a domain you do not hold is refused the same way. The Evidence page's own mismatch-key and field-diff tables, and the evidence-bundle list, are scoped the same way — the list shows only bundles for mappings in your domains.

Two things a business-domain grant does not confine, by design: identity and grant metadata (principals, grants and business-domain records have no domain owner, so they stay tenant-readable for roles whose bundle includes them), and auto-config apply, which creates its mappings in the tenant's root domain.

Grants come from three places — assigned directly, inherited via group membership, or synthesised from an active elevation. The Users page labels the source, and only direct grants can be revoked there.

Disabled principals get nothing, and expired grants stop applying immediately. Authorization decisions are recorded with the permission, scope, matched grant and actor IP.

Every decision is recorded on its own, so the record does not depend on what happened to the request afterwards: a refusal that stops the request at the door — the 403 you get for opening something outside your domain — leaves its deny row, and a request that was allowed and then failed for some unrelated reason still leaves its allow row. Read the decision log as a complete list of the access checks the platform performed.

Upgrading from an older release? Before this was fixed, a refusal that raised the 403 lost its row with the rolled-back request session, so decision history recorded before the upgrade under-reports denials. Only checks that ran inside a request which still returned a response were kept.

What is recorded is what was enforced, not what the grant alone would have allowed. Each decision carries a reason:

Reason Recorded as Means
grant allow a matched grant permitted it
no_grant deny no grant covered the request
approval_required deny a grant matched, but the permission is approval-gated and no active elevation cleared it — the request was refused with APPROVAL_REQUIRED. The matched grant is still recorded, so you can see the base entitlement behind the refusal
elevation allow an active approved elevation cleared the gate; that elevation's id is recorded alongside

Decisions written before this was introduced have an empty reason — they are left as they were rather than reclassified after the fact.

Assigning a role

Users & Access → expand a user → Assign Role. Choose the role, then the scope: Entire tenant, Specific business domain, or Specific system. For platform_admin and tenant_admin the scope picker disappears — those are always tenant-wide — and a reason is mandatory for the SOX trail.

You can only hand out what you hold. The role list offers only roles at or below your own highest tenant-wide role, and the API enforces the same rule:

  • a role above yours is refused with 403 GRANT_ROLE_ABOVE_CALLER. Only a platform_admin can grant (or revoke) platform_admin; a tenant_admin can grant every other role. The same ceiling applies to revoking another principal's grant, so a tenant_admin cannot remove a platform_admin's access. Removing one of your own grants is always allowed.
  • you cannot grant yourself, or a group you belong to, a role you do not already hold: 403 GRANT_SELF_ESCALATION. Ask another administrator.
  • an active break-glass elevation does not raise this ceiling - it is time-boxed, and grants are not.

Every refusal is written to the audit log as grant.refused (warning), with who asked, for whom, which role and why.

Three things to know before you rely on this screen:

  • Granting tenant_admin needs a second administrator. It records a pending approval (HTTP 202) and creates no grant yet; the dialog says so and stays open until you close it. The request appears in the Pending approvals card on this page, where a different administrator holding approval.decide approves or rejects it, with an optional comment. Approving creates the grant (Granted by = the approver). You cannot approve your own request (403 SELF_APPROVAL_FORBIDDEN - the button is disabled), and the role ceiling above applies to the approver too: a tenant_admin held only through a break-glass elevation cannot approve, and nobody approves their own promotion. You can Cancel a request you raised while it is pending. Requests expire after 7 days and then show as Expired; raise the grant again. Each decision is audited (approval.approved, approval.rejected, approval.cancelled); "Show decided" lists past requests.
  • Grants created in the UI never expire. The API supports an expiry; the dialog does not expose it, so the Expires column will read "Never".
  • The API supports six scope types (tenant, business domain, system, replication profile, mapping group, mapping); the UI offers three.

Users and service accounts

Users are created automatically on first SSO login, or pre-registered here so their roles are ready before they arrive — matching is by the email their identity provider asserts.

Add Service Account issues an API key shown exactly once. Copy it then; it cannot be retrieved later. Keys look like dak_<prefix>_<secret>; new keys carry a 12-character prefix, and keys issued earlier — with an 8-character prefix — keep working with no deadline and nothing to rotate. Issuing and revoking a key are both recorded in the audit trail alongside the grant events.

Send the key as an X-API-Key header. It authenticates as the service account itself, so what it can do is decided by the grants you give that account — a service account with no grants can sign in and read nothing.

Two notes for anyone upgrading. Service accounts created before this release were never fully registered, so their keys were refused on every call; the upgrade repairs them in place and their existing keys start working, with nothing to re-issue. And a service account's name must be unique within your organisation — creating a second one with a name already in use is refused rather than silently accepted.

Disable user prevents login while preserving the audit trail — prefer it to deletion. Mark break-glass lets an account sign in with local credentials during an SSO outage; those logins are audited.

Reset identity binding is the recovery path after an identity-provider migration. Paridity links an account to the provider that first authenticated it; a later sign-in for the same email from a different provider is refused rather than silently re-bound, because a silent re-bind is how an account gets taken over. The refused user sees "Sign-in refused" and is told to contact you — retrying will not clear it.

Resetting unbinds the account so the user re-links on their next single sign-on. The account, its roles and its history are all preserved. A reason is required and is written to the audit log. Two refusals worth recognising: the account has no email address to key the placeholder on (set one first), or another account already holds that placeholder (merge or disable it first).


Elevations — temporary privilege with a second pair of eyes

Navigate: Governance → Elevations. Everyone can see this page, but non-approvers see only their own requests.

The flow

1. Request. Choose a role, give a reason of at least 10 characters. The elevatable roles are Operator, Tenant Admin, DBA, Data Steward, Auditor — platform_admin is deliberately excluded, because that path requires explicit grant management rather than break-glass. Requests raised from a mapping's detail page are scoped to that mapping; requests raised here are tenant-wide, and the dialog warns you so.

2. Approve. Only platform_admin and tenant_admin hold elevation.approve. The reviewer sets a duration in hours — default 4, maximum 72.

Separation of duties is enforced. The requester cannot approve their own request, even though tenant_admin holds both permissions. Attempting it returns a clear 403, and the rule is also embedded in the database update so it cannot be bypassed by calling the service directly.

3. In force. While approved and unexpired, the platform synthesises the granted role — tenant-scope, or mapping-scope if a mapping was named — and it can clear the approval-gated permissions listed above. Your session reflects an elevation within about 15 seconds.

4. End. Either it expires (evaluated at check time — the row keeps showing approved with a past date rather than flipping to expired), or someone revokes it. Revocation is limited to the original requester or a holder of elevation.approve.


Audit log

Navigate: Governance → Audit Log. Readable by platform admin, tenant admin, operator and auditor. Append-only — nothing on this page mutates it.

Every entry carries who, what, when, where, why and outcome: actor with IP and source, entity type/name/id, action, category, severity, a reason for privileged actions, a materiality line (N mismatched rows · N field diffs · N open issues) for run outcomes, an expandable before/after field diff, and correlation ids. Run-related entries deep-link straight to the run's evidence.

Placing and releasing a legal hold, every retention-purge execution (including a dry run and a paused no-op), and every period bundle anchored to a write-once store are all recorded here.

Acknowledging or resolving an anomaly is recorded too (anomaly.acknowledged / anomaly.resolved), with who, from which IP, the note or reason given, and the before and after of the acknowledgement and resolution fields.

Tamper evidence

Entries are sealed into a hash chain. The header strip shows the signer, the sealing mode, checkpoint count, last-sealed time, and how many entries are pending seal.

Verify integrity recomputes the chain. Success tells you how many sealed entries are intact; failure names the exact entry where the chain breaks, and distinguishes a broken chain from a signed-anchor mismatch — the latter catching a wholesale re-seal that a plain chain check would miss.

Security Posture

Administration > Security Posture (/administration/posture) lists every unsafe or unverified setting of the deployment - the same checks the platform runs at startup in production (#2134) - evaluated on any environment. Each finding shows a severity, the consequence, and the Helm value or environment variable that fixes it:

Severity Meaning
Fatal In production the platform refuses to start with it. Elsewhere it is reported, not enforced.
Warning Weakens tamper evidence or transport security; the platform keeps running.
Unverified The deployment did not declare the fact (for example a non-Helm install). Unverified is not the same as on.

A development deployment always has findings (no external anchor, undeclared network policy) and the page says so in a banner. An empty report reads "No findings across N checks" and lists the checks; a check that could not be measured in this environment (the development-signer check depends on a production-only helper) is listed as not evaluated, never as passed. The Audit Log header strip carries the same consequence text next to "Sealing: Manual" and next to the anchors indicator, so the strip never shows a bare label. Read-only; the settings themselves are deployment-managed.

Export All walks every page and exports the complete matching set with proper RFC-4180 escaping and an Excel-friendly byte-order mark.

Filters

Entity type, entity name, entity id, action, actor, category (access, config, reconciliation, evidence, ops), severity (info, notice, warning, critical), source (ui, api, system, scheduler, migration), actor IP for access attribution, and a date range that defaults to today.

Entity types and actions are loaded live from the server, so the filter list can never drift from what the platform actually records.