find_superseded_score_set_tail in lib/score_sets.py walks forward to the end of the supersession chain, and when the next score set in the chain fails the permission check it returns the current score set directly:
if action is not None and not has_permission(user_data, next_score_set_in_chain, action).permitted:
return score_set
The score set it returns is never itself checked against action. The check below the loop — the one that walks back through superseded_score_set looking for something the caller may see — is skipped entirely on that path.
Not reachable on current data, since a score set must be published before it can be superseded, so any score set with a superseding_score_set is public and passes READ. It becomes reachable the moment supersession is permitted on a private score set, and the failure mode is a silently widened read rather than an error.
Proposed behavior
break instead of return score_set, so the path falls through to the permission check below the loop and the backward walk applies.
Acceptance criteria
- a caller with no permission on any score set in the chain receives
None
- a caller permitted on an earlier link receives that link, not an unchecked one
- a test covers a private score set carrying a
superseding_score_set the caller cannot read
find_superseded_score_set_tailinlib/score_sets.pywalks forward to the end of the supersession chain, and when the next score set in the chain fails the permission check it returns the current score set directly:The score set it returns is never itself checked against
action. The check below the loop — the one that walks back throughsuperseded_score_setlooking for something the caller may see — is skipped entirely on that path.Not reachable on current data, since a score set must be published before it can be superseded, so any score set with a
superseding_score_setis public and passes READ. It becomes reachable the moment supersession is permitted on a private score set, and the failure mode is a silently widened read rather than an error.Proposed behavior
breakinstead ofreturn score_set, so the path falls through to the permission check below the loop and the backward walk applies.Acceptance criteria
Nonesuperseding_score_setthe caller cannot read