RapidValueDocs RapidValue.eu
Docs/The governance model

Separation of duties and risk

Two questions sit next to each other in every governance review: which combinations of access must never land on one person, and who or what is risky right now. This page is how both are expressed — and, more usefully, what each one refuses to conclude.

One SoDRule model is the source for the workbench, conflict evaluation and enforcement. Legacy policy pre-flight is not a second decision path. Visibility and write actions remain role- and ownership-scoped; a workbench route does not grant tenant-wide access by itself.

A rule is a set of filters, not a table of pairs

A separation-of-duties rule declares two or more conditions, each a filter over entitlement attributes. An entitlement belongs to a side if it matches that side's filter. An identity violates the rule when it holds at least one active grant on each side.

Those filters use the same filter DSL that drives workflow triggers, dynamic group membership, audience conditions and scoping — a deliberate choice over the obvious alternative, a table of forbidden entitlement pairs. It earns its keep twice: administrators learn one syntax, and a rule describes a shape rather than an inventory. A pair table has to be extended by hand whenever a new entitlement is created that belongs on one side of an existing conflict, which nobody does, so it quietly stops describing the estate.

Conflicts span systems by construction. Sides resolve to entitlements, grants join back through accounts to the identity, and nothing in that path cares which connector a side lives on. A vendor-master permission in the ERP and a payment approval in a separate finance application is one violation on one person.

What one rule catches, and what it does not

Five rules ship seeded and enabled. Take the fraud one — Vendor master + payment approval, severity critical, referencing SOX 404 and COSO ICFR:

  • Side A: entitlement name contains any of vendor, supplier, creditor, leverancier
  • Side B: entitlement name contains any of payment, payable, approve, betaling, or the word AP

An identity holding SAP Vendor Master Maintenance and AP Payment Approval is flagged, whichever systems those live on. The finding names both grants, their systems and how each was obtained — a reviewer deciding which side to remove needs to know that one arrived through a role and the other was requested by hand.

Now the other half, because it decides whether you trust the number on the dashboard:

  • It matches names, so it inherits whatever your source systems call things. An entitlement named AP-CRED-MAINT-01 contains no side-A keyword. It is a vendor-master permission and this rule will never see it. Seeded rules are a starting position, not an inventory; a rule that matters should be re-stated against attributes you control — a classification, a risk level, an owning application.
  • contains is a case-insensitive substring match, so a short keyword in a rule you write matches inside longer words: AP would also match SAP Basis Admin and Graph API Reader. The seeded rules avoid this for their short abbreviations (AP, HR, PII, prod) by matching them as whole words, so App-Timevault-Users is not a payment-approval right. Rules you write yourself get no such protection. Hence the dry-run: before you save, the editor reports how many entitlements landed on each side and a sample of who would be flagged.
  • Only in-target grants count, so access that is expected but not yet provisioned is not a violation for the detector. The enforcement gate sees it earlier.
  • Only active identities, and by default no machines — suspended and terminated identities are pending revocation anyway, and batch accounts legitimately hold combinations that would be toxic for a person.
  • Nothing in an unconnected system is considered. There is no inference step.

A rule can also be scoped by an identity-side filter, so a conflict that only matters for Finance is only evaluated for Finance.

Two guards that stop a rule being useless

Both sides must be non-empty. A rule is rejected at save if fewer than two conditions carry content. An empty filter matches every entitlement, so one empty side flags every identity holding two grants — a confident, comprehensive, worthless finding, and one that reads as a catastrophic compliance failure rather than as a typo.

An entitlement matching more than one side is dropped from all of them. A permission matching both the vendor and the payment pattern would otherwise occupy both sides at once, and a single grant on it would flag its holder for a conflict of one permission with itself. Such a permission is ambiguous, not two things. The same de-duplication runs in the enforcement gate — a detector and a gate disagreeing about who is in violation is worse than either being wrong alone.

Four actions, and what each does to access

Every rule carries one enforcement action. Four exist, all four are implemented, and the seeded rules ship on the mildest.

  • flag_only — detect and surface. One finding per rule and identity in the advisor inbox, contributing to that identity's risk score. No access changes.
  • require_approval — the access is allowed, but the request gets one extra security step in its approval chain. It changes who must agree, not what access exists, so reconciliation never revokes access because of it.
  • block_overrule_allowed — the conflicting access is blocked, and a time-boxed exception can restore it. The exception is not a checkbox: it is a risk acceptance that runs an approval chain, carries a justification, and expires at a date capped by policy. While one is active the entry reads overruled rather than blocked. The security team sets that cap (the maximum and default duration of a separation-of-duties exception); who approves stays in the exception policy. A finding kind without its own policy is approved in one step by the security team, compliance or an administrator. Under Governance settings an administrator ticks, per finding kind, which groups decide its exception requests (security team, compliance, IGA administrator, business administrator; one is enough), and "Use the standard again" returns a kind to the standard chain. Open requests keep the chain they were sent with. A compliance step reaches the compliance officer.

