OpenAI ChatGPT Enterprise Impossible Travel Login

Detects successful ChatGPT Enterprise logins for the same user email from source IPs whose geo-locations are separated by at least 1,000 km, with implied travel faster than 800 km/h, within a 24-hour window. That pattern can indicate account sharing, VPN or proxy egress mismatches, or an adversary authenticating from a geography far from the legitimate user's baseline.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/10/06"
  3integration = ["openai_chatgpt_enterprise"]
  4maturity = "production"
  5min_stack_comments = "ES|QL FIRST/LAST aggregations and st_distance require 9.4.0+."
  6min_stack_version = "9.4.0"
  7updated_date = "2026/10/06"
  8
  9[rule]
 10author = ["Elastic"]
 11description = """
 12Detects successful ChatGPT Enterprise logins for the same user email from source IPs whose geo-locations are
 13separated by at least 1,000 km, with implied travel faster than 800 km/h, within a 24-hour window. That pattern can
 14indicate account sharing, VPN or proxy egress mismatches, or an adversary authenticating from a geography far from
 15the legitimate user's baseline.
 16"""
 17false_positives = [
 18    "Users on VPN or proxy egress that geo-resolves through a region distant from the user physical location.",
 19    "Mobile clients on cellular networks that peer through regional hubs may geo-resolve differently than the user location.",
 20]
 21from = "now-24h"
 22interval = "1h"
 23language = "esql"
 24license = "Elastic License v2"
 25name = "OpenAI ChatGPT Enterprise Impossible Travel Login"
 26note = """## Triage and analysis
 27
 28### Investigating OpenAI ChatGPT Enterprise Impossible Travel Login
 29
 30Two successful ChatGPT Enterprise logins for the same `user.email` appear too far apart, too quickly, to be physical travel. That can indicate account sharing, VPN or proxy geo mismatch, or adversary authentication from a distant egress.
 31
 32Note: this detection compares the first and last successful login locations in the lookback window. An A-B-A return to the original location can under-report distance, so always timeline-sort raw authentication events before concluding.
 33
 34Escalate when the distant IP or user agent is unfamiliar, the user denies travel or VPN use, or privileged activity follows the distant sign-in. Close as a false positive when the user confirms known travel, split home and office VPN use, or geo-database noise with matching corporate clients.
 35
 36#### Possible investigation steps
 37
 38- Review `Esql.distance_km`, `Esql.travel_kmh`, `Esql.window_minutes`, and the geo and IP value lists.
 39- Timeline-sort successful logins and compare each `source.ip`, geo region, and `user_agent.original`.
 40- Pair with multi-country authentication activity or privileged audit actions after the distant sign-in.
 41- Contact the user only after geo and user-agent triage.
 42
 43### False positive analysis
 44
 45- VPN pools and anycast or satellite geo misplacements commonly inflate implied travel speed. Corroborate with ASN and user agent before escalating.
 46
 47### Response and remediation
 48
 49- On suspected compromise: revoke sessions, reset credentials and MFA, and review admin or data-access activity after the distant sign-in.
 50"""
 51references = [
 52    "https://www.elastic.co/docs/reference/integrations/openai_chatgpt_enterprise",
 53    "https://platform.openai.com/docs/compliance",
 54]
 55risk_score = 73
 56rule_id = "b9e509ba-ca0f-450a-8577-0b0a1098d598"
 57setup = """## Setup
 58
 59This rule requires the OpenAI ChatGPT Enterprise integration to collect compliance logs into Elasticsearch.
 60
 61https://www.elastic.co/docs/reference/integrations/openai_chatgpt_enterprise
 62"""
 63severity = "high"
 64tags = [
 65    "Domain: GenAI",
 66    "Platform: OpenAI",
 67    "Data Source: OpenAI ChatGPT Enterprise",
 68    "Service: OpenAI ChatGPT Enterprise",
 69    "Use Case: Threat Detection",
 70    "Resources: Investigation Guide",
 71    "Rule Type: ES|QL",
 72    "Domain: Identity",
 73    "Use Case: Identity and Access Audit",
 74    "Tactic: Initial Access",
 75    "Mitre Atlas: AML.T0012",
 76    "Threat: Impossible Travel",
 77]
 78timestamp_override = "event.ingested"
 79type = "esql"
 80
 81query = '''
 82from logs-openai_chatgpt_enterprise.auth_log-*
 83| where
 84    data_stream.dataset == "openai_chatgpt_enterprise.auth_log" and
 85    event.action in ("login_success", "native_login_success") and
 86    user.email is not null and
 87    source.ip is not null and
 88    source.geo.location is not null and
 89    source.geo.country_iso_code is not null
 90| eval
 91    Esql.source_geo_lat = st_y(source.geo.location),
 92    Esql.source_geo_lon = st_x(source.geo.location)
 93| where Esql.source_geo_lat is not null and Esql.source_geo_lon is not null
 94| stats
 95    Esql.first_lat = first(Esql.source_geo_lat, @timestamp),
 96    Esql.first_lon = first(Esql.source_geo_lon, @timestamp),
 97    Esql.last_lat = last(Esql.source_geo_lat, @timestamp),
 98    Esql.last_lon = last(Esql.source_geo_lon, @timestamp),
 99    Esql.event_count = count(*),
100    Esql.country_count = count_distinct(source.geo.country_iso_code),
101    Esql.event_action_values = values(event.action),
102    Esql.source_ip_values = values(source.ip),
103    Esql.source_geo_country_iso_code_values = values(source.geo.country_iso_code),
104    Esql.source_geo_region_name_values = values(source.geo.region_name),
105    Esql.source_geo_city_name_values = values(source.geo.city_name),
106    Esql.user_agent_original_values = values(user_agent.original),
107    Esql.timestamp_first_seen = min(@timestamp),
108    Esql.timestamp_last_seen = max(@timestamp)
109  by user.email
110| where Esql.event_count >= 2 and Esql.country_count >= 2
111| eval
112    Esql.p1 = to_geopoint(concat("POINT(", to_string(Esql.first_lon), " ", to_string(Esql.first_lat), ")")),
113    Esql.p2 = to_geopoint(concat("POINT(", to_string(Esql.last_lon), " ", to_string(Esql.last_lat), ")"))
114| eval
115    Esql.distance_km = round(st_distance(Esql.p1, Esql.p2) / 1000.0, 0),
116    Esql.window_minutes = date_diff("minute", Esql.timestamp_first_seen, Esql.timestamp_last_seen),
117    Esql.travel_kmh = case(Esql.window_minutes > 0, round(Esql.distance_km * 60.0 / Esql.window_minutes, 0), null)
118| where Esql.distance_km >= 1000 and Esql.travel_kmh >= 800
119| keep user.email, Esql.*
120'''
121
122
123[[rule.threat]]
124framework = "MITRE ATT&CK"
125[[rule.threat.technique]]
126id = "T1078"
127name = "Valid Accounts"
128reference = "https://attack.mitre.org/techniques/T1078/"
129[[rule.threat.technique.subtechnique]]
130id = "T1078.004"
131name = "Cloud Accounts"
132reference = "https://attack.mitre.org/techniques/T1078/004/"
133
134
135[rule.threat.tactic]
136id = "TA0001"
137name = "Initial Access"
138reference = "https://attack.mitre.org/tactics/TA0001/"
139
140[[rule.threat_mappings]]
141framework = "MITRE ATLAS"
142version = "2026.08"
143[[rule.threat_mappings.threat]]
144framework = "MITRE ATLAS"
145[[rule.threat_mappings.threat.technique]]
146id = "AML.T0012"
147name = "Valid Accounts"
148reference = "https://atlas.mitre.org/techniques/AML.T0012/"
149
150
151[rule.threat_mappings.threat.tactic]
152id = "AML.TA0004"
153name = "Initial Access"
154reference = "https://atlas.mitre.org/tactics/AML.TA0004/"
155
156[rule.alert_suppression]
157group_by = ["user.email"]
158missing_fields_strategy = "suppress"
159
160[rule.alert_suppression.duration]
161unit = "h"
162value = 24
163
164[rule.investigation_fields]
165field_names = [
166    "user.email",
167    "Esql.distance_km",
168    "Esql.travel_kmh",
169    "Esql.window_minutes",
170    "Esql.event_count",
171    "Esql.country_count",
172    "Esql.source_ip_values",
173    "Esql.source_geo_country_iso_code_values",
174    "Esql.source_geo_region_name_values",
175    "Esql.source_geo_city_name_values",
176    "Esql.user_agent_original_values",
177    "Esql.timestamp_first_seen",
178    "Esql.timestamp_last_seen",
179]

