> For the complete documentation index, see [llms.txt](https://docs.forestall.io/forestall/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forestall.io/forestall/edges/github/gh_push_to_default.md).

# GH\_PUSH\_TO\_DEFAULT

## Summary

|                              |                                                                           |
| ---------------------------- | ------------------------------------------------------------------------- |
| **Forestall ACL Alias**      | GH\_PUSH\_TO\_DEFAULT                                                     |
| **Edge Type**                | Attack Path                                                               |
| **Affected Object Types**    | GitHub Repositories                                                       |
| **Exploitation Certainty**   | Certain                                                                   |
| **GitHub Role / Permission** | Repository `write` (push) or `maintain`, on an unprotected default branch |

## Description

`GH_PUSH_TO_DEFAULT` represents that a user or team can push code **directly to a repository's default branch**. The default branch is what CI/CD builds and deploys from, so the ability to write to it without review is a supply-chain foothold.

The edge is created only when both conditions hold:

* The entity has **write-level** permission (`push` or `maintain`) on the repository, and
* The default branch is **not protected** — there is no branch protection rule gating direct pushes. This is the relational form of the `FS6021` (No Branch Protection) finding.

Admin holders are intentionally **not** given this edge: they are already modelled by `GH_ADMIN` (full control, which also includes the ability to remove protection and push). Archived repositories cannot be pushed to and are excluded.

When the default branch is protected (required reviews, status checks, or push restrictions), a direct push is blocked and this edge is not created — the writer must go through a reviewed pull request instead.

## Identification

### GitHub Web Interface

1. Open the repository → **Settings → Branches**. If the default branch has **no protection rule**, direct pushes are allowed.
2. Open **Settings → Collaborators and teams**. Any user or team with **Write** or **Maintain** can push directly to the unprotected default branch.

### GitHub CLI

Check whether the default branch is protected (404 = unprotected):

```bash
gh api "repos/{org}/{repo}" --jq '.default_branch' \
  | xargs -I{} gh api "repos/{org}/{repo}/branches/{}/protection" 2>/dev/null \
  || echo "default branch is NOT protected"
```

List teams and collaborators that hold write-level access:

```bash
gh api "repos/{org}/{repo}/teams" --paginate \
  --jq '.[] | select(.permission=="push" or .permission=="maintain") | .slug'
gh api "repos/{org}/{repo}/collaborators?affiliation=direct" --paginate \
  --jq '.[] | select(.permissions.push==true and .permissions.admin==false) | .login'
```

## Exploitation

When the default branch is unprotected, a write-level entity can compromise the repository's build and deployment pipeline with a single push:

1. **Inject a malicious GitHub Actions workflow.** A workflow pushed to `.github/workflows/` runs with the repository's secrets and `GITHUB_TOKEN`. Where OIDC federation is configured, it can mint a cloud token and pivot out of GitHub entirely.

   ```yaml
   # .github/workflows/ci.yml — attacker-controlled
   on: [push]
   permissions:
     id-token: write   # request an OIDC token
     contents: read
   jobs:
     pivot:
       runs-on: ubuntu-latest
       steps:
         - uses: azure/login@v2          # exchange OIDC token for cloud creds
           with:
             client-id: ${{ secrets... }}
             tenant-id: ...
             subscription-id: ...
         - run: az account show           # now operating in the cloud tenant
   ```
2. **Tamper with shipped code.** Backdoor application source on the default branch so the next deploy carries the attacker's payload to production.
3. **Exfiltrate secrets at build time.** Print or transmit repository/environment secrets from a workflow step.

Because no review gates the push, the change reaches the default branch (and the pipeline) immediately — hence **Certain** certainty.

## Mitigation

1. **Protect the default branch.** Enable a branch protection rule (or a ruleset) on the default branch: require pull requests, require at least one review, and disallow direct pushes. This removes the edge entirely. See `FS6021`.
2. **Require review for workflow changes.** Use `CODEOWNERS` to require an owner's review for `.github/workflows/` so workflow tampering cannot be self-merged.
3. **Grant least privilege.** Prefer `triage`/`read` over `write` where contributors do not need to push. Manage write access through teams, not direct collaborator grants.
4. **Constrain Actions and OIDC.** Set default workflow permissions to read-only, restrict which actions can run, and scope cloud federated-identity credentials (`subject` claim) to specific protected branches/environments rather than the whole repository.
5. **Include administrators in protection.** Enable "Do not allow bypassing the above settings" so even admins cannot push directly to the default branch on sensitive repositories.

## Detection

Monitor the GitHub audit log and Git events for direct writes to the default branch:

| Event                                        | Description                                                            |
| -------------------------------------------- | ---------------------------------------------------------------------- |
| `git.push`                                   | A push was received (correlate ref = default branch, no associated PR) |
| `protected_branch.destroy`                   | A branch protection rule was removed (opens this edge)                 |
| `repo.update_member` / `team.add_repository` | Write-level access was granted                                         |
| `workflows` run from an unexpected actor     | A new/modified workflow executed shortly after a direct push           |

Alert on direct pushes to the default branch that are not associated with a reviewed pull request, on any removal of branch protection, and on workflow runs that request `id-token: write` from an unexpected actor.

## References

* <https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches>
* <https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect>
* <https://docs.github.com/en/rest/branches/branch-protection#get-branch-protection>
