High Number of Closed Pull Requests by User
Detects a high number of closed pull requests by a single user within a short time frame. Adversaries may close multiple pull requests to disrupt development workflows or hide malicious changes.
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 closed pull requests by a single user within a short time frame. Adversaries may
11close multiple pull requests to disrupt development workflows or hide malicious changes.
12"""
13from = "now-9m"
14interval = "8m"
15language = "esql"
16license = "Elastic License v2"
17name = "High Number of Closed Pull Requests by User"
18note = """ ## Triage and analysis
19
20> **Disclaimer**:
21> 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.
22
23### Investigating High Number of Closed Pull Requests by User
24
25This rule flags a single user rapidly closing many pull requests in a short window, a disruptive pattern that suppresses review history, delays releases, and masks unauthorized changes. An attacker with stolen maintainer access mass-closes pull requests across multiple repositories, then force-pushes branches and opens new pull requests that sidestep earlier review threads, making malicious edits appear routine amid churn.
26
27### Possible investigation steps
28
29- Determine if the actor is a bot or sanctioned maintenance by confirming account type, scheduled workflows, and change advisories from repo/org owners.
30- Open a sample of the closed PRs to review comments, labels, linked issues, and whether closure coincided with branch deletions, force-pushes, or unusual commit history in the target branches.
31- Correlate the closure burst with audit events for permission changes, role assignments, repository settings edits, or protection rule modifications to detect potential sabotage.
32- Validate the actor’s IPs, geolocation, and user agents against baselines and check for recent PAT creations, OAuth app grants, or SSO anomalies indicating credential theft.
33- Identify whether closed PRs were immediately replaced by new PRs carrying similar diffs that bypass prior review threads and required checks, and verify branch protection remained enforced.
34
35### False positive analysis
36
37- A maintainer or org-owned bot performs scheduled backlog hygiene, closing stale, duplicate, or superseded PRs across multiple repositories after a default branch rename or policy update, resulting in a high closure count from one account.
38- During a planned migration or archival, a release manager closes PRs tied to deprecated branches and consolidates work into new targets, legitimately generating a burst of closures attributed to a single user.
39
40### Response and remediation
41
42- Immediately contain by removing the user from teams with Triage/Write permissions on affected repositories, revoking their personal access tokens from Tokens & keys, and tightening branch protection by disallowing force-pushes and restricting who can push to main and release branches.
43- Trigger escalation to Security Incident Response if closed pull requests span more than five repositories within one hour, coincide with branch deletions or forced pushes, or originate from a new user agent/IP, and disable the account at the identity provider while contacting GitHub Support.
44- Eradicate impact by reopening legitimate PRs via each closed PR URL, using Restore branch or recreating the head branch from the last known commit SHA, and reapplying required labels and reviewers.
45- Recover repository state by comparing diffs of closed PRs to any newly opened PRs by the same user, reverting unauthorized commits in target branches with git revert, and re-running required status checks before merging.
46- Harden controls by enforcing branch protection rules (require two approvals, restrict who can dismiss reviews, require signed commits), enabling CODEOWNERS for critical paths, and turning off Allow deletions on default and release branches.
47- Prevent recurrence by disabling classic PATs and requiring short-lived fine-grained PATs, revoking unusual OAuth app grants, mandating SSO with hardware-backed MFA, and installing a GitHub App/Action that notifies on PR closures with PR URLs, repos, and branches and requires a reason-coded label per policy.
48"""
49references = [
50 "https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack",
51 "https://trigger.dev/blog/shai-hulud-postmortem",
52 "https://posthog.com/blog/nov-24-shai-hulud-attack-post-mortem",
53]
54risk_score = 47
55rule_id = "098bd5cc-fd55-438f-b354-7d6cd9856a08"
56severity = "medium"
57tags = [
58 "Domain: Cloud",
59 "Use Case: Threat Detection",
60 "Tactic: Impact",
61 "Tactic: Exfiltration",
62 "Data Source: Github",
63 "Resources: Investigation Guide",
64]
65timestamp_override = "event.ingested"
66type = "esql"
67query = '''
68from logs-github.audit-* metadata _id, _index, _version
69| where
70 data_stream.dataset == "github.audit" and
71 github.category == "pull_request" and
72 event.type == "change" and
73 event.action == "pull_request.close"
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_user_agent_values = values(github.user_agent),
79 Esql.github_pull_request_url_values = values(github.pull_request_url),
80 Esql.user_name_values = values(user.name),
81 Esql.agent_id_values = values(agent.id),
82 Esql.event_dataset_values = values(event.dataset),
83 Esql.data_stream_namespace_values = values(data_stream.namespace)
84
85 by user.name
86
87| keep Esql.*
88
89| where
90 Esql.document_count >= 10
91'''
92
93[[rule.threat]]
94framework = "MITRE ATT&CK"
95
96[[rule.threat.technique]]
97id = "T1485"
98name = "Data Destruction"
99reference = "https://attack.mitre.org/techniques/T1485/"
100
101[rule.threat.tactic]
102id = "TA0040"
103name = "Impact"
104reference = "https://attack.mitre.org/tactics/TA0040/"
105
106[[rule.threat]]
107framework = "MITRE ATT&CK"
108
109[[rule.threat.technique]]
110id = "T1020"
111name = "Automated Exfiltration"
112reference = "https://attack.mitre.org/techniques/T1020/"
113
114[[rule.threat.technique]]
115id = "T1567"
116name = "Exfiltration Over Web Service"
117reference = "https://attack.mitre.org/techniques/T1567/"
118
119[[rule.threat.technique.subtechnique]]
120id = "T1567.001"
121name = "Exfiltration to Code Repository"
122reference = "https://attack.mitre.org/techniques/T1567/001/"
123
124[rule.threat.tactic]
125id = "TA0010"
126name = "Exfiltration"
127reference = "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 High Number of Closed Pull Requests by User
This rule flags a single user rapidly closing many pull requests in a short window, a disruptive pattern that suppresses review history, delays releases, and masks unauthorized changes. An attacker with stolen maintainer access mass-closes pull requests across multiple repositories, then force-pushes branches and opens new pull requests that sidestep earlier review threads, making malicious edits appear routine amid churn.
Possible investigation steps
- Determine if the actor is a bot or sanctioned maintenance by confirming account type, scheduled workflows, and change advisories from repo/org owners.
- Open a sample of the closed PRs to review comments, labels, linked issues, and whether closure coincided with branch deletions, force-pushes, or unusual commit history in the target branches.
- Correlate the closure burst with audit events for permission changes, role assignments, repository settings edits, or protection rule modifications to detect potential sabotage.
- Validate the actor’s IPs, geolocation, and user agents against baselines and check for recent PAT creations, OAuth app grants, or SSO anomalies indicating credential theft.
- Identify whether closed PRs were immediately replaced by new PRs carrying similar diffs that bypass prior review threads and required checks, and verify branch protection remained enforced.
False positive analysis
- A maintainer or org-owned bot performs scheduled backlog hygiene, closing stale, duplicate, or superseded PRs across multiple repositories after a default branch rename or policy update, resulting in a high closure count from one account.
- During a planned migration or archival, a release manager closes PRs tied to deprecated branches and consolidates work into new targets, legitimately generating a burst of closures attributed to a single user.
Response and remediation
- Immediately contain by removing the user from teams with Triage/Write permissions on affected repositories, revoking their personal access tokens from Tokens & keys, and tightening branch protection by disallowing force-pushes and restricting who can push to main and release branches.
- Trigger escalation to Security Incident Response if closed pull requests span more than five repositories within one hour, coincide with branch deletions or forced pushes, or originate from a new user agent/IP, and disable the account at the identity provider while contacting GitHub Support.
- Eradicate impact by reopening legitimate PRs via each closed PR URL, using Restore branch or recreating the head branch from the last known commit SHA, and reapplying required labels and reviewers.
- Recover repository state by comparing diffs of closed PRs to any newly opened PRs by the same user, reverting unauthorized commits in target branches with git revert, and re-running required status checks before merging.
- Harden controls by enforcing branch protection rules (require two approvals, restrict who can dismiss reviews, require signed commits), enabling CODEOWNERS for critical paths, and turning off Allow deletions on default and release branches.
- Prevent recurrence by disabling classic PATs and requiring short-lived fine-grained PATs, revoking unusual OAuth app grants, mandating SSO with hardware-backed MFA, and installing a GitHub App/Action that notifies on PR closures with PR URLs, repos, and branches and requires a reason-coded label per policy.
References
Related rules
- Several Failed Protected Branch Force Pushes by User
- GitHub Exfiltration via High Number of Repository Clones by User
- Github Activity on a Private Repository from an Unusual IP
- AWS SNS Topic Message Publish by Rare User
- GitHub Repository Deleted