> For the complete documentation index, see [llms.txt](https://docs.forestall.io/forestall/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forestall.io/forestall/edges/github/gh_manage_roles.md).

# GH\_MANAGE\_ROLES

## 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**:

1. The holder edits a role (or creates a new one) and adds powerful permissions to it.
2. The holder assigns that role to themselves or to an attacker-controlled account.
3. 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

1. Open the organization → **Settings → Organization roles**.
2. Inspect each custom role's permissions. Any role that includes **"Manage custom organization roles"** is an escalation role.
3. 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:

```bash
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:

```bash
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:

1. **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.

   ```bash
   gh api --method PATCH "orgs/{org}/organization-roles/{role_id}" \
     -f 'permissions[]=manage_organization_ref_rules' \
     -f 'permissions[]=write_organization_actions_secrets'
   ```
2. **Self-assign a powerful role.** Create or pick a role with a strong `base_role` (e.g. `admin` across all repositories) and assign it to an attacker-controlled account.

   ```bash
   gh api --method PUT "orgs/{org}/organization-roles/users/{attacker}/{role_id}"
   ```
3. **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

1. **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.
2. **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.
3. **Minimize custom roles generally.** Every custom role is additional attack surface. Keep the set small, well-named, and least-privilege.
4. **Require 2FA and strong credentials for holders.** A shadow-admin account without 2FA is as dangerous as an owner without 2FA (see `FS6011`).
5. **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.

## References

* <https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-organization-roles>
* <https://docs.github.com/en/rest/orgs/organization-roles>
