All knowledge articles

Protecting the Main Branch Beyond Pull-Request Approval

Why a single required approval is not by itself a defensible main-branch control, and how required status checks, force-push protection, and branch ruleset enforcement close the gap.

CI/CD PipelinesSupply Chain SecurityAccess Control

Most teams treat 'require one approval before merge' as their main-branch control and stop there. It is a necessary layer, but it is not a sufficient one: it says nothing about whether the commits actually being merged are the ones that were reviewed, whether the branch's history can be rewritten after approval, or whether a privileged identity can skip the requirement entirely. This guide is about the merge gate specifically — the point where a change becomes part of the protected branch — and about what a defensible, layered version of that gate actually contains.

Layered protection for a protected main branchA contributor's pull request passes through required review, required status checks, and branch ruleset enforcement before reaching the protected main branch. Interactive: switch between the normal path where every layer passes and a failure mode where a privileged bypass attempt skips required review and required status checks but is still rejected by branch ruleset enforcement, and explore each node's role.ContributorPullrequestRequiredreviewRequired statuschecksBranch rulesetenforcementProtectedmainBypassattempt

Hover or focus a node to explore it.

Fictional repository: contributor → pull request → required review → required status checks → branch ruleset enforcement → main. In the failure mode, a bypass attempt skips required review and required status checks entirely; branch ruleset enforcement — which applies without exception to any actor and blocks force-pushes — is the layer that actually stops it from reaching main.
01

Executive summary

This is a narrower and more concrete companion to 'Threat Modeling a CI/CD Pipeline,' which covers a general method for threat-modeling an entire pipeline's trust boundaries from commit to deployment. This guide does not repeat that method; it applies a layered-defense lens to one specific boundary — required review, required status checks tied to the exact commit, and branch ruleset enforcement — using a fictional repository throughout. No real repository, credential, or production configuration is described.

02

What you will learn

  • Why 'one approval required' has real, well-documented gaps even when it is correctly configured as far as it goes.
  • How required status checks tied to an exact commit close the gap left by commits pushed after approval.
  • Why force-push and history-rewrite protection matters even on a branch that already requires review.
  • How administrator or maintainer bypass exemptions quietly undermine every other layer, and how to remove them.
  • The practical difference between a platform's older, per-branch protection rules and a newer, layered ruleset model — and why the distinction affects what 'protected' actually means.
03

Intended audience

  • Developers who want to understand what their repository's branch protection setting actually guarantees, and what it does not.
  • DevOps practitioners responsible for configuring or auditing a repository's merge gate.
  • Security engineers reviewing whether a team's main-branch control is defensible, not just present.
04

Problem or security question

'Require pull-request approval before merge' is the first main-branch control almost every team configures, and for many teams it is also the last one they configure. Treated as sufficient on its own, it leaves several gaps that do not show up until someone — or something — actually exercises them: a reviewer approves a pull request, and a further commit is pushed to the same branch afterward without triggering a fresh review; a branch is force-pushed, rewriting history the reviewer approved into history they never saw; or a repository administrator, exempted from the review requirement by default in some configurations, merges directly.

None of these require a sophisticated attacker. A compromised contributor account, a misconfigured automation identity with write access, or simply a rushed human under deadline pressure can exercise any of them against a branch whose only enforced layer is 'someone clicked approve at some point.' The fix is not a different single control — it is treating the merge gate as a set of layers, each closing a specific gap the others leave open.

05

Threat model or relevant risk

Assets: the integrity of the main branch's commit history, the provenance of whatever is actually merged into it, and — downstream — the trustworthiness of anything built or deployed from that branch.

Layers, in the order this guide addresses them: required review (is at least one approval recorded), required status checks (do the defined checks pass against the exact commit being merged, not just the branch generally), and branch ruleset enforcement (does the branch reject force-pushes and history rewrites, and does the enforcement apply to every actor with no bypass list).

Representative threats: a reviewer approves a pull request, and a materially different commit is pushed afterward and merged without a fresh review or a fresh check run. A branch is force-pushed after approval, replacing reviewed history with unreviewed history. A repository administrator, exempt from the review requirement by a configuration default nobody revisited, merges directly during an incident and the exemption stays in place afterward. A required check's definition lives in the same repository the check is supposed to gate, and a pull request modifies or disables it in the same change it is meant to be checking.

The interactive diagram accompanying this article shows the failure case concretely: a privileged or compromised identity attempts to skip required review and required status checks entirely, going straight for a direct push or a forced history rewrite. Branch ruleset enforcement — applied without exception and with no bypass list — is the layer that decides whether that attempt actually reaches main.

06

Main technical content

