Skip to main content
RunBook Academy

Git, CI/CD & GitOpsLXXXIX · Repository SecurityRepoSecurity

Branch protection deep dive — the full set of options

Advanced⏱ ~26 mingit

What you'll learn

  • Enumerate the branch protection controls and state precisely which failure each one prevents
  • Select the load-bearing subset for an infrastructure repository and defend the selection
  • Identify the bypass paths that survive a fully configured protection rule
  • Read a protection rule from the API rather than trusting the settings page

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A protected branch is not a single feature. It is a stack of independent switches, each of which prevents one specific thing, and the reason teams end up with a protected branch that fails to protect is that they enabled the two switches everyone talks about and left the rest at defaults. This lesson walks the full set and says what each one is for.

The pull-request controls

These govern how a change reaches the branch at all.

  • Require a pull request before merging. The base control: direct pushes are refused, so every change has a diff, a discussion, and a record.
  • Required approvals. How many. One is a review; two on an infrastructure repository is a real second pair of eyes and a defence against a single compromised account.
  • Dismiss stale approvals when new commits are pushed. Without this, an approval survives a rewrite of the branch it approved. This is the single most commonly missing switch on repositories that have all the others.
  • Require review from Code Owners. Ties approval to the people who own the touched paths rather than to whoever is available.
  • Require approval of the most recent reviewable push. Prevents an author from approving their own last change when they are also an owner of the path.
  • Require conversation resolution before merging. Turns an unresolved review comment into a merge blocker instead of a note nobody actioned.

The history and integrity controls

These govern the shape of what lands.

  • Require signed commits. Every commit on the branch carries a verified signature, so the author field stops being a self-asserted string.
  • Require linear history. Merge commits are refused; the branch is a straight line, which makes bisect and audit reasoning tractable.
  • Require deployments to succeed. Ties merges to a successful deployment to a named environment.
  • Lock branch. Read-only. The correct setting for a branch that is retained for audit and must never change.
  • Do not allow bypassing the above settings. Applies the rules to administrators too. Off by default in effect on many repositories, and it is the switch that decides whether your protection is a control or a suggestion.
flowchart TD
    A["push to protected branch"] --> B{"direct push allowed?"}
    B -->|"no"| C["pull request required"]
    C --> D{"approvals met?"}
    D -->|"yes"| E{"status checks green?"}
    E -->|"yes"| F{"conversations resolved?"}
    F -->|"yes"| G["merge permitted"]
    B -->|"admin bypass enabled"| H["direct push lands"]
    D -->|"stale approval reused"| G

The two edges on the right are the failure modes: an admin bypass that was never disabled, and an approval that survived a force-push because stale dismissal is off.

Restrictions and allowances

  • Restrict who can push to matching branches. Narrows the set of principals allowed to merge, independent of repository role. Use it to keep the deploy bot and nobody else on a release branch.
  • Allow force pushes. Off. On an infrastructure repo this is the switch that lets someone rewrite the history the audit trail depends on.
  • Allow deletions. Off. A deleted protected branch takes its protection rule with it.

Rulesets: the same controls, better shape

Both forges now offer a layered mechanism above classic branch protection. On GitHub these are rulesets; the practical differences that matter:

  • Multiple rules can apply to the same branch and their effects combine, instead of one rule winning.
  • Rules can be defined at organisation level and applied across repositories, so a policy is set once.
  • Bypass is explicit and enumerated - you list who may bypass, rather than inferring it from role.
  • Evaluate mode lets a rule report what it would have blocked before it blocks anything.

Read the effective configuration from the API rather than the settings page, because the page shows one rule and the effective state is the union of all of them:

REPO=example-org/infra-prod
BRANCH=main
gh api "repos/$REPO/branches/$BRANCH/protection" \
  --jq '{checks: .required_status_checks.contexts,
         reviews: .required_pull_request_reviews.required_approving_review_count,
         admins: .enforce_admins.enabled}'

Production discipline

  1. Enable bypass prevention on day one. A rule that exempts administrators is a rule that exempts exactly the accounts an attacker most wants.
  2. Stale approval dismissal is not optional. Approval is of a diff, not of a branch name.
  3. Audit the pattern coverage, not just the rule. List the refs your deployment path reads, and prove each one is matched by a rule.

Cross-course references

  • Terraform for Production Sysadmins - the branch a pipeline applies from must be protected to the same standard as the state backend it writes.
  • Kubernetes for Production Engineers - a GitOps controller reconciling from an unprotected branch is a direct write path into the cluster.
  • Linux for Production Sysadmins - the change-control parts cover the same separation of duties at host level.

Quiz

Knowledge check · 4 questions

  1. Q1. A repository requires a pull request, two approvals, and green status checks on `main`. An engineer gets two approvals, then force-pushes a rewritten branch containing an extra commit that adds a wildcard IAM policy, and merges. Which protection setting was missing?

  2. Q2. Cloning a protected repository carries the branch protection rules into the clone.

  3. Q3. Name three branch protection settings whose default state leaves a bypass path open on an infrastructure repository.

  4. Q4. A change reached production through a branch nobody had protected. Reconstruct how, and specify the coverage fix.

    A GitOps controller reconciles the production cluster from the branch `deploy/prod` in repository `infra-prod`. Branch protection on that repository has one rule, matching `main`, with two required approvals, required checks, and bypass prevention enabled. A change adding a privileged container to a production namespace was pushed directly to `deploy/prod` by an engineer with write access; the controller applied it eleven minutes later. There is no pull request, no review, and no CI run associated with the change.

Passing score: 75%. Answers are checked in this browser.