Besides the inbox finding, My requests shows a person who may read separation-of-duties findings a card with the open conflicts in their own access, each with a button to request an exception; while a request waits for a decision the card says so instead of offering a second one. - block_no_exceptions — blocked with no overrule path. The only remedy is removing one side.

Enforcement happens at two points, answering different questions.

At request time, submitting an access request runs a pre-flight that expands both the requested entitlements and the identity's current holdings through their role and group hierarchies — a conflict hidden two levels inside a requested role is still a conflict. Anything that would create a violation under a block_no_exceptions rule is refused at submission with the rule named, since approving a request whose grant would be killed on arrival with no exception path wastes an approver's time. Conflicts under the other actions ride along as warnings, so the approver decides with the conflict in front of them. The approver's decision screen names each conflicting rule and its reason, and the requester's review screen shows the same conflicts before submitting, so what the requester sees is what submit and the approver act on.

When a role is built, creating a role or adding a child to one checks the role's own children against the enabled rules. A role whose children together cover every side of a rule under block_overrule_allowed or block_no_exceptions is refused, naming the rule; under the other two actions it is allowed with a warning.

In reconciliation, access already held is handled where all access is handled. When an identity's expected access is recomputed, an enforcing rule annotates the conflicting entries — they stay in the expected set, marked blocked or overruled, because deleting them would erase the explanation. The apply path acts on the annotation: a blocked grant present in the target is revoked, an overruled grant that is absent is restored. The revocation is reversible, since the grant row survives for an approved overrule to put back. An overrule expires on its end date: a scheduler sweep (every 15 minutes) marks lapsed exceptions expired, and every enforcement point checks the date as well as the status, so the conflict is blocked again and re-surfaces as a finding.

Which side is blocked is decided rather than guessed, and an entire side goes so the conflict is genuinely broken. One side established and the other new: the new side goes, because the arriving access is the creep. Both established: the side with the most recent grant. All new: all of it. With three or more sides, the newest.

No automated resolution

The advisor finding for a violation has no one-click fix. Its apply action returns a refusal and an explanation, deliberately. Which side to remove is a business judgement: revoking the vendor-maintenance half and revoking the payment-approval half both satisfy the rule and have entirely different consequences for the person's job, and nothing in the data says which is right. The finding names the identity, the rule, both sides and how each grant was obtained; a person chooses, and the advisory closes itself once the conflict is gone. Revoking one side from the inbox schedules a re-scan of that person's finding, so a resolved conflict leaves the inbox on the next scheduler tick rather than after the detector's regular six-hour cycle. The asymmetry with the blocks above is intentional — a block is a policy decision taken in advance, with a published tie-break, whereas auto-resolution would be the platform making that call after the fact on its own.

That boundary also applies to require_approval: the platform can require an explicit treatment and time-boxed risk acceptance, but it does not infer which side is least important to the person's job.

Held people. A person whose access is held (locked, or not yet activated) is still scanned. Their conflict is labelled "Held", is informational, and is left out of the open and critical SoD counts, the risk score and the dashboard tiles. It counts as live risk again once the hold is lifted.

Risk scores are weighted sums you can recompute

The identity risk score is a single number from 0 to 100: a sum of components, each a count multiplied by a per-unit weight, capped at a per-component maximum, with the total clamped to 100.

That is the whole model. No learned model, no behavioural baseline, no opaque scoring service — and that is the design position rather than a stage on the way to one. A score that drives revocation and prioritisation has to be defensible to the person it describes and to the auditor asking why. Every score stores its own breakdown, so "why is this 69?" is answered with arithmetic:

Component Count Per unit Cap Contribution
Critical SoD violation 1 20 60 20
Dormant privileged grant 4 10 25 25 (40 capped)
Dormant grant 3 3 15 9
Open critical advisor finding 1 15 30 15
Total 69

Sixty-nine falls in the high band (0–25 low, 26–50 medium, 51–75 high, 76–100 critical). Crossing into critical emits an event, so it can drive a workflow rather than waiting to be noticed on a dashboard.

How recently an identity was reconciled is shown in its breakdown as a data-quality signal, but it carries weight zero by default: how fresh our own data is says nothing about how risky the person is.

