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_granted and 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

to-top