Branch/ref protection is not enforced or is bypassable
ID |
ref_protection_bypass |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
low |
Family |
SCM |
Tags |
ASVS50:v15.1.1, branch-protection, cicd-sec-01, cicd-security, security, source-code, spvs10-v3.3.18, supply-chain |
Description
Is the branch/ref protection not only present, but effective?
A protection rule that looks configured but can be trivially skipped provides a false sense of security. This check reports two weaknesses in existing protection, on the platforms whose API exposes the primitive:
-
not enforced — the protection exists but is in an advisory/disabled state, so it blocks nothing.
-
bypassable — the protection is active but privileged or broad actors can bypass it (or remove it) unconditionally.
This is the cross-platform counterpart of github_rulesets (which covers GitHub). Presence and strength of protection (does the branch have protection at all, force-push/delete disabled, reviews required) remain owned by unprotected_branch — this detector is deliberately scoped to the enforcement/bypass quality to avoid duplicate findings. A "not enforced" finding is reported one severity below a "bypassable" one, so it never outranks the underlying unprotected_branch finding.
| Insufficient data (the scan token lacks the scope to read the protection detail, or the access lists are empty) yields no finding — the detector never guesses. |
Platform support
| Platform | What is evaluated | Status |
|---|---|---|
GitLab / GitLab Enterprise |
Protected branches: |
Supported |
Azure DevOps |
Branch policies: a policy that is disabled or advisory ( |
Supported |
Bitbucket Cloud |
Branch restrictions that list exempt |
Supported |
GitHub / GitHub Enterprise |
Covered by the |
Covered elsewhere |
Impact context
Branch/ref protection is the control that stops unreviewed or malicious changes from reaching a protected branch (CICD-SEC-1, SPVS V3.3.18). If the rule is not enforced, or a broad set of actors can push around it or remove it, the control is theatre: an attacker with ordinary developer access — or a compromised token — can rewrite history, merge without review, or simply unprotect the branch and then do as they please.
Protected branch "main"
allow_force_push: true
push_access_levels: [ { access_level: 30 } ] # developers
unprotect_access_levels: [ { access_level: 40 } ] # maintainers (non-admin)
-> force-push allowed (history rewritable)
-> developers can push directly to main
-> non-admins can unprotect main and strip all protection
Protected branch "main"
allow_force_push: false
push_access_levels: [ { access_level: 0 } ] # no direct push (MR only)
merge_access_levels: [ { access_level: 40 } ] # maintainers
unprotect_access_levels: [ { access_level: 60 } ] # admins only
-> no finding
Security
Enforce the protection and keep its bypass surface minimal: disallow force-push on protected branches, require merge requests / pull requests rather than direct pushes by broad roles, and restrict who can unprotect a branch (or bypass policies) to administrators. A protection rule is only as strong as the smallest, most-trusted set of actors that can go around it.
Mitigation / Fix
GitLab — in Settings → Repository → Protected branches:
-
Turn off "Allowed to force push".
-
Set "Allowed to push and merge" / "Allowed to merge" to Maintainers (avoid Developers or explicit broad user/group grants).
-
Restrict "Allowed to unprotect" to administrators/owners.
Azure DevOps — in Project settings → Repositories → Policies (branch policies):
-
Set protection policies to Required (blocking) and enabled rather than optional/disabled.
-
On "Minimum number of reviewers", turn off "Allow requestors to approve their own changes".
Bitbucket Cloud — in Repository settings → Branch restrictions:
-
Avoid adding exempt users/groups to a restriction; if an exemption is unavoidable, keep the list minimal and periodically reviewed.
Configuration
To change these options you can modify SCANNER_DIR/conf/misconfigurations/ref_protection_bypass.yml. The following are the default configuration properties:
properties:
# GitLab: report protected branches that allow force-push.
flagForcePush: true
# GitLab: report protected branches with broad push/merge access (developer role or explicit user/group).
flagWeakPushAccess: true
# GitLab: report protected branches that non-admins (or explicit users/groups) can unprotect.
flagUnprotectBypass: true
# Azure DevOps: report branch policies that are disabled or advisory (non-blocking).
flagPolicyNotEnforced: true
# Azure DevOps: report minimum-reviewers policies that allow authors to approve their own PR.
flagSelfApproval: true
# Bitbucket: report branch restrictions that have exempt users/groups.
flagExemptActors: true