Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/governance-sentinel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: governance-sentinel

# Hardening Q6 — alert if the default-branch protection ruleset (hardening Q2)
# is removed. Covers loss of force-push + deletion protection on the default
# branch. A failed scheduled run also emails the owner, so the check failing
# is itself a signal.
#
# NOT covered here (GitHub Free + this repo token has no admin:org): org
# member-change and audit-log alerting — those need an org-level webhook or the
# audit-log API (GitHub Team/Enterprise) or a PAT with admin:org. See
# dev/reference/github-org-operations.md §Q6 for the documented remainder.
Comment on lines +8 to +11

on:
schedule:
- cron: '0 6 * * 1' # Mondays 06:00 UTC
workflow_dispatch: {}

permissions:
contents: read
administration: read
issues: write

jobs:
check-branch-protection:
runs-on: ubuntu-latest
steps:
- name: Verify default-branch protection ruleset is active
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
echo "Checking rulesets on $REPO (default branch: $DEFAULT_BRANCH)"

# Fail SAFE/QUIET on a token-permission problem: warn, do not alarm.
if ! rulesets=$(gh api "repos/$REPO/rulesets" 2>/tmp/err); then
echo "::warning::Could not read rulesets (token permissions?). Skipping check."
cat /tmp/err || true
exit 0
fi

ok=0
for id in $(echo "$rulesets" | jq -r '.[] | select(.enforcement=="active" and .target=="branch") | .id'); do
rules=$(gh api "repos/$REPO/rulesets/$id" --jq '[.rules[].type] | sort | join(",")')
echo "active branch ruleset $id -> rules: $rules"
if echo "$rules" | grep -q "non_fast_forward" && echo "$rules" | grep -q "deletion"; then
ok=1
fi
done
Comment on lines +44 to +50

if [ "$ok" -eq 1 ]; then
echo "OK: default-branch force-push + deletion protection is active."
exit 0
fi

echo "::error::Default-branch protection ruleset MISSING or incomplete on $REPO"
title="⚠️ Governance drift: default-branch protection missing on $REPO"
existing=$(gh issue list --repo "$REPO" --state open --search "$title in:title" --json number --jq '.[0].number // empty')
if [ -z "$existing" ]; then
gh issue create --repo "$REPO" --title "$title" --body \
"The scheduled **governance-sentinel** workflow detected that the default-branch protection ruleset (force-push + deletion block, hardening Q2) is no longer active on \`$REPO\`.

**Expected:** an active \`branch\`-target ruleset carrying \`non_fast_forward\` + \`deletion\` rules on \`$DEFAULT_BRANCH\`.

**Action:** re-apply per \`dev/reference/github-org-operations.md\` §Q2, or close this issue if the change was intentional.

_Filed automatically by \`.github/workflows/governance-sentinel.yml\`._"
else
echo "Alert issue already open: #$existing"
fi
exit 1