Skip to content

Instantly share code, notes, and snippets.

@jabley
Last active April 24, 2026 08:37
Show Gist options
  • Select an option

  • Save jabley/4b401ceafb70395f0345c8ce3089cb34 to your computer and use it in GitHub Desktop.

Select an option

Save jabley/4b401ceafb70395f0345c8ce3089cb34 to your computer and use it in GitHub Desktop.
Tools for checking private vulnerability reporting on your public repos

In large organisations, you probably use teams to manage maintainer access to repositories. So here are a couple of scripts.

list-team-repos.sh will recursively enumerate all repositories visible to a particular team. Recurisive because teams might be nested and so child teams will grant access to further repos.

check-private-vuln-reporting.sh will list (and can be tweaked to set) private vulnerability reporting for a list of repos in the format emitted by list-team-repos.sh.

Both scripts use the gh CLI tool and you'll need to gh auth login first to run them.

#!/usr/bin/env bash
set -euo pipefail
usage() {
echo "Usage: $0 [owner/repo ...]" >&2
echo " cat repos.txt | $0" >&2
}
check_repo() {
local repo_ref="$1"
local owner=""
local repo=""
local visibility=""
local archived=""
local enabled=""
if [[ "$repo_ref" != */* ]]; then
echo "Invalid repository format: '$repo_ref' (expected owner/repo)" >&2
return 64
fi
owner="${repo_ref%%/*}"
repo="${repo_ref#*/}"
if [[ -z "$owner" || -z "$repo" ]]; then
echo "Invalid repository format: '$repo_ref' (expected owner/repo)" >&2
return 64
fi
if ! IFS=$'\t' read -r visibility archived < <(
gh api \
-H "Accept: application/vnd.github+json" \
"/repos/$owner/$repo" \
--jq '[.visibility, (.archived|tostring)] | @tsv' 2>/dev/null
); then
echo "$owner/$repo: could not determine repository visibility/archive status" >&2
echo "Check repo visibility, permissions, and gh auth scopes." >&2
return 2
fi
# Private vulnerability reporting applies only to public, non-archived repositories.
if [[ "$visibility" != "public" || "$archived" != "false" ]]; then
return 0
fi
if ! enabled="$(
gh api \
-H "Accept: application/vnd.github+json" \
"/repos/$owner/$repo/private-vulnerability-reporting" \
--jq '.enabled' 2>/dev/null
)"; then
echo "$owner/$repo: could not determine private vulnerability reporting status" >&2
echo "Check repo visibility, permissions, and gh auth scopes." >&2
return 2
fi
if [[ "$enabled" != "true" ]]; then
# uncomment the following lines to enable private vulnerability reporting for the repo
# See https://docs.github.com/en/rest/repos/repos?apiVersion=2026-03-10#enable-private-vulnerability-reporting-for-a-repository
# gh api \
# --method PUT \
# -H "Accept: application/vnd.github+json" \
# "/repos/$owner/$repo/private-vulnerability-reporting" \
# && echo "$owner/$repo: private vulnerability reporting is now enabled"
# Alternatively, just report the current status without making changes
echo "$owner/$repo: private vulnerability reporting is NOT enabled"
fi
}
# Decide how we're running. Either:
# 1. With repo references as arguments
# 2. With repo references piped in via stdin
# 3. Without arguments (in which case we show the usage instructions and exit)
# If repo references are provided as arguments, check those.
if [[ $# -gt 0 ]]; then
status=0
for repo_ref in "$@"; do
check_repo "$repo_ref" || status=$?
done
exit "$status"
fi
# If stdin is a terminal and therefore isn't being redirected or piped, show usage and exit.
if [[ -t 0 ]]; then
usage
exit 64
fi
# Otherwise, read repo references from stdin and check those.
status=0
while IFS= read -r repo_ref || [[ -n "$repo_ref" ]]; do
[[ -z "$repo_ref" || "$repo_ref" == \#* ]] && continue
check_repo "$repo_ref" || status=$?
done
exit "$status"
#!/usr/bin/env bash
set -euo pipefail
ORG="${1:?Usage: $0 <org> <root-team-slug>}"
ROOT_TEAM_SLUG="${2:?Usage: $0 <org> <root-team-slug>}"
queue=("$ROOT_TEAM_SLUG")
visited=()
contains() {
local needle="$1"
shift
local item
for item in "$@"; do
[[ "$item" == "$needle" ]] && return 0
done
return 1
}
for ((i=0; i<${#queue[@]}; i++)); do
team_slug="${queue[$i]}"
if [[ ${#visited[@]} -gt 0 ]] && contains "$team_slug" "${visited[@]}"; then
continue
fi
visited+=("$team_slug")
# Repos directly granted to this team
gh api --paginate "/orgs/$ORG/teams/$team_slug/repos" \
--jq '.[].full_name'
# Direct child teams; they will be processed recursively via the queue
while IFS= read -r child_slug; do
[[ -n "$child_slug" ]] && queue+=("$child_slug")
done < <(
gh api "/orgs/$ORG/teams/$team_slug/teams" \
--jq '.[].slug'
)
done | sort -u
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment