Skip to main content
Applies to BloodHound Enterprise and CE This document provides empirically verified analysis of how GitHub branch protection rules interact with two key attack paths in the GitHound model. All findings were validated through systematic testing against live GitHub repositories.

Attack Paths

1. Secret Exfiltration via Workflow Creation

A user with write access (GH_WriteRepoContents) to a repository can:
  1. Create a new branch in the repository
  2. Push a workflow file (.github/workflows/*.yml) to that branch with on: push trigger
  3. The workflow executes automatically on push
  4. The workflow can access all repo-level and org-level secrets (GH_HasSecret) available to the repository
  5. The workflow exfiltrates the secrets (e.g., via HTTP request to an attacker-controlled server)
Graph path: (:GH_User)-[:GH_HasRole|GH_HasBaseRole|GH_MemberOf*1..]->(:GH_RepoRole)-[:GH_WriteRepoContents]->(repo:GH_Repository)-[:GH_HasSecret]->(:GH_RepoSecret|:GH_OrgSecret) PR reviews do not prevent this attack because they only gate merging, not pushing to new branches. The attacker never needs to merge anything.

2. Supply Chain Attack via Direct Push

A user with write access to a repository can push directly to the default branch (e.g., main, master), injecting a backdoor into released software. Graph path: (:GH_User)-[:GH_HasRole|GH_HasBaseRole|GH_MemberOf*1..]->(:GH_RepoRole)-[:GH_WriteRepoContents]->(repo:GH_Repository)

Branch Protection Settings

SettingGraphQL FieldBPR PropertyEffect
Require PR reviewsrequiresApprovingReviewsrequired_pull_request_reviewsBlocks direct pushes to existing protected branches (merge-gate control)
Restrict pushesrestrictsPushespush_restrictionsRestricts who can push to matching branches (push-gate control)
Block creationsblocksCreationsblocks_creationsRestricts creation of new branches matching the pattern. Requires push_restrictions; silently reverts to false otherwise
Lock branchlockBranchlock_branchMakes the branch completely read-only (merge-gate control)
Enforce for adminsisAdminEnforcedenforce_adminsEnforces merge-gate controls for admins and users with bypass_branch_protection
Allow force pushesallowsForcePushesallows_force_pushesControls whether force pushes are allowed; does not grant push access

Setting Dependencies

  • blocks_creations requires push_restrictions to be true. If push_restrictions is false, the GitHub API accepts the mutation but silently reverts blocks_creations to false.
  • allows_force_pushes only controls whether history rewrites are permitted. It does not bypass any access controls.

Merge-Gate vs. Push-Gate Controls

Branch protection settings fall into two distinct categories based on what they control and how they can be bypassed. This distinction is critical because each category has a completely different set of bypass mechanisms, and enforce_admins only affects one category.

Merge-Gate Controls

Govern whether changes can be merged or committed to a protected branch. They enforce code review and read-only policies.
SettingPropertyWhat it blocks
Require PR reviewsrequired_pull_request_reviewsDirect pushes to existing protected branches — forces changes through pull requests
Lock branchlock_branchAll changes to the branch — makes it completely read-only
Merge-gate controls are bypassed by bypass_branch_protection and bypassPullRequestAllowances. They are enforced by enforce_admins.

Push-Gate Controls

Govern who is authorized to push to matching branches. They are an access control layer that restricts push operations to an explicit allowlist.
SettingPropertyWhat it blocks
Restrict pushespush_restrictionsPushes from anyone not in the pushAllowances list
Block creationsblocks_creationsCreation of new branches matching the pattern (requires push_restrictions)
Push-gate controls are bypassed by push_protected_branch, admin access, and pushAllowances. They are NOT enforced by enforce_admins.
A common misconfiguration is enabling enforce_admins and assuming all protections are enforced. In reality, enforce_admins only enforces merge-gate controls. Admin users and users with push_protected_branch can still bypass push restrictions regardless of the enforce_admins setting.

Bypass Mechanisms

There are seven mechanisms that can bypass branch protection rules. They fall into two categories based on which type of control they bypass.

Merge-Gate Bypasses

MechanismScopeEdge/PropertyBlocked by enforce_admins?
bypass_branch_protection permissionRepo-wide (via custom role)GH_BypassBranchProtectionYes
bypassPullRequestAllowancesPer-rule (specific users/teams)GH_BypassPullRequestAllowancesNot tested (likely yes)
bypassPullRequestAllowances is narrower than bypass_branch_protection. It only bypasses PR review requirements, not lock branch.

Push-Gate Bypasses

MechanismScopeEdge/PropertyBlocked by enforce_admins?
push_protected_branch permissionRepo-wide (via custom role)GH_PushProtectedBranchNo
Admin accessRepo-wide (built-in role)GH_AdminToNo
pushAllowancesPer-rule (specific users/teams)GH_RestrictionsCanPushNot tested (likely no)

Other Bypasses

MechanismEffectEdge
edit_repo_protections permissionCan remove/modify protection rules entirely, then pushGH_EditRepoProtections

Complete Test Results

Test Series 1: New Branch Creation (Secret Exfiltration Path)

Can a user with write access create a new branch and push a workflow?
TestPR ReviewsPush RestrictionsBlocks Creations (*)Result
1OnOffOffSucceeded — new branch created
2OnOnOffSucceeded — new branch created
3OnOnOnBlocked
4OffOnOnBlocked
5OffOffOn (silently ignored)Succeededblocks_creations reverted to false
Conclusion: The only branch protection configuration that blocks the secret exfiltration attack is push_restrictions + blocks_creations on a * pattern rule.

Test Series 2: Push to Existing Protected Branch (Supply Chain Path)

Can a user with write access push directly to master?
TestProtection ConfigResultError Message
1PR reviews onlyBlocked”Changes must be made through a pull request”
2Push restrictions (user NOT in allowances)Blocked”You’re not authorized to push”
3Push restrictions (user IN allowances)Succeeded
4Lock branchBlocked”Cannot change this locked branch”
Conclusion: Any one of PR reviews, push restrictions (without allowance), or lock branch is sufficient to block direct pushes to an existing protected branch.

Test Series 3: bypass_branch_protection Permission

TestProtection ConfigResult
3.1PR reviewsBypassed (“Bypassed rule violations”)
3.2Push restrictions (not in allowances)Blocked (“You’re not authorized to push”)
3.3Lock branchBypassed (“Bypassed rule violations”)
3.4Push restrictions + blocks creations (*)Blocked (“You’re not authorized to push”)
Conclusion: bypass_branch_protection bypasses merge-gate controls (PR reviews, lock branch) but NOT push-gate controls (push_restrictions).

Test Series 4: push_protected_branch Permission

TestProtection ConfigResult
4.1PR reviewsBlocked (“Changes must be made through a pull request”)
4.2Push restrictions (not in allowances)Bypassed
4.3Lock branchBlocked (“Cannot change this locked branch”)
4.4Push restrictions + blocks creations (*)Bypassed (new branch created)
Conclusion: push_protected_branch bypasses push-gate controls (push_restrictions, blocks_creations) but NOT merge-gate controls (PR reviews, lock branch). It is the exact complement of bypass_branch_protection.

Test Series 5: enforce_admins Interaction

TestProtectionActorResult
5.1PR reviewsbypass_branch_protectionBlocked (enforce_admins suppresses bypass)
5.2Lock branchbypass_branch_protectionBlocked (enforce_admins suppresses bypass)
5.3Push restrictionspush_protected_branchBypassed (enforce_admins has no effect)
5.4Push restrictions + blocks creations (*)Admin (enforce_admins OFF)Bypassed
5.5Push restrictions + blocks creations (*)Admin (enforce_admins ON)Bypassed (enforce_admins has no effect)
Conclusion: enforce_admins only enforces merge-gate controls. It suppresses bypass_branch_protection but has no effect on push_protected_branch or admin push access.

Test Series 6: allows_force_pushes

TestProtection ConfigResult
6.1Lock branch + force push allowedBlocked (“Cannot change this locked branch”)
6.2Push restrictions + force push allowedBlocked (“You’re not authorized to push”)
Conclusion: allows_force_pushes is not a bypass mechanism. It only controls whether force pushes (history rewrites) are permitted for users who already have push access.

Test Series 7: Both Permissions Combined

User with both bypass_branch_protection and push_protected_branch:
TestProtection ConfigResult
7.1PR reviewsBypassed
7.2Push restrictionsBypassed
7.3Lock branchBypassed
7.4Push restrictions + blocks creations (*)Bypassed (new branch created)
Conclusion: Both permissions combined provide full bypass capability, equivalent to admin access.

Test Series 8: bypassPullRequestAllowances (Per-Rule Edge)

User in bypassPullRequestAllowances list (regular write access, no custom role):
TestProtection ConfigResult
8.1PR reviewsBypassed (“Bypassed rule violations”)
8.2Lock branchBlocked (“Cannot change this locked branch”)
Conclusion: bypassPullRequestAllowances is narrower than the bypass_branch_protection permission. It only bypasses PR review requirements, not lock branch.

Summary Matrix

ProtectionRegular Writebypass_branch_protectionpush_protected_branchBothAdminbypassPRAllowancespushAllowances
PR reviewsBlockedBypassedBlockedBypassedN/TBypassedN/T
Push restrictionsBlockedBlockedBypassedBypassedBypassedN/TBypassed
Lock branchBlockedBypassedBlockedBypassedN/TBlockedN/T
Blocks creations (*)BlockedBlockedBypassedBypassedBypassedN/TN/T
enforce_admins effectSuppressedNo effectNo effectN/TN/T
N/T = Not tested (not applicable to that control type)

Effective Mitigating Controls

For Secret Exfiltration (Write → New Branch → Workflow → Secrets)

The attack requires creating a new branch. This is only blocked when all of the following are true:
  1. A GH_BranchProtectionRule exists with pattern = *
  2. push_restrictions = true
  3. blocks_creations = true
However, even with this control in place, the following actors can still exfiltrate secrets:
  • Users with push_protected_branch permission (GH_PushProtectedBranch)
  • Users with admin access (GH_AdminTo) — cannot be mitigated by enforce_admins
  • Users in pushAllowances for the * rule (GH_RestrictionsCanPush)
  • Users with edit_repo_protections permission (GH_EditRepoProtections) — can remove the rule
  • Users with both bypass_branch_protection and push_protected_branch

For Supply Chain Attack (Write → Push to Default Branch)

Any one of the following protections is sufficient to block direct pushes to an existing branch:
  • required_pull_request_reviews = true
  • push_restrictions = true (and attacker not in pushAllowances)
  • lock_branch = true
Bypass vectors per protection type:
ProtectionBypassed by
PR reviewsbypass_branch_protection, bypassPullRequestAllowances (both blocked by enforce_admins)
Push restrictionspush_protected_branch, admin, pushAllowances (none blocked by enforce_admins)
Lock branchbypass_branch_protection (blocked by enforce_admins)