Several Failed Protected Branch Force Pushes by User

Detects a high number of failed force push attempts to protected branches by a single user within a short time frame. Adversaries may attempt multiple force pushes to overwrite commit history on protected branches, potentially leading to data loss or disruption of development workflows.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2025/12/16"
  3integration = ["github"]
  4maturity = "production"
  5updated_date = "2026/01/12"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Detects a high number of failed force push attempts to protected branches by a single user within a short
 11time frame. Adversaries may attempt multiple force pushes to overwrite commit history on protected branches,
 12potentially leading to data loss or disruption of development workflows.
 13"""
 14from = "now-9m"
 15interval = "8m"
 16language = "esql"
 17license = "Elastic License v2"
 18name = "Several Failed Protected Branch Force Pushes by User"
 19note = """ ## Triage and analysis
 20
 21> **Disclaimer**:
 22> This investigation guide was created using generative AI technology and has been reviewed to improve its accuracy and relevance. While every effort has been made to ensure its quality, we recommend validating the content and adapting it to suit your specific environment and operational needs.
 23
 24### Investigating Several Failed Protected Branch Force Pushes by User
 25
 26This rule flags a single user generating multiple failed force push attempts to protected branches within a short span, indicating attempts to rewrite commit history and bypass branch protections. An attacker with a compromised maintainer role repeatedly tries to roll back a security fix, delete prior commits, and erase history entries before pushing a malicious revision. This activity threatens code integrity, disrupts pipelines, and can propagate harmful changes across repositories.
 27
 28### Possible investigation steps
 29
 30- Pull audit entries for the rejected updates to confirm the rejection reasons and the exact org/repo/branch targets, then reconstruct the timeline and sequence of attempts.
 31- Verify the user's current and recent permissions, team membership, and role changes, and confirm whether any admin or ownership transfers occurred before the attempts.
 32- Correlate the attempts with authentication and token activity (SSO logins, PAT/SSH key usage, IP/device fingerprints, geo), flagging any new or unusual sources.
 33- Review branch protection settings and recent edits (require status checks, linear history, admin enforcement, force push exemptions) to detect policy tampering or misconfiguration.
 34- Identify the specific commits the force pushes sought to overwrite by diffing the attempted ref against the protected branch head, prioritizing impacts to security fixes, release branches, or signed commits.
 35
 36### False positive analysis
 37
 38- During a repository migration or history cleanup, a maintainer runs a local script that loops through branches and tries to push rewritten commits with --force, but newly tightened branch protection rejects each attempt, resulting in multiple failures.
 39- A developer who previously had a force-push exemption on a protected release branch loses that permission during a role or team change and continues their usual rebase-and-force-push workflow, causing several rapid rejected ref updates.
 40
 41### Response and remediation
 42
 43- Immediately block the user in the GitHub organization, revoke all active personal access tokens and SSH keys from their account, and force sign-out to stop further push attempts.
 44- On each affected repository and branch (e.g., main, release/*), remove any force-push exemptions, enable “Include administrators,” require signed commits and status checks, and restrict push access to specific teams.
 45- Purge staging artifacts by deleting any branches or tags the user created around the attempts, rotate the user’s password and regenerate PATs/SSH keys, and remove newly registered keys or OAuth apps added during the window.
 46- Validate recovery by confirming the protected branch HEAD matches the last known good signed commit SHA, re-running CI for impacted repos, and creating a restore point tag for rapid rollback.
 47- Escalate to incident response if any attempts targeted main or release branches, originated from a newly created PAT/SSH key or an unrecognized IP/device, or the user holds repo admin/organization owner rights.
 48- Harden long term by enforcing org-wide 2FA/SSO, removing all standing force-push exemptions, requiring CODEOWNERS approvals on protected branches, and enabling audit alerts for branch protection edits and new credential creation.
 49"""
 50references = [
 51    "https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack",
 52    "https://trigger.dev/blog/shai-hulud-postmortem",
 53    "https://posthog.com/blog/nov-24-shai-hulud-attack-post-mortem",
 54]
 55risk_score = 47
 56rule_id = "8bd1c36a-2c4f-4801-a43d-ba696c13ffc2"
 57severity = "medium"
 58tags = [
 59    "Domain: Cloud",
 60    "Use Case: Threat Detection",
 61    "Tactic: Impact",
 62    "Tactic: Exfiltration",
 63    "Data Source: Github",
 64    "Resources: Investigation Guide",
 65]
 66timestamp_override = "event.ingested"
 67type = "esql"
 68query = '''
 69from logs-github.audit-* metadata _id, _index, _version
 70| where
 71  data_stream.dataset == "github.audit" and
 72  github.category == "protected_branch" and
 73  event.action == "protected_branch.rejected_ref_update"
 74| stats
 75  Esql.document_count = COUNT(*),
 76  Esql.github_org_values = values(github.org),
 77  Esql.github_repo_values = values(github.repo),
 78  Esql.github_branch_values = values(github.branch),
 79  Esql.github_reasons_code_values = values(github.reasons.code),
 80  Esql.github_reasons_message_value = values(github.reasons.message),
 81  Esql.user_name_values = values(user.name),
 82  Esql.agent_id_values = values(agent.id),
 83  Esql.event_dataset_values = values(event.dataset),
 84  Esql.data_stream_namespace_values = values(data_stream.namespace)
 85
 86  by user.name
 87
 88| keep Esql.*
 89
 90| where
 91  Esql.document_count >= 5
 92'''
 93
 94[[rule.threat]]
 95framework = "MITRE ATT&CK"
 96
 97[[rule.threat.technique]]
 98id = "T1485"
 99name = "Data Destruction"
100reference = "https://attack.mitre.org/techniques/T1485/"
101
102[rule.threat.tactic]
103id = "TA0040"
104name = "Impact"
105reference = "https://attack.mitre.org/tactics/TA0040/"
106
107[[rule.threat]]
108framework = "MITRE ATT&CK"
109
110[[rule.threat.technique]]
111id = "T1020"
112name = "Automated Exfiltration"
113reference = "https://attack.mitre.org/techniques/T1020/"
114
115[[rule.threat.technique]]
116id = "T1567"
117name = "Exfiltration Over Web Service"
118reference = "https://attack.mitre.org/techniques/T1567/"
119
120[[rule.threat.technique.subtechnique]]
121id = "T1567.001"
122name = "Exfiltration to Code Repository"
123reference = "https://attack.mitre.org/techniques/T1567/001/"
124
125[rule.threat.tactic]
126id = "TA0010"
127name = "Exfiltration"
128reference = "https://attack.mitre.org/tactics/TA0010/"

Triage and analysis

Disclaimer: This investigation guide was created using generative AI technology and has been reviewed to improve its accuracy and relevance. While every effort has been made to ensure its quality, we recommend validating the content and adapting it to suit your specific environment and operational needs.

Investigating Several Failed Protected Branch Force Pushes by User

This rule flags a single user generating multiple failed force push attempts to protected branches within a short span, indicating attempts to rewrite commit history and bypass branch protections. An attacker with a compromised maintainer role repeatedly tries to roll back a security fix, delete prior commits, and erase history entries before pushing a malicious revision. This activity threatens code integrity, disrupts pipelines, and can propagate harmful changes across repositories.

Possible investigation steps

  • Pull audit entries for the rejected updates to confirm the rejection reasons and the exact org/repo/branch targets, then reconstruct the timeline and sequence of attempts.
  • Verify the user's current and recent permissions, team membership, and role changes, and confirm whether any admin or ownership transfers occurred before the attempts.
  • Correlate the attempts with authentication and token activity (SSO logins, PAT/SSH key usage, IP/device fingerprints, geo), flagging any new or unusual sources.
  • Review branch protection settings and recent edits (require status checks, linear history, admin enforcement, force push exemptions) to detect policy tampering or misconfiguration.
  • Identify the specific commits the force pushes sought to overwrite by diffing the attempted ref against the protected branch head, prioritizing impacts to security fixes, release branches, or signed commits.

False positive analysis

  • During a repository migration or history cleanup, a maintainer runs a local script that loops through branches and tries to push rewritten commits with --force, but newly tightened branch protection rejects each attempt, resulting in multiple failures.
  • A developer who previously had a force-push exemption on a protected release branch loses that permission during a role or team change and continues their usual rebase-and-force-push workflow, causing several rapid rejected ref updates.

Response and remediation

  • Immediately block the user in the GitHub organization, revoke all active personal access tokens and SSH keys from their account, and force sign-out to stop further push attempts.
  • On each affected repository and branch (e.g., main, release/*), remove any force-push exemptions, enable “Include administrators,” require signed commits and status checks, and restrict push access to specific teams.
  • Purge staging artifacts by deleting any branches or tags the user created around the attempts, rotate the user’s password and regenerate PATs/SSH keys, and remove newly registered keys or OAuth apps added during the window.
  • Validate recovery by confirming the protected branch HEAD matches the last known good signed commit SHA, re-running CI for impacted repos, and creating a restore point tag for rapid rollback.
  • Escalate to incident response if any attempts targeted main or release branches, originated from a newly created PAT/SSH key or an unrecognized IP/device, or the user holds repo admin/organization owner rights.
  • Harden long term by enforcing org-wide 2FA/SSO, removing all standing force-push exemptions, requiring CODEOWNERS approvals on protected branches, and enabling audit alerts for branch protection edits and new credential creation.

References

Related rules

to-top