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 GitHub extension graph 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

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. 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. 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

bypassPullRequestAllowances is narrower than bypass_branch_protection. It only bypasses PR review requirements, not lock branch.

Push-Gate Bypasses

Other Bypasses

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? 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? 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

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

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

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

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: 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): Conclusion: bypassPullRequestAllowances is narrower than the bypass_branch_protection permission. It only bypasses PR review requirements, not lock branch.

Summary Matrix

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:

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: