GitHub Token Clone Push or ZIP from Unusual ASN
Detects git clone, git push, or ZIP download authenticated with a classic PAT, fine-grained PAT, or OAuth access token from an autonomous system (ASN) not previously seen for that user in the last 7 days. The source IP is resolved to its ASN at query time, so address churn inside the same ISP or cloud provider does not produce new alerts. A token used from an unfamiliar ASN can indicate credential theft or use from an unauthorized location.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2026/11/03"
3integration = ["github"]
4maturity = "production"
5min_stack_comments = "ES|QL IP_LOCATION requires 9.5.0+."
6min_stack_version = "9.5.0"
7updated_date = "2026/11/03"
8
9[rule]
10author = ["Elastic"]
11description = """
12Detects git clone, git push, or ZIP download authenticated with a classic PAT, fine-grained PAT, or OAuth access token
13from an autonomous system (ASN) not previously seen for that user in the last 7 days. The source IP is resolved to its
14ASN at query time, so address churn inside the same ISP or cloud provider does not produce new alerts. A token used
15from an unfamiliar ASN can indicate credential theft or use from an unauthorized location.
16"""
17false_positives = [
18 """
19 CI, laptops, or travel networks using the same PAT or OAuth token from a new egress IP. Confirm the token owner and
20 whether the repositories are expected.
21 """,
22]
23from = "now-10085m"
24interval = "5m"
25language = "esql"
26license = "Elastic License v2"
27name = "GitHub Token Clone Push or ZIP from Unusual ASN"
28note = """## Triage and analysis
29
30### Investigating GitHub Token Clone Push or ZIP from Unusual ASN
31
32First-seen `user.name` + ASN on `git.clone`, `git.push`, or `repo.download_zip` over a 7-day look-back, using classic PATs, fine-grained PATs, or OAuth tokens. The IP is resolved to its ASN at query time (`IP_LOCATION` / GeoLite2-ASN), so this keys on the source network rather than the raw IP — address churn inside one ISP or cloud provider does not re-alert. Sibling production rule `GitHub Activity on a Private Repository from an Unusual ASN` applies the same ASN approach to clone/push on private repos.
33
34### Possible investigation steps
35
36- Identify `user.name`, the new ASN (`Esql.source_as_number` / `Esql.source_as_organization_name`), `github.programmatic_access_type`, the token (`Esql_priv.token_id_values`), and the repositories (`Esql.repo_values`).
37- Review the IPs and countries observed for the ASN (`Esql.source_ip_values`, `Esql.country_values`) and whether they match the token owner's known locations.
38- Correlate with `personal_access_token.access_granted` and collaborator adds from the same ASN or token.
39
40### False positive analysis
41
42- Developers travelling or switching between home, mobile, and corporate networks appear from a new ASN the first time each is used.
43- Shared automation tokens or CI appear once per cloud provider ASN; add exceptions for known automation identities if the volume is high.
44
45### Response and remediation
46
47- Revoke the token if the ASN is not the owner's. Review clones and ZIP downloads in the same window.
48"""
49references = [
50 "https://securitylabs.datadoghq.com/articles/mapping-out-your-unknown-threat-hunters-guide-to-github/",
51 "https://github.blog/news-insights/company-news/security-alert-stolen-oauth-user-tokens/",
52 "https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization",
53 "https://docs.github.com/en/rest/repos/contents#download-a-repository-archive-zip",
54]
55risk_score = 21
56rule_id = "f2ecac9a-7145-4587-95bc-70c1ee0fc7ba"
57severity = "low"
58tags = [
59 "Domain: Cloud",
60 "Domain: SaaS",
61 "Platform: GitHub",
62 "Use Case: Threat Detection",
63 "Tactic: Collection",
64 "Tactic: Exfiltration",
65 "Tactic: Initial Access",
66 "Tactic: Defense Evasion",
67 "Data Source: Github",
68 "Data Source: GitHub Audit Logs",
69 "Rule Type: ES|QL",
70 "Resources: Investigation Guide",
71 "Noise: High",
72]
73timestamp_override = "event.ingested"
74type = "esql"
75
76query = '''
77FROM logs-github.audit-*
78| WHERE event.dataset == "github.audit"
79 AND event.action IN ("git.clone", "git.push", "repo.download_zip")
80 AND github.programmatic_access_type IN ("OAuth access token", "Fine-grained personal access token", "Personal access token")
81 AND user.name IS NOT NULL AND github.actor_ip IS NOT NULL
82| IP_LOCATION asn = github.actor_ip WITH { "database_file": "GeoLite2-ASN.mmdb", "properties": ["asn", "organization_name"] }
83| IP_LOCATION geo = github.actor_ip WITH { "properties": ["country_iso_code"] }
84| WHERE asn.asn IS NOT NULL
85| STATS Esql.event_count = COUNT(*),
86 Esql.first_seen = MIN(@timestamp),
87 Esql.last_seen = MAX(@timestamp),
88 Esql.source_ip_values = VALUES(github.actor_ip),
89 Esql.country_values = VALUES(geo.country_iso_code),
90 Esql.repo_values = VALUES(github.repo),
91 Esql.event_action_values = VALUES(event.action),
92 Esql.programmatic_access_type_values = VALUES(github.programmatic_access_type),
93 Esql.user_agent_values = VALUES(github.user_agent),
94 Esql_priv.token_id_values = VALUES(github.token_id),
95 Esql_priv.hashed_token_values = VALUES(github.hashed_token)
96 BY user.name, asn.asn, asn.organization_name
97// The 7-day history is the rule look-back (from = now-10085m). A group is new when its first event
98// falls inside the current interval; the 10m bound allows for the 5m interval plus scheduling drift.
99// Novelty is keyed on user + ASN only, so a new IP inside an already-seen ASN does not re-alert.
100| WHERE DATE_DIFF("minute", Esql.first_seen, NOW()) <= 10
101| EVAL Esql.source_as_number = asn.asn,
102 Esql.source_as_organization_name = asn.organization_name
103| KEEP user.name, Esql.*, Esql_priv.*
104'''
105
106
107[[rule.threat]]
108framework = "MITRE ATT&CK"
109[[rule.threat.technique]]
110id = "T1567"
111name = "Exfiltration Over Web Service"
112reference = "https://attack.mitre.org/techniques/T1567/"
113[[rule.threat.technique.subtechnique]]
114id = "T1567.001"
115name = "Exfiltration to Code Repository"
116reference = "https://attack.mitre.org/techniques/T1567/001/"
117
118
119[rule.threat.tactic]
120id = "TA0010"
121name = "Exfiltration"
122reference = "https://attack.mitre.org/tactics/TA0010/"
123[[rule.threat]]
124framework = "MITRE ATT&CK"
125[[rule.threat.technique]]
126id = "T1213"
127name = "Data from Information Repositories"
128reference = "https://attack.mitre.org/techniques/T1213/"
129[[rule.threat.technique.subtechnique]]
130id = "T1213.003"
131name = "Code Repositories"
132reference = "https://attack.mitre.org/techniques/T1213/003/"
133
134
135
136[rule.threat.tactic]
137id = "TA0009"
138name = "Collection"
139reference = "https://attack.mitre.org/tactics/TA0009/"
140[[rule.threat]]
141framework = "MITRE ATT&CK"
142[[rule.threat.technique]]
143id = "T1078"
144name = "Valid Accounts"
145reference = "https://attack.mitre.org/techniques/T1078/"
146[[rule.threat.technique.subtechnique]]
147id = "T1078.004"
148name = "Cloud Accounts"
149reference = "https://attack.mitre.org/techniques/T1078/004/"
150
151
152
153[rule.threat.tactic]
154id = "TA0001"
155name = "Initial Access"
156reference = "https://attack.mitre.org/tactics/TA0001/"
157[[rule.threat]]
158framework = "MITRE ATT&CK"
159[[rule.threat.technique]]
160id = "T1550"
161name = "Use Alternate Authentication Material"
162reference = "https://attack.mitre.org/techniques/T1550/"
163[[rule.threat.technique.subtechnique]]
164id = "T1550.001"
165name = "Application Access Token"
166reference = "https://attack.mitre.org/techniques/T1550/001/"
167
168
169[rule.threat.tactic]
170id = "TA0005"
171name = "Defense Evasion"
172reference = "https://attack.mitre.org/tactics/TA0005/"
173
174[rule.investigation_fields]
175field_names = [
176 "user.name",
177 "Esql.source_ip_values",
178 "Esql.country_values",
179 "Esql.source_as_number",
180 "Esql.source_as_organization_name",
181 "Esql.event_count",
182 "Esql.repo_values",
183 "Esql.event_action_values",
184 "Esql.programmatic_access_type_values",
185 "Esql.user_agent_values",
186 "Esql_priv.token_id_values",
187]
Triage and analysis
Investigating GitHub Token Clone Push or ZIP from Unusual ASN
First-seen user.name + ASN on git.clone, git.push, or repo.download_zip over a 7-day look-back, using classic PATs, fine-grained PATs, or OAuth tokens. The IP is resolved to its ASN at query time (IP_LOCATION / GeoLite2-ASN), so this keys on the source network rather than the raw IP — address churn inside one ISP or cloud provider does not re-alert. Sibling production rule GitHub Activity on a Private Repository from an Unusual ASN applies the same ASN approach to clone/push on private repos.
Possible investigation steps
- Identify
user.name, the new ASN (Esql.source_as_number/Esql.source_as_organization_name),github.programmatic_access_type, the token (Esql_priv.token_id_values), and the repositories (Esql.repo_values). - Review the IPs and countries observed for the ASN (
Esql.source_ip_values,Esql.country_values) and whether they match the token owner's known locations. - Correlate with
personal_access_token.access_grantedand collaborator adds from the same ASN or token.
False positive analysis
- Developers travelling or switching between home, mobile, and corporate networks appear from a new ASN the first time each is used.
- Shared automation tokens or CI appear once per cloud provider ASN; add exceptions for known automation identities if the volume is high.
Response and remediation
- Revoke the token if the ASN is not the owner's. Review clones and ZIP downloads in the same window.
References
Related rules
- GitHub Webhook Created or Configuration Changed
- GitHub Activity on a Private Repository from an Unusual ASN
- GitHub API Sensitive Resource Enumeration
- GitHub OAuth App Access Restrictions Disabled
- GitHub SAML SSO Disabled