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.
operatorinherits 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:
- A tenant-scope grant covers everything in the tenant.
- An exact scope match covers that resource.
- Business domains inherit: a grant on a parent domain covers every descendant domain.
- 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 aplatform_admincan grant (or revoke)platform_admin; atenant_admincan grant every other role. The same ceiling applies to revoking another principal's grant, so atenant_admincannot remove aplatform_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_adminneeds 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 holdingapproval.decideapproves 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: atenant_adminheld 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_adminholds 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.