Layer 1 — required review — is where most teams start and, too often, where they stop. At minimum it requires at least one approving review before a merge is offered. Configured correctly, this is a real control: it means a second set of eyes examined the diff. Configured as the only layer, its gaps become the whole story. An approval is granted against whatever commits exist on the branch at that moment; nothing about the approval itself re-validates automatically if the branch changes afterward, unless the platform is separately configured to require it. And unless a repository's protection settings explicitly say otherwise, an administrator or maintainer role frequently carries an implicit exemption from the review requirement — a detail that matters precisely because those are the accounts with the broadest reach if compromised.

Layer 2 — required status checks — closes the most common version of the first gap. A defined set of checks (a build, a test suite, and where the platform supports it, verification that commits are signed) must report success against the exact commit about to be merged, and the branch is usually required to be up to date with its base branch first. This is the layer that prevents 'the pull request was approved, then quietly changed, then merged anyway' — because the new commit has to pass its own checks, not inherit the approval or the check results of an earlier one. Commit signing, where required and enforced (rather than merely displayed as a badge), adds a related but distinct guarantee: that the commit's claimed author is verifiable, independent of whether its contents passed review.

Layer 3 — branch ruleset enforcement — is where the two most consequential gaps get closed: force-push protection and bypass exemptions. A branch that blocks force-pushes and history rewrites means an already-approved, already-checked set of commits cannot be silently replaced with a different set after the fact; the history a reviewer saw is the history that merges. And a ruleset with no bypass list — one that applies identically to a repository administrator and to the newest contributor — is what actually determines whether a privileged-account shortcut succeeds. This is the layer most likely to be left weaker than intended, because it is usually configured once, rarely revisited, and its exemptions tend to accumulate quietly (an administrator added to a bypass list 'temporarily' during an incident, and never removed).

Two mechanisms are worth naming specifically because they are commonly conflated. Many Git hosting platforms distinguish between an older, per-branch protection-rule model — typically a single set of toggles applied to one branch pattern, including an explicit 'include administrators' style setting that is easy to leave unchecked — and a newer ruleset model that layers multiple named rules, can target several branches by pattern at once, and is built around an explicit, auditable bypass list rather than a binary include/exclude toggle. Neither model is automatically the safe one; a ruleset with a generous bypass list is no better than a classic rule with administrators exempted. The distinction matters because reviewing 'is this branch protected' now requires reading the actual rule contents, not just confirming that some protection mechanism exists.

One further gap is worth calling out because it is easy to miss: if the required checks' own definitions live inside the repository being protected, a pull request can, in principle, modify or weaken those definitions in the same change the checks are meant to be evaluating. A required workflow enforced from outside the individual repository's control — where the hosting platform supports that — removes this specific blind spot; where it isn't available, treating any change to check definitions as requiring its own heightened review is the next best mitigation.

Put together, the layers are cumulative, not redundant: required review catches what an automated check cannot (does this change make sense, is it the right approach); required status checks catch what a human reviewer easily misses (does it actually build, does it pass its tests, is the commit signed) and specifically catch late changes to the branch; branch ruleset enforcement catches what neither of the other two layers can — an attempt to bypass them altogether, whether by rewriting history or by exploiting a privileged account's exemption.

07

Requirements

  • Administrative access (or a documented change request to whoever holds it) to the repository's branch protection or ruleset configuration — this guide describes what a defensible configuration contains, not how to request access to change it.
  • A clear inventory of who currently holds elevated (administrator or maintainer) access to the target repository, since several of the gaps below matter specifically for privileged identities, not ordinary contributors.
  • Team agreement on what 'required' actually means for status checks — a check that can be marked non-blocking, skipped, or is simply missing from the required list provides no more assurance than not having it at all.
  • Willingness to test the configuration by attempting the exact bypasses it claims to prevent, in a lab or non-production repository, rather than assuming a configuration screen's label does what it says.
08

Procedure

  • Start from the assumption that required review is necessary but not sufficient, and enumerate what it does not cover on its own: commits pushed to the branch after approval was granted, force-pushes that rewrite history the reviewer never saw, and any bypass path available to a privileged identity.
  • Add required status checks and pin them to re-run against the exact commit being merged, not merely 'somewhere on this branch.' Require the branch to be up to date with its base before merge is offered, so a stale branch cannot merge on the strength of checks that ran against an earlier, different base.
  • Where commit authorship needs to be verifiable, require signed commits and reject unsigned ones at the ruleset level rather than treating signature verification as informational metadata a reviewer might or might not notice.
  • Block force-pushes and branch deletion on the protected branch, and require linear history if the team's workflow depends on merge commits or rebases behaving predictably. A branch that can be rewritten after review invalidates everything the review layer was supposed to guarantee.
  • Remove bypass exemptions for privileged identities. A classic branch-protection rule that leaves administrators exempt from its own requirements, or a ruleset with a non-empty bypass list, quietly reintroduces the single-layer failure mode this guide is about — for exactly the accounts with the most reach if compromised.
  • Where the CI configuration itself lives inside the repository, treat a change to the required-workflow definition as a change that needs the same scrutiny as any other protected-branch change — a contributor should not be able to weaken or remove a required check from within the same pull request that check is supposed to be gating. A required workflow enforced centrally, outside the reach of an individual repository's own contributors, closes this specific gap more reliably than a workflow file that branch's own contributors can edit.
  • Document the resulting layered configuration — which checks are required, whether commit signing is required, whether the bypass list is empty, whether force-push is blocked — somewhere a reviewer can audit later without re-deriving it from the platform's configuration screens each time.
