Excessive number of Repo admins
More Info:
Allowing multiple users admin or push access to organization repositories places the organization at risk for contributions that can be pushed without review.
Risk Level
Low
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Using Console
Below are concise, step‑by‑step instructions to reduce excessive repository admins in GitHub using the web console (GitHub.com UI).
1. Confirm You Have Permission
You must be:
- An Organization Owner (for org repos), or
- A Repository Admin (for personal or org repos)
to change admin permissions.
2. List Current Admins for a Repository
- Sign in to GitHub.com.
- Open the repository:
https://github.com/<org-or-user>/<repo-name> - Click Settings (top menu in the repo).
- In the left sidebar, click:
- Collaborators and teams (for org repos: “Manage access”)
- Under Manage access, review:
- Teams with access and their permission level
- Individuals with access and their permission level
Anyone with Admin permission here is effectively a repo admin.
3. Reduce Individual Admins to Lower Roles
For each user who doesn’t truly need admin rights:
- In Settings → Manage access, find the person with Admin access.
- Click the gear icon (or three-dot menu) next to that user.
- Under Repository access, change:
- From: Admin
- To: Maintain (for maintainers) or Write (for regular contributors)
- Click Save or Update access.
Repeat for all users who should not be admins.
4. Use Teams Instead of Individual Admins (Org Repos)
If you are in an organization:
- Go to the org page:
https://github.com/<org> - Click Teams.
- Create or choose a team (e.g.
repo-maintainers). - Click the team → Repositories → Add a repository to this team.
- Select the repository and set permission to:
- Maintain for people who manage issues/PRs but don’t need admin, or
- Write for regular contributors.
- Remove individual Admin access for those now covered by the team (same method as step 3).
5. Remove Unnecessary Teams with Admin Access
- In the repository, go to Settings → Manage access.
- Under Teams, find any team with Admin level.
- Click the gear icon next to the team.
- Change permission:
- From Admin
- To Maintain or Write, as appropriate.
- Save changes.
6. Restrict Who Can Be a Repo Admin at Org Level (Optional)
For organizations to keep admin counts low going forward:
- Go to the Organization page.
- Click Settings.
- In the left sidebar, review:
- Member privileges / Repository creation / Base permissions.
- Set:
- Base permissions to Read (or lower), not Admin.
- Restrict who can create repositories or manage settings to Organization owners only.
- Ensure only a small set of Organization Owners exist (Org Settings → People → change role from Owner to Member where appropriate).
7. Periodically Audit Admins
- On a schedule (e.g. monthly), for each critical repo:
- Go to Settings → Manage access.
- Export or screenshot current access.
- Remove or downgrade permissions that are no longer needed.
If you share your repo type (personal vs organization) and current number of admins/teams, I can suggest an exact target layout (e.g., “1 owner + 1 maintainers team + write-only contributors”).
Using CLI
Below is a concise, step‑by‑step way to identify and reduce excessive repo admins using the GitHub CLI (gh).
Assumptions:
- You have
ghinstalled and authenticated (gh auth login). - You’re an admin/owner on the repo or org.
- Replace
OWNER(user/org) andREPOwith your values.
1. List who has admin access on a repository
A. List collaborators and their permissions (classic collaborators)
gh api \
repos/OWNER/REPO/collaborators \
--paginate \
--jq '.[] | select(.permissions.admin == true) | {login, permissions}'
This shows logins that have admin: true via direct collaborator access.
B. List teams with admin access (for org repos)
gh api \
repos/OWNER/REPO/teams \
--paginate \
--jq '.[] | select(.permission == "admin") | {name, slug, permission}'
Members of these teams may effectively be “repo admins” depending on team permission.
C. (Optional) List org members with admin role (org‑wide)
gh api \
orgs/OWNER/members \
--paginate \
--jq '.[] | select(.role == "admin") | .login'
Use this to identify who can manage repos/org settings globally.
2. Decide the minimal required admins
Offline step:
- For each admin user or team, decide:
- Keep as admin,
- Downgrade to
maintainorpush, or - Remove.
Document the desired target state: user/team → new permission.
3. Downgrade or remove individual repo admins
A. Change a collaborator’s permission (e.g., admin → maintain)
gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
repos/OWNER/REPO/collaborators/USERNAME \
-f permission=maintain
Valid values: pull, triage, push, maintain, admin.
B. Remove a collaborator entirely
gh api \
--method DELETE \
repos/OWNER/REPO/collaborators/USERNAME
4. Downgrade or remove admin teams
A. Change team permission on the repo (admin → maintain, for example)
gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
orgs/OWNER/teams/TEAM_SLUG/repos/OWNER/REPO \
-f permission=maintain
B. Remove team access to the repo
gh api \
--method DELETE \
orgs/OWNER/teams/TEAM_SLUG/repos/OWNER/REPO
5. Reduce organization‑wide admins (if needed)
Use this carefully—org admins lose global powers.
A. List current org admins
gh api \
orgs/OWNER/members \
--paginate \
--jq '.[] | select(.role == "admin") | .login'
B. Change a user’s org role from admin → member
gh api \
--method PUT \
orgs/OWNER/memberships/USERNAME \
-f role=member
C. Remove a user from the org (if appropriate)
gh api \
--method DELETE \
orgs/OWNER/members/USERNAME
6. Re‑verify effective repo admins
Re‑run checks:
# Direct collaborators with admin
gh api repos/OWNER/REPO/collaborators --paginate \
--jq '.[] | select(.permissions.admin == true) | {login, permissions}'
# Teams with admin
gh api repos/OWNER/REPO/teams --paginate \
--jq '.[] | select(.permission == "admin") | {name, slug, permission}'
Confirm the list is limited to the strictly necessary admins.
Using Python
Below is a practical, step‑by‑step way to reduce excessive repo admins in GitHub using Python (via the GitHub REST API and/or PyGithub).
1. Preparation
- Decide the policy you want to enforce, for example:
- Only a specific team(s) can be admin.
- Or max N admin users per repo.
- Create a GitHub Personal Access Token (PAT) with at least:
repoadmin:org(if managing org repos, teams, etc.)
Export it as an environment variable (recommended):
export GITHUB_TOKEN="ghp_XXXXXXXXXXXXXXXXXXXX"
export GITHUB_ORG="your-org-name"
2. Install Python dependencies
pip install PyGithub requests
3. Enumerate Admins and Decide Remediations
3.1. Using PyGithub to list repos and their admins
from github import Github
import os
token = os.environ["GITHUB_TOKEN"]
org_name = os.environ["GITHUB_ORG"]
g = Github(token)
org = g.get_organization(org_name)
for repo in org.get_repos():
print(f"\nRepo: {repo.full_name}")
# Direct collaborators
print(" Direct admins:")
for collab in repo.get_collaborators(permission="admin"):
print(f" USER: {collab.login}")
# Teams with admin permission
print(" Teams with admin:")
for team in repo.get_teams():
if team.permission == "admin": # 'admin', 'push', 'pull', 'maintain', etc.
print(f" TEAM: {team.slug}")
Use this output to decide which users/teams should no longer have admin rights.
4. Implement Least-Privilege Changes
4.1. Define allowed admins (policy)
Example: only one “core-admins” team plus maybe some specific user(s) per repo.
ALLOWED_ADMIN_TEAMS = {"core-admins"} # team slugs
ALLOWED_ADMIN_USERS = {"repo-owner-1"} # usernames allowed to stay admin
DEFAULT_USER_PERMISSION = "push" # downgrade excess admins to 'push'
4.2. Downgrade or remove excessive admins
from github import Github
import os
token = os.environ["GITHUB_TOKEN"]
org_name = os.environ["GITHUB_ORG"]
ALLOWED_ADMIN_TEAMS = {"core-admins"}
ALLOWED_ADMIN_USERS = {"repo-owner-1"}
DEFAULT_USER_PERMISSION = "push" # or 'pull'
g = Github(token)
org = g.get_organization(org_name)
for repo in org.get_repos():
print(f"\nProcessing repo: {repo.full_name}")
# --- Handle teams with admin permissions ---
for team in repo.get_teams():
if team.permission == "admin":
if team.slug not in ALLOWED_ADMIN_TEAMS:
print(f" Downgrading/removing TEAM admin: {team.slug}")
# Option 1: downgrade to 'push'
team.update_team_repository(repo, permission="push")
# Option 2 (alternative): remove team access completely
# team.remove_from_repos(repo)
# --- Handle direct users with admin permissions ---
for collab in repo.get_collaborators(permission="admin"):
if collab.login not in ALLOWED_ADMIN_USERS:
print(f" Downgrading USER admin: {collab.login}")
# Option 1: downgrade to 'push'
repo.add_to_collaborators(collab.login, permission=DEFAULT_USER_PERMISSION)
# Option 2 (alternative): remove access completely
# repo.remove_from_collaborators(collab.login)
Run in dry-run mode first by commenting out the change lines and just printing what would happen.
5. Safer: Dry-run / Audit Mode
Add a DRY_RUN = True flag:
DRY_RUN = True # Set False to apply changes
for repo in org.get_repos():
print(f"\nProcessing repo: {repo.full_name}")
for team in repo.get_teams():
if team.permission == "admin" and team.slug not in ALLOWED_ADMIN_TEAMS:
print(f" Would downgrade TEAM {team.slug} from admin to push")
if not DRY_RUN:
team.update_team_repository(repo, permission="push")
for collab in repo.get_collaborators(permission="admin"):
if collab.login not in ALLOWED_ADMIN_USERS:
print(f" Would downgrade USER {collab.login} from admin to {DEFAULT_USER_PERMISSION}")
if not DRY_RUN:
repo.add_to_collaborators(collab.login, permission=DEFAULT_USER_PERMISSION)
6. (Optional) Using Raw REST API via requests
If you prefer not to use PyGithub:
- List collaborators with permission:
GET /repos/{owner}/{repo}/collaborators?permission=admin - Change a collaborator’s permission:
PUT /repos/{owner}/{repo}/collaborators/{username}with body:{"permission": "push"}
Minimal example:
import os
import requests
token = os.environ["GITHUB_TOKEN"]
org = os.environ["GITHUB_ORG"]
headers = {"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"}
def list_repos():
url = f"https://api.github.com/orgs/{org}/repos"
r = requests.get(url, headers=headers)
r.raise_for_status()
return r.json()
def list_admin_collaborators(repo):
url = f"https://api.github.com/repos/{org}/{repo}/collaborators"
r = requests.get(url, headers=headers, params={"permission": "admin"})
r.raise_for_status()
return [c["login"] for c in r.json()]
def downgrade_user(repo, user, perm="push"):
url = f"https://api.github.com/repos/{org}/{repo}/collaborators/{user}"
r = requests.put(url, headers=headers, json={"permission": perm})
r.raise_for_status()
for r in list_repos():
repo_name = r["name"]
admins = list_admin_collaborators(repo_name)
for user in admins:
if user not in {"repo-owner-1"}:
print(f"Downgrading {user} on {repo_name}")
downgrade_user(repo_name, user, "push")
7. Governance
- Run the audit/remediation script periodically (e.g., as a GitHub Action or CI job).
- Log all changes.
- Optionally notify affected users/teams before/after changes.
If you describe your exact policy (who should remain admin and in what cases), I can adjust the Python logic and permissions transitions precisely to match it.
Using Terraform
# GitHub repository (maps to github-applicationintegration-scm-repository)
resource "github_repository" "repo" {
name = "REPO_NAME" # replace with the repository name
description = "REPO_DESCRIPTION"
visibility = "private"
}
# SINGLE admin (keep admin here)
resource "github_repository_collaborator" "admin" {
repository = github_repository.repo.name
username = "PRIMARY_ADMIN_USERNAME" # replace with the single intended admin
permission = "admin"
}
# All other users: drop down from admin/push to lower permission (e.g., pull)
resource "github_repository_collaborator" "read_only_collaborators" {
for_each = toset([
"USER_1_USERNAME", # replace with each non-admin user that previously had admin/push
"USER_2_USERNAME",
# ...
])
repository = github_repository.repo.name
username = each.value
permission = "pull" # or "triage" / "maintain" as policy allows, but not "admin" or "push"
}
Changing a collaborator’s permission does not replace the repository; it updates access in place, but it is immediately effective and may block previous admin/push actions.
For verification, terraform plan should show:
- No change to
github_repository.repo github_repository_collaborator.adminwithpermission = "admin"- All other
github_repository_collaborator.*changing fromadmin/pushto the lower permission you chose (e.g.,pull).