#!/bin/bash RED=$(tput setaf 1) GREEN=$(tput setaf 2) YELLOW=$(tput setaf 3) BLUE=$(tput setaf 4) RESET=$(tput sgr0) usage() { echo "${BLUE}Usage:${RESET} $0 " echo " Path to JSON file with parameters (org, user, whitelist)" echo "" echo "GITHUB_TOKEN must be set in your environment (e.g., in ~/.zshrc, ~/.bashrc, or .env)." } if [[ "$1" == "-h" || "$1" == "--help" ]]; then usage exit 0 fi if [[ $# -ne 1 ]]; then usage exit 1 fi INPUT_JSON="$1" if [[ ! -f "$INPUT_JSON" ]]; then echo "${RED}❌ Error: Input file '$INPUT_JSON' not found.${RESET}" exit 1 fi # Check for required tools for tool in jq curl; do if ! command -v $tool &>/dev/null; then echo "${RED}❌ Error: Required tool '$tool' is not installed. Please install it and try again.${RESET}" exit 1 fi done # Load environment variables (token only) if [[ -f .env ]]; then export $(grep -v '^#' .env | xargs) fi if [[ -z "$GITHUB_TOKEN" ]]; then echo "${RED}❌ Error: GITHUB_TOKEN is not set.\nSet it in your environment (e.g., export GITHUB_TOKEN=... in ~/.zshrc, ~/.bashrc, or .env).${RESET}" exit 1 fi # Parse JSON input GITHUB_ORG=$(jq -r '.org // empty' "$INPUT_JSON") GITHUB_USER=$(jq -r '.user // empty' "$INPUT_JSON") WHITELIST=($(jq -r '.whitelist[]?' "$INPUT_JSON")) INTERACTIVE_REVIEW=$(jq -r '.interactive_review // "false"' "$INPUT_JSON") if [[ -z "$GITHUB_ORG" || -z "$GITHUB_USER" || ${#WHITELIST[@]} -eq 0 ]]; then echo "${RED}❌ Error: 'org', 'user', and 'whitelist' must be specified in the JSON file.${RESET}" exit 1 fi # Fetch all private repositories where the user is an owner or collaborator REPO_DATA=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/user/repos?per_page=100&affiliation=owner,collaborator&visibility=private") # Extract repository names REPO_LIST=$(echo "$REPO_DATA" | jq -r '.[].name') # Exit if no repositories found if [[ -z "$REPO_LIST" ]]; then echo "${YELLOW}❌ No private repositories found or token is missing the 'repo' scope.${RESET}" exit 1 fi echo "${BLUE}🔍 Checking repositories for non-whitelisted collaborators...${RESET}" for REPO in $REPO_LIST; do # Determine if repo is under organization or user REPO_OWNER=$(echo "$REPO_DATA" | jq -r --arg REPO "$REPO" '.[] | select(.name==$REPO) | .owner.login') # If repo belongs to the org, use $GITHUB_ORG instead of user if [[ "$REPO_OWNER" == "$GITHUB_ORG" ]]; then REPO_OWNER="$GITHUB_ORG" fi # Fetch all collaborators (includes users even if they haven't committed) ALL_COLLABORATORS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/repos/$REPO_OWNER/$REPO/collaborators" | jq -r '.[].login') # Identify non-whitelisted collaborators NON_WHITELISTED_COLLABS=() for COLLAB in $ALL_COLLABORATORS; do if [[ ! " ${WHITELIST[@]} " =~ " ${COLLAB} " ]]; then NON_WHITELISTED_COLLABS+=("$COLLAB") fi done # Only show repositories where non-whitelisted collaborators exist if [[ ${#NON_WHITELISTED_COLLABS[@]} -gt 0 ]]; then echo "${YELLOW}🚨 Repository: $REPO (Owner: $REPO_OWNER)${RESET}" echo "${RED}❌ Non-Whitelisted Collaborators:${RESET}" printf '%s\n' "${NON_WHITELISTED_COLLABS[@]}" echo "--------------------------------------------" if [[ "$INTERACTIVE_REVIEW" == "true" ]]; then # Ask for confirmation and delete non-whitelisted collaborators for USER in "${NON_WHITELISTED_COLLABS[@]}"; do read -p "Do you want to remove collaborator '$USER' from '$REPO'? (yes/no): " CONFIRM if [[ "$CONFIRM" == "yes" ]]; then # Attempt to remove as a direct repository collaborator RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/repos/$REPO_OWNER/$REPO/collaborators/$USER") if [[ "$RESPONSE" -eq 204 ]]; then echo "${GREEN}✅ Successfully removed $USER from repository $REPO.${RESET}" else echo "${YELLOW}⚠️ Failed to remove $USER from repository $REPO (HTTP Status: $RESPONSE). Checking if they are an org member...${RESET}" # Check if the user is an organization member ORG_MEMBER_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/orgs/$GITHUB_ORG/memberships/$USER") if [[ "$ORG_MEMBER_STATUS" -eq 200 ]]; then read -p "❗ $USER is an organization member. Remove them from org '$GITHUB_ORG'? (yes/no): " CONFIRM_ORG if [[ "$CONFIRM_ORG" == "yes" ]]; then ORG_REMOVE_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/orgs/$GITHUB_ORG/memberships/$USER") if [[ "$ORG_REMOVE_RESPONSE" -eq 204 ]]; then echo "${GREEN}✅ Successfully removed $USER from organization '$GITHUB_ORG'.${RESET}" else echo "${RED}❌ Failed to remove $USER from the organization (HTTP Status: $ORG_REMOVE_RESPONSE). Checking if they are in a team...${RESET}" # If removal from org fails, check if the user is in a team TEAMS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/orgs/$GITHUB_ORG/teams" | jq -r '.[].slug') for TEAM in $TEAMS; do TEAM_REMOVE_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/orgs/$GITHUB_ORG/teams/$TEAM/memberships/$USER") if [[ "$TEAM_REMOVE_RESPONSE" -eq 204 ]]; then echo "${GREEN}✅ Successfully removed $USER from team '$TEAM'.${RESET}" break fi done fi else echo "${YELLOW}🚫 Skipped removal of $USER from organization.${RESET}" fi else echo "${RED}❌ $USER is neither a direct collaborator, nor an organization member, nor a team member. No action taken.${RESET}" fi fi else echo "${YELLOW}🚫 Skipped removal of $USER from $REPO.${RESET}" fi done echo "--------------------------------------------" fi fi done