GitHub API Failed Access Burst
Detects a burst of failed GitHub API requests (HTTP 4xx/5xx) recorded as api.request, where a single non-bot actor
and source IP generate a high volume of failures spread across many distinct API routes within a short window. An
adversary probing private repositories, secrets, or admin routes produces clusters of 401/403/404 responses across many
endpoints, whereas broken automation usually hammers a small number of routes. Grouping by actor and source IP and
requiring both a high failure count and a high number of distinct routes separates broad enumeration from a single
misconfigured integration. Requires API Request Events on GitHub audit log streaming.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2026/11/02"
3integration = ["github"]
4maturity = "production"
5updated_date = "2026/11/02"
6
7[rule]
8author = ["Elastic"]
9description = """
10Detects a burst of failed GitHub API requests (HTTP 4xx/5xx) recorded as `api.request`, where a single non-bot actor
11and source IP generate a high volume of failures spread across many distinct API routes within a short window. An
12adversary probing private repositories, secrets, or admin routes produces clusters of 401/403/404 responses across many
13endpoints, whereas broken automation usually hammers a small number of routes. Grouping by actor and source IP and
14requiring both a high failure count and a high number of distinct routes separates broad enumeration from a single
15misconfigured integration. Requires API Request Events on GitHub audit log streaming.
16"""
17false_positives = [
18 """
19 Broken automation, expired tokens, or inventory tools hitting renamed repositories. Review the routes and whether
20 the token is expected.
21 """,
22]
23from = "now-11m"
24interval = "10m"
25language = "esql"
26license = "Elastic License v2"
27name = "GitHub API Failed Access Burst"
28note = """## Triage and analysis
29
30> **Disclaimer**:
31> 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.
32
33### Investigating GitHub API Failed Access Burst
34
35Failed `api.request` status codes often precede successful enumeration after the attacker finds a valid path or token. This rule groups failed requests by `user.name` and `github.actor_ip` and fires when a single actor/IP produces at least 15 failures spread across 10 or more distinct API routes in the window, which distinguishes broad enumeration from a single broken integration hammering one endpoint.
36
37### Possible investigation steps
38
39- Review the alerting `user.name` and `github.actor_ip`, and inspect `Esql.distinct_route_count` and `Esql.github_route_values` to see which routes were probed.
40- Review `Esql.github_status_code_values` to confirm the mix of 401/403/404 responses typical of access probing.
41- Pivot on `Esql.github_token_id_values` and `Esql.github_programmatic_access_type_values` to identify the token and access method used.
42- Check `Esql.distinct_repo_count` and `Esql.github_org_values` to gauge how broadly the actor is probing across repositories and organizations.
43- Look for a later run of 200s on secrets, trees, or contents from the same actor or token, which indicates successful enumeration.
44
45### False positive analysis
46
47- CI or inventory tooling using a token that lacks repo scope against private repositories. These typically hit a small number of routes, so review `Esql.distinct_route_count` and the route values before dismissing.
48- Expired or rotated tokens causing widespread 401s from a known automation IP.
49
50### Response and remediation
51
52- Revoke the token if the actor is not a known automation identity. Tighten org PAT policies.
53- Block or monitor the source IP if the activity is clearly reconnaissance.
54- Review whether any probed route later returned a success for the same actor.
55"""
56references = [
57 "https://securitylabs.datadoghq.com/articles/mapping-out-your-unknown-threat-hunters-guide-to-github/",
58 "https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise#enabling-api-request-events",
59]
60risk_score = 21
61rule_id = "45e23f23-eb7d-460d-98ee-81c9925efd4e"
62setup = """## Setup
63
64Requires the Elastic GitHub integration, GitHub Enterprise audit log streaming, and **Enable API Request Events**.
65"""
66severity = "low"
67tags = [
68 "Domain: Cloud",
69 "Domain: SaaS",
70 "Platform: GitHub",
71 "Use Case: Threat Detection",
72 "Tactic: Discovery",
73 "Data Source: Github",
74 "Data Source: GitHub Audit Logs",
75 "Rule Type: ES|QL",
76 "Resources: Investigation Guide",
77]
78timestamp_override = "event.ingested"
79type = "esql"
80
81query = '''
82from logs-github.audit-* metadata _id, _index, _version
83| where
84 data_stream.dataset == "github.audit" and event.action == "api.request" and github.status_code >= 400
85 and (github.actor_is_bot == false or github.actor_is_bot is null)
86| stats
87 Esql.event_count = COUNT(*),
88 Esql.distinct_route_count = COUNT_DISTINCT(github.route),
89 Esql.distinct_repo_count = COUNT_DISTINCT(github.repo),
90 Esql.github_route_values = values(github.route),
91 Esql.github_status_code_values = values(github.status_code),
92 Esql.github_org_values = values(github.org),
93 Esql.github_programmatic_access_type_values = values(github.programmatic_access_type),
94 Esql.github_user_agent_values = values(github.user_agent),
95 Esql.agent_id_values = values(agent.id),
96 Esql.data_stream_dataset_values = values(data_stream.dataset),
97 Esql.data_stream_namespace_values = values(data_stream.namespace),
98 Esql.github_token_id_values = values(github.token_id)
99 by user.name, github.actor_ip
100| keep user.name, github.actor_ip, Esql.*
101| where Esql.event_count >= 15 and Esql.distinct_route_count >= 10
102'''
103
104
105[[rule.threat]]
106framework = "MITRE ATT&CK"
107[[rule.threat.technique]]
108id = "T1526"
109name = "Cloud Service Discovery"
110reference = "https://attack.mitre.org/techniques/T1526/"
111
112
113[rule.threat.tactic]
114id = "TA0007"
115name = "Discovery"
116reference = "https://attack.mitre.org/tactics/TA0007/"
117
118[rule.investigation_fields]
119field_names = [
120 "user.name",
121 "github.actor_ip",
122 "Esql.event_count",
123 "Esql.distinct_route_count",
124 "Esql.distinct_repo_count",
125 "Esql.github_route_values",
126 "Esql.github_status_code_values",
127 "Esql.github_org_values",
128 "Esql.github_programmatic_access_type_values",
129 "Esql.github_user_agent_values",
130 "Esql.github_token_id_values",
131]
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 GitHub API Failed Access Burst
Failed api.request status codes often precede successful enumeration after the attacker finds a valid path or token. This rule groups failed requests by user.name and github.actor_ip and fires when a single actor/IP produces at least 15 failures spread across 10 or more distinct API routes in the window, which distinguishes broad enumeration from a single broken integration hammering one endpoint.
Possible investigation steps
- Review the alerting
user.nameandgithub.actor_ip, and inspectEsql.distinct_route_countandEsql.github_route_valuesto see which routes were probed. - Review
Esql.github_status_code_valuesto confirm the mix of 401/403/404 responses typical of access probing. - Pivot on
Esql.github_token_id_valuesandEsql.github_programmatic_access_type_valuesto identify the token and access method used. - Check
Esql.distinct_repo_countandEsql.github_org_valuesto gauge how broadly the actor is probing across repositories and organizations. - Look for a later run of 200s on secrets, trees, or contents from the same actor or token, which indicates successful enumeration.
False positive analysis
- CI or inventory tooling using a token that lacks repo scope against private repositories. These typically hit a small number of routes, so review
Esql.distinct_route_countand the route values before dismissing. - Expired or rotated tokens causing widespread 401s from a known automation IP.
Response and remediation
- Revoke the token if the actor is not a known automation identity. Tighten org PAT policies.
- Block or monitor the source IP if the activity is clearly reconnaissance.
- Review whether any probed route later returned a success for the same actor.
References
Related rules
- GitHub SAML SSO Disabled
- High Number of Protected Branch Force Pushes by User
- GitHub Activity on a Private Repository from an Unusual ASN
- New GitHub Self Hosted Action Runner
- GitHub OAuth Application Authorized