v26.3.13
v26.3.13
Permission checks answer correctly when branches meet or loop
A permission check could return the wrong answer — most often a denial or a max_depth_exceeded failure for a subject who should
have been allowed, and on permissions that use !, a grant to a subject who should have been denied. Two cases caused it:
- Branches that meet. When two paths through your data led to the same group or role, the first path to reach it claimed it for the whole check. If that path then ran out of depth, the second path was dropped even though it would have found the subject. Nested groups reachable more than one way are enough to hit this.
- Loops in your data. A relationship that leads back to where it started — a folder chain that eventually points at an ancestor, or two groups that contain each other — was followed until the depth limit stopped it, instead of being recognized as adding nothing.
Both now resolve correctly. A loop in your data is answered decisively rather than reported as too deep, and one branch can no longer suppress another.
If you worked around this by raising limit.max_read_depth, you can lower it again. Checks over data whose branches converge are
also considerably faster, because each group or role is now resolved once per check rather than once per path that reaches it.
Behavior changes
Checks that previously answered "allowed": false — or 403 on the endpoints that signal a denial that way — or failed with
422 for the cases above now return the correct answer, which may be an allow. If you built on one of those denials, review it.
Checks also change the other way, which is the case to review first. A permission written as "allowed unless denied" — an allow
list combined with ! over a deny list — could be granted to a subject the deny list covers, whenever another part of the same
check had already walked the group that leads to that deny list. The deny branch was dropped as already seen, that reported "not
denied", and ! turned it into a grant. Such a check now denies. Review any permission that negates a deny list reached through a
group or a subject-set pointer: subjects who were allowed may no longer be, and some of them should never have been.
A permission defined as the negation of itself, directly or through other permissions — for example edit defined as "not view"
while view is defined as "not edit" — has no answer, and never had one. Such a check now reports negation_cycle instead of
failing after an internal step limit, and only that part of the check is affected: if the permission can also be granted another
way, the check now answers from that instead of failing outright. This signals a definition to fix in your permission model.