Repository protection configuration is currently classic branch protection on develop only. This moves it to rulesets and closes two gaps.
Gaps
- Tags are unprotected. A published release tag drives
build_and_push.yml → Docker Hub :latest → the Watchtower ping that recreates production. A moved or force-pushed tag is a direct production-deploy vector, and GitHub sunset classic tag protection rules in Aug 2024 — a ruleset is now the only mechanism.
- No explicit PR requirement, no linear-history requirement, and
enforce_admins: false meant every control was admin-bypassable.
Target state
- Ruleset
release-tags — target ~ALL (tags here are bare semver, 3.1.4; a refs/tags/v* pattern would match nothing), deletion + non_fast_forward, zero bypass actors.
- Ruleset
default-branch — target ~DEFAULT_BRANCH, zero bypass actors: deletion, non_fast_forward, required_linear_history, required checks Build/Lint/Test with strict, and PR required with squash-only merges and review-thread resolution.
- Required approvals stay at 0. PR authors cannot approve their own PRs, and this repo has one active author — requiring an approval would block every PR and get routed around via bypass. The PR itself is the control, not the approval.
.github/CODEOWNERS for routing only; require_code_owner_review stays off (it needs two or more active owners to be anything but a merge block).
- Dependabot non-major updates arm GitHub auto-merge; majors still land by hand.
- Classic protection deleted once a real PR has passed through the ruleset.
Declined
Signed commits (GitHub blocks squash-merging someone else's PR into a signing-required branch, and the standards that matter require signed releases and tags, not commits), the OpenSSF Scorecard workflow, merge queue, and a Dependabot bypass actor (unnecessary at 0 approvals).
Repository protection configuration is currently classic branch protection on
developonly. This moves it to rulesets and closes two gaps.Gaps
build_and_push.yml→ Docker Hub:latest→ the Watchtower ping that recreates production. A moved or force-pushed tag is a direct production-deploy vector, and GitHub sunset classic tag protection rules in Aug 2024 — a ruleset is now the only mechanism.enforce_admins: falsemeant every control was admin-bypassable.Target state
release-tags— target~ALL(tags here are bare semver,3.1.4; arefs/tags/v*pattern would match nothing),deletion+non_fast_forward, zero bypass actors.default-branch— target~DEFAULT_BRANCH, zero bypass actors:deletion,non_fast_forward,required_linear_history, required checksBuild/Lint/Testwithstrict, and PR required with squash-only merges and review-thread resolution..github/CODEOWNERSfor routing only;require_code_owner_reviewstays off (it needs two or more active owners to be anything but a merge block).Declined
Signed commits (GitHub blocks squash-merging someone else's PR into a signing-required branch, and the standards that matter require signed releases and tags, not commits), the OpenSSF Scorecard workflow, merge queue, and a Dependabot bypass actor (unnecessary at 0 approvals).