09

Validation

  • Confirm required status checks are enforced against the exact commit being merged, not merely present in the repository: attempt, in a lab or non-production repository, to merge a pull request whose latest commit has not yet reported a passing check, and confirm the platform refuses.
  • Confirm the branch rejects a force-push attempt, including from an account with elevated repository permissions, and confirm the rejection is an actual block rather than a warning that can be dismissed.
  • Confirm no bypass actor, exemption, or blanket administrator opt-out is configured — enumerate the effective bypass list directly from the configuration rather than trusting a policy document that says bypass isn't allowed.
  • Confirm that a new commit pushed to an already-approved pull request requires required status checks to pass again on that new commit before merge is offered, and confirm separately whether the platform also re-requires review on that push — this varies by configuration and is worth confirming explicitly rather than assumed.
  • Where a control could not be exercised directly (no lab environment, no authorized change to attempt a real bypass), record that limitation explicitly as UNVERIFIED rather than treating a configuration screen's description as proof the control holds.
10

Rollback

  • If tightened branch ruleset enforcement blocks a legitimate emergency-fix workflow — a genuine production incident that needs a fast, reviewed merge — have a documented, time-boxed emergency path defined in advance, such as a narrowly scoped and reviewed-after-the-fact exception, rather than disabling the ruleset or reintroducing a standing bypass actor under pressure.
  • If a required status check turns out to be flaky rather than meaningfully protective, fix or replace the check; do not quietly drop it from the required list, since that silently returns the branch to a weaker layer without anyone deciding that on purpose.
  • Keep a record of any period during which a layer was relaxed and why, so a later reviewer can distinguish 'this was never enforced' from 'this was temporarily relaxed for a documented reason and then restored.'
11

Validation or evidence

This article describes a configuration pattern and a fictional illustrative repository; it does not reproduce a specific implementation or a completed audit against a real system. Its evidence state is UNVERIFIED, and the recommendations should be treated as a starting checklist to adapt and then verify against your own repository's actual configuration, not as a validated result.

12

Limitations

This guide addresses one branch's merge gate. It does not cover multi-branch promotion flows (feature to staging to main), environment-specific deployment approval, or the security of the build runner itself once a commit does merge — 'Threat Modeling a CI/CD Pipeline' covers the broader pipeline picture this guide deliberately does not repeat.

Exact feature names, defaults, and availability (for example, which tier or plan includes a bypass list, centrally enforced required workflows, or signed-commit enforcement at the ruleset level) vary by platform and change over time. Verify the current behavior of your specific platform before relying on any single mechanism described here.

This guide assumes a single protected main branch is the goal. Some teams intentionally use a more complex branching model; the same layered principle (review, status checks tied to an exact commit, and enforcement with no bypass) still applies to whichever branch or branches actually gate a release, but the mapping onto this guide's fictional example will need adjusting.

13

Defensive recommendations

  • Require status checks against the exact commit being merged, and require the branch to be up to date with its base before merge is offered.
  • Require signed commits where authorship verification matters, and enforce it at the branch level rather than displaying it only as informational.
  • Block force-pushes and branch deletion on every branch that gates a release, not only on main.
  • Remove administrator and maintainer bypass exemptions; if an emergency path is genuinely needed, make it a documented, logged, time-boxed exception rather than a standing exemption.
  • Prefer required-check definitions enforced from outside the repository they protect, where the platform supports it, so a pull request cannot weaken the check that is supposed to be gating it.
  • Review the effective bypass list directly from the configuration on a recurring cadence — exemptions added during an incident have a way of outliving the incident.
  • Document the full layered configuration in one place a reviewer can audit without re-deriving it from the platform's settings screens each time.
14

Key takeaways

  • Required review is necessary but not sufficient: it does not by itself prevent late commits, force-push history rewrites, or privileged bypass.
  • Required status checks tied to the exact commit being merged close the 'approved, then quietly changed' gap.
  • Branch ruleset enforcement with no bypass list is what actually stops a privileged or compromised identity from skipping the other layers.
  • A defensible main-branch control is the combination of these layers, not any single one of them — and it is worth auditing periodically, since bypass exemptions and disabled checks tend to accumulate quietly rather than being removed all at once.
15

References

  • GitHub Docs, About protected branches: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches
  • GitHub Docs, About rulesets: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets
  • SLSA (Supply-chain Levels for Software Artifacts): https://slsa.dev/
  • OWASP Top 10 CI/CD Security Risks: https://owasp.org/www-project-top-10-ci-cd-security-risks/