Summary
| Forestall ACL Alias | GH_MANAGE_ROLES |
| Edge Type | Attack Path |
| Affected Object Types | GitHub Organizations |
| Exploitation Certainty | Certain |
| GitHub Role / Permission | Custom organization role granting write_organization_custom_org_role (“Manage custom organization roles”) |
Description
GH_MANAGE_ROLES represents that a non-owner user or team holds a custom organization role that grants the “Manage custom organization roles” permission (write_organization_custom_org_role).
GitHub lets organization owners define custom organization roles — a named bundle of fine-grained permissions — and assign them to ordinary members or teams without making them owners. The “manage custom organization roles” permission is special: a principal who holds it can create and edit any organization role, including granting that role any permission and assigning it to any account.
That makes this a privilege-escalation attack path to owner-equivalent control:
- The holder edits a role (or creates a new one) and adds powerful permissions to it.
- The holder assigns that role to themselves or to an attacker-controlled account.
- The account now wields organization-administration capabilities despite appearing as a plain Member in the People list.
This is a shadow-admin condition. GH_OWNER only captures principals whose org role is literally admin/owner, so it does not see these escalation holders — yet they can reach the same level of control. Owners themselves are excluded from this edge: they already hold GH_OWNER.
Identification
GitHub Web Interface
- Open the organization → Settings → Organization roles.
- Inspect each custom role’s permissions. Any role that includes “Manage custom organization roles” is an escalation role.
- For each such role, open its assignees to see which members and teams hold it.
GitHub CLI
List organization roles and find those granting the escalation permission:
gh api "orgs/{org}/organization-roles" \ --jq '.roles[] | select(.permissions[] | contains("write_organization_custom_org_role")) | {id, name}'List the users and teams assigned to such a role:
gh api "orgs/{org}/organization-roles/{role_id}/users" --paginate --jq '.[].login'gh api "orgs/{org}/organization-roles/{role_id}/teams" --paginate --jq '.[].slug'Exploitation
Holding “manage custom organization roles” is the escalation — no other privilege is required:
-
Grant an existing role more power. Add high-impact permissions (manage org rulesets, manage org Actions secrets, manage runners, etc.) to a role the attacker already holds.
gh api --method PATCH "orgs/{org}/organization-roles/{role_id}" \-f 'permissions[]=manage_organization_ref_rules' \-f 'permissions[]=write_organization_actions_secrets' -
Self-assign a powerful role. Create or pick a role with a strong
base_role(e.g.adminacross all repositories) and assign it to an attacker-controlled account.gh api --method PUT "orgs/{org}/organization-roles/users/{attacker}/{role_id}" -
Operate as an effective owner. With org rulesets, Actions secrets, and all-repository admin in hand, the attacker can remove branch protections org-wide, read every secret, and push to any repository — then pivot into connected cloud accounts via OIDC.
Because the holder controls the permission system itself, escalation is Certain.
Mitigation
-
Treat “manage custom organization roles” as Tier-0. Grant it only to accounts you would trust as owners, and prefer not to delegate it through custom roles at all.
-
Audit custom organization roles and their assignees. Review which roles carry org-administration permissions and who holds them; remove unnecessary grants. Watch for roles whose permission set has grown over time.
-
Minimize custom roles generally. Every custom role is additional attack surface. Keep the set small, well-named, and least-privilege.
-
Require 2FA and strong credentials for holders. A shadow-admin account without 2FA is as dangerous as an owner without 2FA (see
FS6011). -
Alert on role changes. Monitor for new permissions being added to roles and for role assignments, especially self-assignment.
Detection
| Event | Description |
|---|---|
organization_role.create |
A custom organization role was created |
organization_role.update |
A role’s permissions were changed (watch for added org-admin permissions) |
organization_role.assign / organization_role.user_add |
A role was assigned to a user |
organization_role.team_add |
A role was assigned to a team |
Alert on any role gaining the “manage custom organization roles” permission, on permission additions to existing roles, and on role assignments performed outside an approved change process — particularly self-assignment by a non-owner.