Repository navigation
Description
Description
ClientPermissions.isInternal denylists only the realm-management client (and, in the master realm, client IDs ending in the -realm suffix). The admin-permissions client — the resource server holding the realm's entire FGAP V2 model — is not in that list.
As a result, a delegated administrator holding a FGAP Clients:manage permission (and no realm-management roles) can update and disable the admin-permissions client through the Admin API. The API's own access report (getAccess) correctly returns manage: false for this client, but the enforcement path in canManage does not agree — it falls through to the FGAP permission check and allows the write.
This is a gap in the isInternal denylist introduced in 26.7.0. No privilege escalation results from this — FGAP enforcement resolves the client by internal ID, not by clientId, and disabling the client does not relax the delegated admin's own restrictions.
Version affected
26.7.0 and main. The isInternal guard was introduced in 26.7.0; 26.6.x has a different code path and is not affected.
Expected behavior
The admin-permissions client should be treated as internal, consistent with realm-management. A delegated admin with Clients:manage should not be able to update or disable it. The enforcement path and the access report should agree.
Actual behavior
isInternalreturnsfalsefor theadmin-permissionsclientcanManagefalls through to the FGAP permission check, which grants access viaClients:managegetAccessreturnsmanage: false(it special-cases this client), contradicting enforcement- The delegated admin can set
serviceAccountsEnabled, add redirect URIs, enabledirectAccessGrantsEnabled, and renameclientId - Deleting the client is correctly refused (HTTP 400)
Steps to reproduce
- Start Keycloak 26.7.0 with default features (FGAP V2 enabled by default).
- In a realm, enable
adminPermissionsEnabled(FGAP V2 configuration). - Create a delegated admin user with a FGAP
Clients:managepermission but no realm-management roles. - As the delegated admin,
GET /admin/realms/{realm}/clients/{admin-permissions-client-id}— noteaccess.manage = false. - As the delegated admin,
PUT /admin/realms/{realm}/clients/{admin-permissions-client-id}with a modified body (e.g., add a redirect URI) — observe HTTP 204 success. - As the master admin, verify the change persisted.
Root cause
ClientPermissions.isInternal (line 719) checks only for realm-management and -realm suffix clients. Constants.ADMIN_PERMISSIONS_CLIENT_ID is missing from the predicate. The getAccess method at line 676 correctly special-cases this client, but canManage at line 63 does not mirror the same check.
Suggested fix
Add Constants.ADMIN_PERMISSIONS_CLIENT_ID to ClientPermissions.isInternal. This is the smallest change and matches how realm-management is already handled.
Acknowledgement
Mahdi Alhakim (GitHub: mahdi-al-hakim)
This issue was originally tracked in the private repository. Migrated by @abstractj.
Metadata
Metadata
Assignees
Labels
Type
Fields
Projects
- StatusShow more project fieldsDone
Activity
fix: add admin-permissions client to isInternal denylist
4 remaining items
Treat the admin-permissions client as an internal client
Treat the admin-permissions client as an internal client
Treat the admin-permissions client as an internal client
Treat the admin-permissions client as an internal client
Treat the admin-permissions client as an internal client
Treat the admin-permissions client as an internal client
Treat the admin-permissions client as an internal client