HomeBlog › Branch protection

Branch protection: stopping direct pushes to main

A protected branch is the cheapest control in this whole guide. It takes five minutes, changes nothing about how you work on a good day, and removes an entire category of bad one.

Branch protection illustration: feature and bugfix branches climbing to a pull request, through required code review and passing checks, before merging into a protected main branch that refuses direct pushes.

Of everything in the readiness guide, this is the item with the best ratio of effort to benefit. It is a settings page. It takes five minutes. And it is almost always the first thing a security questionnaire asks about engineering process, because it is concrete, checkable, and tells the reader a great deal about how a team works.

The question underneath it is simple: can one person, on their own, at any hour, put code into production with nobody else having seen it? If the answer is yes, then every control that depends on code review is theoretical.

What the rule actually does

Branch protection is a set of constraints your source control host applies to a named branch, usually main. The useful ones:

  • No direct pushes. Changes must arrive through a pull or merge request. This is the core of it.
  • Required approving review. At least one other person must look at the change and say so.
  • Required status checks. Tests, linting, a build, a security scan. Whatever you already run has to pass.
  • No force pushes. Prevents history being rewritten, which matters because a rewritten history can quietly remove evidence of what actually happened.
  • No branch deletion. Stops the obvious accident.
  • Dismiss stale approvals. If somebody approves a change and then more commits are pushed, the approval no longer applies to what will be merged.

That last one is subtle and worth turning on. Without it, a reviewer's approval of a small, sensible change can carry over to a much larger one pushed afterwards, which is a review in name only.

Turning it on in GitHub

Repository settings, then Branches, then add a branch protection rule, or use the newer rulesets interface which does the same job at organisation level.

Name the branch, then enable: require a pull request before merging, require approvals with a count of one, dismiss stale approvals when new commits are pushed, require status checks to pass, and require branches to be up to date before merging.

Two options deserve a moment's thought.

Include administrators. By default, repository admins can bypass the rules. Leaving that bypass in place means the rule applies to everyone except the people most able to cause damage. Turn it on. If you genuinely need to bypass it in an emergency, you can turn it off for the two minutes that takes, and that act is logged.

Require branches to be up to date. This forces a change to be rebased or merged against current main before it can land, which catches the case where two changes are individually fine and together are not. It adds a small amount of friction and removes a genuinely confusing class of bug.

Turning it on in GitLab

Settings, then Repository, then Protected branches. Set the allowed-to-push role to No one and allowed-to-merge to your developer role. Then under Merge requests, enable approvals and set the required number to one.

GitLab separates push permission from merge permission more explicitly than GitHub does, which makes the intent clearer: nobody pushes, everybody merges through a request.

The solo maintainer problem

If you are the only person with commit access, requiring an approving review locks you out of your own repository. You cannot approve your own pull request on either platform.

The answer is not to skip the whole thing. Enable everything except the review requirement:

  • No direct pushes, so every change goes through a pull request.
  • Required status checks, so tests and linting must pass.
  • No force pushes, no deletion.
  • Include administrators, so the rule applies to you.

You then open a pull request, wait for CI, and merge it yourself. That feels like ceremony for one person, and it buys three real things: the automated checks genuinely gate the merge, there is a diff you look at once more before it lands, and you have an auditable record of every change with its checks attached.

When somebody asks how changes reach production, "through a pull request with passing checks, on a branch that refuses direct pushes" is a good answer even when you are the only reviewer.

The checks worth requiring

Required checks are only as useful as the checks themselves. A gate that runs nothing is a gate that opens.

Start with what you have. If you have tests, require them. If you have a linter, require it. Then consider adding two things that are cheap and catch disproportionate problems:

  • A secret scanner, which refuses a merge containing something that looks like a credential. This is the automated half of keeping secrets out of your repository, and it works better than remembering.
  • A dependency audit, flagging known vulnerabilities in packages you depend on. Noisy at first, because there is always a backlog, so it is worth starting as a warning and promoting it to a blocker once the existing findings are dealt with.

Resist requiring a check that fails regularly for reasons nobody understands. A flaky required check teaches the team to look for the bypass, and once the bypass becomes habit, the protection is gone.

What reviewers should actually look for

Requiring review does not by itself produce review. A rubber stamp satisfies the rule and catches nothing, and most teams drift towards rubber stamps without noticing.

A short shared understanding of what the reviewer is responsible for helps more than a long checklist. Three questions carry most of the value:

  • Does this change who can see or do what? New endpoints, changed permissions, new queries against customer data. These deserve more attention than anything else in a diff.
  • Is there a credential, a token or a connection string in here? The scanner should catch it, and a human glance is free.
  • Would I be able to work out what this does in six months? Not a security question exactly, and the thing that makes an incident at 2am shorter.

Everything else, formatting, naming, structure, is worth saying but not worth blocking on. A review culture that blocks on taste produces slow reviews, and slow reviews produce the pressure that gets the rule turned off.

Why this one comes up first

Auditors and prospects ask about branch protection early for the same reason: it is binary and it is verifiable. There is no interpretation involved. Either changes require review and passing checks, or they do not.

It is also a proxy. A team with branch protection, required checks and a sensible review habit is usually a team that has thought about the other items in this guide. A team pushing directly to main usually has not. That inference is not always fair, and it is the inference people draw.

What a Trufend scan can and cannot tell you here. Branch protection is a setting inside GitHub, GitLab or wherever your code lives. No external scan of your domain can observe it. Trufend reports what your site exposes to an anonymous visitor and is explicit that it is not a SOC 2 assessment. This item sits in the readiness guide because it is a real control that no scanner will ever score.

Five minutes

If you take one action from this whole series, make it this one. Open your repository settings, protect main, include administrators, and require whatever checks you already run. It changes almost nothing about a normal Tuesday and removes the version of Friday night where one tired person puts something into production alone.

Questions people ask about this

What is branch protection?

A rule on your source control host that restricts what may be done to a particular branch. Typically it blocks direct pushes, requires changes to arrive through a pull request, requires at least one approving review, and requires automated checks to pass before a merge is allowed.

How do I handle this as a solo developer?

You cannot approve your own pull request, so requiring a review would block you entirely. Enable everything else: no direct pushes, required status checks, no force pushes, no branch deletion. You still get the checks and the audit trail, and you merge your own pull request once CI is green.

Does branch protection slow the team down?

For ordinary work, barely. It adds a pull request to a flow that usually has one anyway. What it does slow down is pushing a hotfix straight to production at midnight, which is the thing it exists to slow down.

What is the difference between required reviews and required checks?

A required review is a person approving the change. A required status check is automation, such as tests or a linter, that must pass. They catch different classes of problem and are worth having together.

Can Trufend see whether our main branch is protected?

No. Branch protection is a setting inside your source control host, invisible to any external scan of your domain. Trufend reports what your site exposes publicly and is not a SOC 2 assessment.

See what your domain exposes from outside

Trufend checks TLS, DNS, security headers, email authentication and exposed assets on any domain. Free, no account, about thirty seconds.

Run a free check