# When does inherited role scoping widen privilege instead of narrowing it? **Date of Record:** 2026 November 1 **Scope:** Applies to role models where a parent scope (organization, project, tenant) defines a boundary and child roles define permissions within it. Excludes flat permission lists with no inheritance. ## Core Distinction **Predicate.** Inheritance narrows privilege if the effective permission set is the intersection of the child's grants with the parent's boundary. Inheritance widens privilege if the effective set is the union. A model that documents intersection but implements union grants access outside the parent boundary whenever a child role carries a permission the parent does not. **Failure.** A user is granted a child role at a project. The child role includes a permission the organization role does not. Under union semantics, the effective permission includes the child grant scoped to the organization rather than the project, so a project-scoped grant becomes organization-wide. **Asymmetry.** The grant is visible and intentional at the child level. The widening is a consequence of the combination rule, which is documented in one place and implemented in another, and only the combination rule is wrong. ## What Was Observed **Signature.** A user able to act outside the scope their visible grant should allow. The grant list looks correct at every level. The effective permission set, when computed, contains entries no single grant would produce. **Trace.** Organization role: read. Project role: read plus write. Union semantics produce read plus write at organization scope, so the user can write to every project in the organization. Intersection semantics produce read at organization and read plus write at the project. The visible grant is identical in both cases. **Why monitoring stays green.** Every grant is authorized and recorded. Audit logs show the user acting with a permission they were granted. The scope violation is not a permission the user lacked, so no permission check fails. ## Condition of Validity Narrowing inheritance holds only while at least one holds: 1. **Intersection.** Effective permissions are computed as the intersection of child grants with the parent boundary, not the union. 2. **No widening grants.** Child roles are constrained to permissions the parent already holds, so union and intersection coincide. 3. **Explicit scope.** Every permission carries its own explicit scope, and inheritance does not re-scope grants that already carry one. Condition 2 is a policy invariant that erodes as roles are added; 1 is the structural fix. ## Decision Boundary - Intersection semantics: inheritance narrows, safe. - Union with child grants constrained to the parent's set: safe today, breaks when a new child permission is added. - Union with unconstrained child grants: privilege widens at every scope boundary. - Explicit per-permission scope: safe regardless of combination rule, at the cost of managing scope per grant. ## Operational Effect The defect lives in the combination rule, which is written once and read by no one, while roles are added continuously by people who assume narrowing. Remediation is to make the combination rule structural rather than conventional. The trap is to audit roles instead of the rule. The audit finds no bad grant because every grant is fine, and the widening persists. ```python from dataclasses import dataclass class PrivilegeWideningError(PermissionError): """Raised when role inheritance combines by union and can exceed the parent scope.""" @dataclass(frozen=True) class RoleLatticeGate: combination: str # "intersection", "union" child_grants_within_parent: bool per_permission_scope: bool = False def verify(self) -> bool: if self.combination == "intersection" or self.per_permission_scope: return True if self.combination == "union" and not self.child_grants_within_parent: raise PrivilegeWideningError( "union semantics with child grants outside the parent set widen privilege" ) if self.combination not in ("intersection", "union"): raise PrivilegeWideningError(f"unknown combination {self.combination!r}") return True def dispatch_effective_permissions(gate: RoleLatticeGate) -> str: try: gate.verify() except PrivilegeWideningError: return "reject-until-scoped" return "combine" ```