Triage and analysis

Investigating OpenAI ChatGPT Enterprise Impossible Travel Login

Two successful ChatGPT Enterprise logins for the same user.email appear too far apart, too quickly, to be physical travel. That can indicate account sharing, VPN or proxy geo mismatch, or adversary authentication from a distant egress.

Note: this detection compares the first and last successful login locations in the lookback window. An A-B-A return to the original location can under-report distance, so always timeline-sort raw authentication events before concluding.

Escalate when the distant IP or user agent is unfamiliar, the user denies travel or VPN use, or privileged activity follows the distant sign-in. Close as a false positive when the user confirms known travel, split home and office VPN use, or geo-database noise with matching corporate clients.

Possible investigation steps

  • Review Esql.distance_km, Esql.travel_kmh, Esql.window_minutes, and the geo and IP value lists.
  • Timeline-sort successful logins and compare each source.ip, geo region, and user_agent.original.
  • Pair with multi-country authentication activity or privileged audit actions after the distant sign-in.
  • Contact the user only after geo and user-agent triage.

False positive analysis

  • VPN pools and anycast or satellite geo misplacements commonly inflate implied travel speed. Corroborate with ASN and user agent before escalating.

Response and remediation

  • On suspected compromise: revoke sessions, reset credentials and MFA, and review admin or data-access activity after the distant sign-in.

References

Related rules

to-top