You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
sec(auth): Standard Users and Read-Only Users are not system_managed, so #1737's guard does not protect them #168
Two seeded groups are notsystem_managed, so PUT /api/groups/{id} can corrupt their permissions even after PR LeanerCloud/cloud-commitments-cli#1737 lands its grant ceiling and system_managed guard.
group
id
system_managed
Administrators + 3 others
…000001–…000004
TRUE (000059)
Purchaser
…000007
TRUE (000064)
Standard Users
…000005
FALSE
Read-Only Users
…000006
FALSE
Traced through every migration touching the column: 000057 inserts both with an INSERT column list that omits system_managed, so they take DEFAULT FALSE. 000059's UPDATE … SET system_managed = TRUE covers only …000001–…000004. 000064 covers Purchaser. 000086 and 000088 modify those groups' permissions but never the column.
Two independent reasons, both read out of the committed code:
A narrowing write is invisible to the ceiling.checkGrantCeiling (internal/auth/group_ceiling.go:81) iterates the requested permissions only. It never diffs against existing to detect removals. sec(frontend): group edit drops unrepresentable permissions and widens their resource to * cloud-commitments-cli#1629's primary harm is the group-edit form silently dropping cancel-own / retry-own / approve-own on purchases — all three present on Standard Users (000057:30-32), none representable in the form's hardcoded action list. That write passes the ceiling untouched.
The decision this needs — a product call, not an engineering one
Should Standard Users and Read-Only Users be system_managed?
If they are meant to be fixed role mirrors, a one-line migration setting the column TRUE closes this entirely and is the smallest possible fix.
If operators are meant to customise them — plausible, since "Standard Users" reads like a default an organisation would tailor — then locking them is wrong, and the gap must instead be closed by making the ceiling detect removals as well as additions. That is a larger change with its own risk: an over-strict removal check would block legitimate permission tidying.
This was deliberately not decided inside LeanerCloud/cloud-commitments-cli#1737. Marking a seeded group immutable changes what operators can configure, which is not a call to make as a side effect of a security PR.
If the removal-detection route is chosen
Note it is a genuinely different guard from the grant ceiling. "You may not grant what you do not hold" and "you may not silently drop what you did not intend to" are separate properties, and a single check that tries to be both will likely get one of them wrong. Any implementation needs a negative control proving deliberate permission removal still works.
Two seeded groups are not
system_managed, soPUT /api/groups/{id}can corrupt their permissions even after PR LeanerCloud/cloud-commitments-cli#1737 lands its grant ceiling andsystem_managedguard.system_managed…000001–…000004000059)…000007000064)…000005…000006Traced through every migration touching the column:
000057inserts both with an INSERT column list that omitssystem_managed, so they takeDEFAULT FALSE.000059'sUPDATE … SET system_managed = TRUEcovers only…000001–…000004.000064covers Purchaser.000086and000088modify those groups' permissions but never the column.Why LeanerCloud/cloud-commitments-cli#1737's ceiling does not cover them
Two independent reasons, both read out of the committed code:
checkGrantCeiling(internal/auth/group_ceiling.go:81) iterates the requested permissions only. It never diffs againstexistingto detect removals. sec(frontend): group edit drops unrepresentable permissions and widens their resource to * cloud-commitments-cli#1629's primary harm is the group-edit form silently droppingcancel-own/retry-own/approve-ownonpurchases— all three present on Standard Users (000057:30-32), none representable in the form's hardcoded action list. That write passes the ceiling untouched.grantCeilingAllows(:104) checkscheckAdminPermission(held)first;view:*is not inadminCarvedOuts, so it returns true.view:historywidens toview:*.LeanerCloud/cloud-commitments-cli#1629 names this group explicitly: "The seeded user role-mirror group has the same shape."
What is and is not affected
system_managed. sec(auth): enforce a grant ceiling and system-managed guard on group writes cloud-commitments-cli#1737 closes sec(auth): the #923 money separation-of-duties carve-out is voidable by any admin in one request cloud-commitments-cli#1550 completely.system_managedgroups. Neither protects Standard Users or Read-Only Users from a direct API call.The decision this needs — a product call, not an engineering one
Should
Standard UsersandRead-Only Usersbesystem_managed?This was deliberately not decided inside LeanerCloud/cloud-commitments-cli#1737. Marking a seeded group immutable changes what operators can configure, which is not a call to make as a side effect of a security PR.
If the removal-detection route is chosen
Note it is a genuinely different guard from the grant ceiling. "You may not grant what you do not hold" and "you may not silently drop what you did not intend to" are separate properties, and a single check that tries to be both will likely get one of them wrong. Any implementation needs a negative control proving deliberate permission removal still works.
Found during the pre-push review of LeanerCloud/cloud-commitments-cli#1737.