Anthropic Impossible Travel Login

Detects successful Anthropic magic link or SSO sign-ins for the same user email from source IP addresses whose query-time geo-locations (via IP_LOCATION) 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/09/15"
  3integration = ["anthropic"]
  4maturity = "production"
  5min_stack_comments = "ES|QL IP_LOCATION requires 9.5.0+. FIRST/LAST aggregations and st_distance require 9.4.0+."
  6min_stack_version = "9.5.0"
  7updated_date = "2026/09/25"
  8
  9[rule]
 10author = ["Elastic"]
 11description = """
 12Detects successful Anthropic magic link or SSO sign-ins for the same user email from source IP addresses whose
 13query-time geo-locations (via IP_LOCATION) are separated by at least 1,000 km, with implied travel faster than 800 km/h,
 14within a 24-hour window. That pattern can indicate account sharing, VPN or proxy egress mismatches, or an adversary
 15authenticating from a geography far from the legitimate user's baseline.
 16"""
 17false_positives = [
 18    """
 19    Users on VPN or proxy egress that geo-resolves through a region distant from the user's physical location. Mobile
 20    clients on cellular networks that peer through regional hubs may geo-resolve differently than the user's location.
 21    """,
 22]
 23from = "now-24h"
 24interval = "1h"
 25language = "esql"
 26license = "Elastic License v2"
 27name = "Anthropic Impossible Travel Login"
 28note = """## Triage and analysis
 29
 30### Investigating Anthropic Impossible Travel Login
 31
 32Two successful magic-link or SSO sign-ins for the same `user.email` appear too far apart, too quickly, to be physical
 33travel. That can be account sharing, VPN/proxy geo mismatch, or adversary authentication from a distant egress.
 34
 35Note: an A-B-A return to the first location may not alert (first/last coords can be close) — always sort raw Timeline
 36auth events before concluding.
 37
 38Unauthorized / escalate when the distant IP/UA is unfamiliar, the user denies travel/VPN use, or failures / SSO
 39weakening sit nearby. Close as FP when the user confirms known travel, split home/office VPN, or geo DB noise with
 40matching corporate ASN/UA.
 41
 42#### Possible investigation steps
 43
 44- Read `Esql.distance_km`, `Esql.travel_kmh`, `Esql.window_minutes`, and the geo/IP value lists — high speed with a
 45  country change is stronger than a same-region VPN hop.
 46- Timeline-sort successful logins; compare each `source.ip`, geo, and `user_agent.original`. A browser→curl/python
 47  shift on the distant hop is higher priority than two similar corporate browsers.
 48- Pair with **Anthropic Multiple Authentication Failures** or SSO / magic-link second-factor changes when takeover is
 49  plausible.
 50- Ask the user only after geo/UA triage: expected travel or VPN egress vs unrecognized location.
 51
 52### False positive analysis
 53
 54- VPN pools and anycast/satellite geo misplacements commonly inflate implied speed — corroborate ASN and UA first.
 55
 56### Response and remediation
 57
 58- On suspected compromise: revoke sessions, reset credentials/MFA, and review admin or data-access activity after the
 59  distant sign-in.
 60"""
 61references = ["https://platform.claude.com/docs/en/api/compliance/activities/list"]
 62risk_score = 73
 63rule_id = "e89e4744-039e-4290-9835-63ea42fe531e"
 64severity = "high"
 65tags = [
 66    "Domain: GenAI",
 67    "Domain: Identity",
 68    "Platform: Anthropic",
 69    "Data Source: Anthropic Audit Logs",
 70    "Use Case: Identity and Access Audit",
 71    "Use Case: Threat Detection",
 72    "Resources: Investigation Guide",
 73    "Rule Type: ES|QL",
 74    "Tactic: Initial Access",
 75    "Mitre Atlas: AML.T0012",
 76]
 77timestamp_override = "event.ingested"
 78type = "esql"
 79
 80query = '''
 81from logs-anthropic.audit-*
 82| where
 83    data_stream.dataset == "anthropic.audit" and
 84    mv_contains(event.category, "authentication") and
 85    event.outcome == "success" and
 86    event.action in ("magic_link_login_succeeded", "sso_login_succeeded") and
 87    user.email is not null and
 88    source.ip is not null
 89| IP_LOCATION geo = source.ip with { "properties": ["country_name", "city_name", "location"] }
 90| eval
 91    Esql.source_geo_lat = st_y(geo.location),
 92    Esql.source_geo_lon = st_x(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.event_id_values = values(event.id),
101    Esql.event_action_values = values(event.action),
102    Esql.source_ip_values = values(source.ip),
103    Esql.source_geo_country_name_values = values(geo.country_name),
104    Esql.source_geo_city_name_values = values(geo.city_name),
105    Esql.user_agent_original_values = values(user_agent.original),
106    Esql.anthropic_audit_actor_type_values = values(anthropic.audit.actor.type),
107    Esql.timestamp_first_seen = min(@timestamp),
108    Esql.timestamp_last_seen = max(@timestamp)
109  by user.email
110| where Esql.event_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
136[rule.threat.tactic]
137id = "TA0001"
138name = "Initial Access"
139reference = "https://attack.mitre.org/tactics/TA0001/"
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.investigation_fields]
161field_names = [
162    "user.email",
163    "Esql.distance_km",
164    "Esql.travel_kmh",
165    "Esql.window_minutes",
166    "Esql.event_count",
167    "Esql.event_id_values",
168    "Esql.event_action_values",
169    "Esql.source_ip_values",
170    "Esql.source_geo_country_name_values",
171    "Esql.source_geo_city_name_values",
172    "Esql.first_lat",
173    "Esql.first_lon",
174    "Esql.last_lat",
175    "Esql.last_lon",
176    "Esql.user_agent_original_values",
177    "Esql.anthropic_audit_actor_type_values",
178    "Esql.timestamp_first_seen",
179    "Esql.timestamp_last_seen",
180]
181
182[rule.alert_suppression.duration]
183unit = "h"
184value = 24

Triage and analysis

Investigating Anthropic Impossible Travel Login

Two successful magic-link or SSO sign-ins for the same user.email appear too far apart, too quickly, to be physical travel. That can be account sharing, VPN/proxy geo mismatch, or adversary authentication from a distant egress.

Note: an A-B-A return to the first location may not alert (first/last coords can be close) — always sort raw Timeline auth events before concluding.

Unauthorized / escalate when the distant IP/UA is unfamiliar, the user denies travel/VPN use, or failures / SSO weakening sit nearby. Close as FP when the user confirms known travel, split home/office VPN, or geo DB noise with matching corporate ASN/UA.

Possible investigation steps

  • Read Esql.distance_km, Esql.travel_kmh, Esql.window_minutes, and the geo/IP value lists — high speed with a country change is stronger than a same-region VPN hop.
  • Timeline-sort successful logins; compare each source.ip, geo, and user_agent.original. A browser→curl/python shift on the distant hop is higher priority than two similar corporate browsers.
  • Pair with Anthropic Multiple Authentication Failures or SSO / magic-link second-factor changes when takeover is plausible.
  • Ask the user only after geo/UA triage: expected travel or VPN egress vs unrecognized location.

False positive analysis

  • VPN pools and anycast/satellite geo misplacements commonly inflate implied speed — corroborate ASN and UA first.

Response and remediation

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

References

Related rules

to-top