The actionSavePermissions() endpoint allows a user with only viewUsers permission to remove arbitrary users from all user groups. While _saveUserGroups() enforces per-group authorization for additions, it performs no equivalent authorization check for removals, so submitting an empty groups value removes all existing group memberships.
viewUsers permission was addededitedUser() required editUsers, which implicitly protected this endpointCmsEdition::Pro)This is a regression introduced in Craft CMS 5.6.0 when the viewUsers permission was added. Before that change, editedUser() required editUsers permission for accessing other users’ data, which implicitly protected actionSavePermissions(). After the change, actionSavePermissions() became reachable for users with read-only access to other users, but the underlying group-saving logic still lacked authorization for group removals.
The vulnerability has two components:
actionSavePermissions() reachable with read-only access: The action only requires a control panel request and delegates to editedUser(), which now only checks viewUsers — a permission explicitly documented as "read-only access to user elements."
Asymmetric authorization in _saveUserGroups(): The method checks assignUserGroup permission only when adding a user to a new group. When the groups parameter is an empty string (resulting in an empty array), the loop is skipped entirely, no authorization checks are run, and all group memberships are removed.
accessCp and viewUsers permissions onlyactions/users/save-permissions with:
userId = target user's IDgroups = `` (empty string)requireElevatedSession() (which is only triggered when new groups are added){
"cwe_ids": [
"CWE-862"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-14T23:34:52Z",
"nvd_published_at": "2026-04-22T00:16:28Z",
"severity": "MODERATE"
}