Repository navigation
security: verify zero-group user cannot retain admin access (QA row 511 / step 15.1) #948
Description
Source: QA sheet row 511 / step 15.1.
Action (repro as reported by QA)
Create an Admin user that is not added to any Group (zero-group user), then log in as that user.
Expected (acceptance)
A user must not be able to hold admin access without an explicit group grant. Either:
- user-create rejects a zero-group user, or
- the user is auto-assigned to the Administrators group,
and admin capability is derived solely from group membership (Administrators group 00000000-0000-5000-8000-000000000001). A user with no group can do nothing.
Actual (observed at QA time)
The user stayed unassigned to any group and still had admin access when logged in. Admin was not strictly group-derived. SECURITY gap (privilege without an explicit grant).
Verification finding (current base: feat/multicloud-web-frontend)
Already fixed by #907/#912 ("group-membership-only authorization, remove roles, require >=1 group"), reinforced by #924/#923 (Purchaser group) and #941 (dropped last session.Role shortcut). Both failure modes are now closed on the current production base:
- Zero-group creation is rejected.
Service.validateCreateUserRequest(internal/auth/service_user.go:134) returnsErrNoGroups-> 400 whenlen(req.GroupIDs) == 0.UpdateUser/guardGroupChange(:328) enforces the same on edit. You cannot create or leave a user with zero groups via the API. - Admin is strictly group-derived.
GetUserPermissions(internal/auth/service_group.go:67) sources permissions "exclusively from group memberships"; a zero-group user gets an empty permission set.UserHasAdminCapability/HasPermissiononly grant admin on the{admin, *}permission, which comes only from the Administrators group. The admin middleware gate (internal/api/middleware.go:266) callsHasPermissionAPI(admin, *)and returns 403 otherwise (fail-closed). No live role-based admin path remains:RoleAdmin(internal/auth/types.go:303) is referenced only in its own definition and doc comments; the lastsession.Roleshortcut was removed in fix(api/purchases): drop removed session.Role shortcut in execute-direct gate (closes #940) #941.
Regression coverage already present:
internal/auth/service_group_only_authz_test.go—TestGroupOnlyAuthz_ZeroGroupFailClosedasserts a zero-group user is denied everything andUserHasAdminCapabilityis false; plus tests assertingErrNoGroupson create/update.internal/auth/service_user_test.go:187,508— assertErrNoGroupson zero-group create.
Conclusion
Already-fixed-by-#907/#912. No code change or PR required. Filing for traceability and to mark the QA row resolved. Recommend re-testing on the current production build to close the sheet row.
Activity
cristim commentedon Jul 27, 2026
Verification sweep against
main(101f099fb) on 2026-07-27 finds this already resolved.Authorization is now group-derived only, with ErrNoGroups on an empty group set and no live session.Role usage remaining.
Evidence: #907/#912.
Recommending close.
cristim commentedon Sep 2, 2026
Verified resolved at 3c0f8ac: user create and update reject an empty group set with ErrNoGroups, permissions come exclusively from group memberships, and no role-based admin shortcut remains, so a zero-group user cannot exist via the API and would hold no capability if one did. Evidence: internal/auth/service_user.go:136 and :371, internal/auth/service_group.go:87, internal/auth/service_group_only_authz_test.go:97 (#912). Residual axes checked: update path (guardGroupChange), session.Role reads in internal/ (none outside tests), RoleAdmin constant (definition only). Closing as completed; reopen if the behaviour recurs.