Four properties matter more than the numbers:

  • Caps stop one signal dominating. The fourth dormant privileged grant adds nothing, so a score cannot be run up by one noisy detector.
  • Weights are per-tenant and merge per key, so changing one component leaves the other defaults intact and a partial override cannot silently zero the model.
  • A component set to zero is disabled, not deleted — it keeps its row and its count, contributing nothing. A tenant governing machine ownership elsewhere can stop that signal moving the score without losing sight of the count.
  • Overlapping signals are de-duplicated. Privileged access with no recent certification decision is a finding — unless it already counts as dormant.

Turning the score off stops the sweep and leaves the last computed values in place. Nothing is silently reset behind a toggle.

Direct access is not the whole picture

"Dormant privileged grant" and every other risk component above reads EntitlementGrant — the access a policy or an approver actually assigned. That misses a real category of access: what a nested group hands someone without anyone assigning it to them directly.

Worked example. An identity is a direct member of APAC-Sales-Reps, a routine regional group with no special permissions of its own. APAC-Sales-Reps is itself a member of Global-Deal-Approvers, which carries a Salesforce permission set: approve deals over $500k. Nobody granted this identity deal approval — they were added to a sales-region group, and a group nesting change six months later quietly handed every member of that group approval authority over half-million-dollar deals.

The platform materialises this as an effective permission, one row per (direct grant, entitlement reached at that depth):

Field Value
Direct grant APAC-Sales-Reps membership
Effective entitlement Global-Deal-Approvers — approve deals > $500k
hop_count 2
source_path APAC-Sales-Reps → Global-Deal-Approvers

"Shadow access" is not a field on this row — it is a derived classification the risk service applies to any row with hop_count >= 2; the row above qualifies by that rule.

Hop_count 1 — the direct grant itself — is materialised too, so a read never has to join direct and transitive access separately; "effective access" is one table. Deeper nesting (hop_count 3, 4…) is walked the same way, up to a 12-level safety cap, with cycle protection per direct grant — Entra and Active Directory both allow nested-group cycles through cross-tenant invites, and an unguarded walk would loop forever on one.

Shadow access feeds the risk score above at 1 point per shadow grant, capped at 20: enough to surface as a finding, low enough that one incidental nested group does not read as urgent. Twenty or more simultaneous shadow grants is a different signal — group-soup that has grown past anyone's mental model of who can do what, and worth resolving on its own rather than waiting for it to tip an identity's score into a higher band.

Two scores, two questions

The identity score answers who is risky. A separate score on each entitlement answers what is risky: privileged access spread across many holders, the share of holders who are dormant, membership of a tracked toxic combination, holders who are themselves high-risk, and exposure through a business-critical application. Same arithmetic, different subject. Neither replaces the administrator-set inherent risk level of an entitlement — that is a judgement about what the permission is, while the resource score measures what has happened around it. An entitlement classified low-risk that has accumulated three hundred holders, most of them dormant, is exactly the case worth seeing.

One eligibility rule, or the number stops meaning anything

The invariant most worth explaining to a reviewer is not about scoring at all: the finding and the score must share their eligibility predicate.

Dormancy proves it. A grant with no last-used timestamp is only dormant if the platform can actually observe activity on that system and has looked at least once; otherwise the empty value means "not tracked", and reading it as dormancy is fabrication. That gate is described in full under non-human identities and ownership.

The point is what happens when two consumers of one signal drift. The advisor's dormancy detector and the risk score's dormancy components once held byte-identical copies of the rule, kept in step by a comment asking the next person to remember. Once they diverge, an administrator sees a score of 69 with dormant grants itemised in its breakdown, opens the inbox it points at, finds nothing — and cannot tell which surface is lying. Both now compute from one shared implementation.

So: change an eligibility predicate — a threshold, a capability gate, a scope — and change it in every path that reads it. A score the inbox cannot account for is worse than no score.

Known edges, published rather than discovered

  • Scores are periodic, not live — identities every thirty minutes, entitlements hourly, with on-demand recompute for administrators and auditors. A score carries the time it was computed, so a stale score says so rather than being quietly trusted.
  • The score measures findings, not behaviour. Every component counts something observed about configuration and governance state — open findings, unreviewed privilege, unused access, conflicts. None of it observes what a person did. A quiet insider with tidy access scores low, correctly; nothing here is a detection capability.

Further reading:

Did this answer your question?One click records the page; add detail by email if something is missing.

Try “tenant isolation”, “role mining” or “Entra”.