The Seven Access Rules
By Sourav Banerjee · Last updated 5 Oct 2026 · Report an issue
8 min read
Roles are easy to explain but cannot express real entitlements, while attributes can express them at the cost of a policy that must be learned rule by rule.
Role-based access control cannot say "EU engineers on the vuln-response team may read restricted advisories, but only after the embargo lifts." Attribute-based access control (ABAC) says it in one rule, so learn the attributes, the seven rules in order, and the obligations that ride on an allow.
Objectives:
- list the resource and principal attributes and the rule each one feeds;
- walk the seven checks in order and explain why the first deny wins;
- say what an obligation is and why it is not a filter.
Meet Two Bundles of Attributes at One Engine
Every decision takes two inputs. The principal describes who is asking. The resource describes the chunk being read. A policy engine compares them. Nothing else takes part: no LLM is involved in any access decision, ever.
The engine runs seven rules in order. Deny overrides allow, and the default is deny. It returns a Decision(allowed, rule, reason). The same shape recurs in the guardrails and the delivery gates later in the course. A named rule decides, and the reason is never "because I said so".
Give Every Resource Field One Job
Each resource field answers one question and feeds one rule.
| Field | Example | The question it answers | Rule it feeds |
|---|---|---|---|
doc_id |
PM-2026-03-14 |
Identity and citation target | Citation verification |
tenant_id |
meridian |
Which customer owns this? | 1 · tenant isolation |
sensitivity |
confidential |
How damaging if it leaks? | 2 · clearance |
sensitivity_level |
2 |
Numeric form, so the index can do <= |
2 (pushdown) |
region |
EU |
Where may it be processed? | 3 · data residency |
valid_from |
2026-09-01 |
Is it published yet? | 4 · embargo |
valid_until |
null |
Has it expired? | 4 · expiry |
need_to_know |
[vuln-response] |
Which compartment? | 5 · need-to-know |
source |
contract |
What kind of document? | 6 · external restriction |
allowed_groups |
[engineering, support-tier3, sre] |
Who was granted it? | 7 · group membership |
contains_pii |
true |
Does it hold personal data? | Obligation: redact |
owner |
ingest-team |
Who to ask for access | Operational |
The sensitivity ladder is ordered: public (0) < internal (1) < confidential (2) < restricted (3). A confidential clearance reads everything at or below it.
The pipeline does not invent these fields. They translate each source system's own permission model: Confluence space permissions, Zendesk organisations, SharePoint groups. Getting that translation wrong is the number one cause of enterprise RAG leaks. Permission-Aware Ingestion shows why ingestion refuses documents it cannot map.
Resolve the Principal Fresh on Every Request
| Field | Example | Where it comes from |
|---|---|---|
user_id |
u_marco_t3 |
SSO or OIDC claim |
tenant_id |
meridian |
Workspace or org binding |
groups |
[support-tier3, engineering] |
Directory groups synced by SCIM |
clearance |
confidential |
HR or entitlement system |
region |
EU |
Employment location |
projects |
[vuln-response] |
Compartment assignment |
is_external |
false |
Contractor flag |
can_view_pii |
true |
Privacy entitlement |
The principal is never cached in a session and never read from anything the user controls. A cached principal cannot see a revocation that happened after it was cached. Resolving it per request makes live revocation work: remove someone from a group and the very next query enforces it, with no reindexing.
Walk the Seven Checks in Order
1 tenant_isolation ─┐
2 clearance │ DENY rules — any hit stops here
3 data_residency │
4 embargo / expiry │
5 need_to_know │
6 external ─┘
↓ (nothing denied)
7 group_membership ── the ONLY rule that can grant
↓ (no grant matched)
default_deny
The first deny wins and short-circuits. A document may break several rules, but only the first one shows. That is how a rule can sit permanently untested.
- Tenant isolation compares the two
tenant_idvalues. A different tenant is denied unconditionally. The negative-control persona,u_attacker_other_tenant, holds every group and the highest clearance in tenantacme. It reads 0 of 22 documents. No accumulation of privilege crosses a tenant boundary. - Clearance compares sensitivity levels.
u_lena_t1hasinternalclearance, so aconfidentialcontract is denied. Clearance is necessary but not sufficient, as rule 7 shows. - Data residency compares regions.
GLOBALresources are readable anywhere.u_marco_t3andu_jin_us_t3share role, clearance and groups, and differ only byEUagainstUS. Marco reads the EU post-mortem and ticket; Jin reads the US ticket. Both read theGLOBALrunbook. The rule comes from the Vertex contract, which bars EU telemetry from being accessed outside the EU: a contract clause became a policy rule. On the principal sideGLOBALis a value, not a wildcard, sou_ravi_secis denied the EU post-mortem. - Embargo and expiry compare
valid_fromandvalid_untilwith today. Ravi has the right clearance and compartment for advisorySA-2026-07, but it publishes on 2026-09-01 and today is 2026-08-22. Only the clock stops him. On 2026-09-01 the decision flips to allow with no data change. - Need-to-know checks that the document's compartments are among the principal's
projects. Ravi, assigned tovuln-response, readsSA-2026-05.u_erin_secmgrhas the samerestrictedclearance but no compartment and is denied. Clearance is a ladder; compartments are orthogonal to it. - External restriction stops external principals reading
contract,pricingorpostmortemsources, whatever their groups or clearance. A contractor's NDA does not care which group someone put them in. - Group membership is the only rule that grants. A non-empty intersection of
groupsandallowed_groupsallows. The pseudo-grouppublicmakes a document readable by everyone in the tenant. If no grant matches, the result isdefault_deny. That accounts for 41 denials in the corpus, the largest single category.
Warning: a shadowed rule looks tested but is not. For the original contractor persona, clearance always denied before the external rule could run, so rule 6 was never exercised. A coverage test found it. The fix was a new persona,
u_dana_ext, withconfidentialclearance and thesalesgroup, whereis_externalis the only thing between her and the contracts. Add a persona for every rule that can only fire when earlier rules pass.
Attach Obligations to an Allow
An allow is not always unconditional. This is the part of ABAC people forget exists.
| Obligation | Fires when | Effect |
|---|---|---|
redact_pii |
contains_pii and not can_view_pii |
Direct identifiers masked before the text reaches the model |
audit_access |
Sensitivity is confidential or restricted |
Access recorded with user, document and rule |
A filter answers yes or no; an obligation answers yes, and transform. A vector store cannot return "this chunk with emails masked", so obligations run after retrieval.
A real failure is worth knowing. On Databricks the mask is a SQL regexp_replace. A \w class was eaten by the Python-to-Spark-SQL escaping chain, so the mask looked attached and redacted nothing. Use explicit character ranges and always assert on real data. This bug shipped and was caught only by reading the output.
Worked Example: One Ticket, Two Readers
u_tom_contractor asks about ticket TK-4488, a US ticket that contains personal data. He passes every deny rule and his group grants access, so the decision is allow with obligations=['redact_pii'].
"Reporter dan.okafor@northgateretail.example reports timeouts."
becomes
"Reporter [REDACTED_EMAIL] reports timeouts."
u_jin_us_t3 reads the same ticket intact, because he has can_view_pii=true. u_lena_t1 holds the right group but is in the EU, so data residency denies her before any obligation matters.
Same document, same query, three outcomes. Each came from a named rule or obligation, and none came from the model.
Key Takeaways
- An ABAC decision compares principal and resource attributes in a policy engine, with deny overriding allow and a default deny; no LLM takes part.
- Every resource field feeds one rule, and the fields translate each source system's permission model.
- The principal is resolved fresh on every request, so a revocation takes effect on the next query.
- Seven rules run in order; the first deny wins, only group membership grants, and a shadowed rule needs its own persona to be tested.
- Obligations attach transformations such as PII redaction and audit to an allow, and they cannot run inside the index.
Check Yourself
Why does the attacker persona read 0 of 22 documents despite holding every group and top clearance? Tenant isolation is checked first and cannot be overridden. A principal in another tenant is denied before any other rule runs.
Why did the project add
u_dana_ext? Without a high-clearance contractor, clearance always denied first, so the external-restriction rule never fired and was untested.What is the difference between clearance, groups and compartments? Clearance is a ladder that says how sensitive a document may be. Groups are the grant that says which documents. Compartments are orthogonal assignments that top clearance cannot bypass. A reader needs all three.