mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-12 06:48:55 +00:00
8372f42525
Reported by @skeletonsec as GHSA-w29w-pj29-x7ww. Deleting an account that owns files makes the admin choose who inherits them. The picker narrows that list for a client-scoped staff member to their own roster, and says why two methods up: "a client-scoped staff member is not shown the name of somebody they can reach nothing of, and a picker is no more a reason to hand one over than a listing is." The write asked something else entirely — exists, active, and not the account being deleted. All three are true of every account on the installation. So a scoped staffer could name an id the picker had deliberately kept off the list, and a roster client's files and folders landed with a client on somebody else's roster: readable, editable and deletable there, because a client owns what they uploaded and visibleToClient() includes uploaded_by. The entry doors scope the source account and always did — guardTarget goes through canAssignClient. It is the destination nobody scoped. candidates() and validate() now run one predicate, reachableTargets(), rather than two that happened to agree. Two that agree by inspection is what this was: the narrowing existed, was correct, and was only ever applied to the list. The refusal deliberately reads as "no such account". An out-of-roster id and an id belonging to nobody now produce the same message, because a refusal that distinguishes them lets a scoped staffer walk the id space and learn which accounts exist outside their roster. That is why Rule::exists is gone rather than kept alongside: one code path, one answer. A test pins the two messages as identical instead of naming either. Both the web screen and the API twin come through this one validate(), so both are fixed by it — and the test file proves each separately rather than assuming the sharing holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CNFU55Tkq6MuEQ73nbbBRx