Anthropic Session Reuse Impossible Travel

Detects successful Anthropic audit activity for the same user email from source IP addresses whose query-time geo-locations (via IP_LOCATION) span at least two countries, are separated by at least 500 km, and imply travel faster than 800 km/h within a short (~15-minute) lookback. Unlike login-only impossible travel, this rule covers any successful user-actor activity and can surface session cookie replay or concurrent session reuse when no new authentication events appear.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/09/16"
  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/21"
  8
  9[rule]
 10author = ["Elastic"]
 11description = """
 12Detects successful Anthropic audit activity for the same user email from source IP addresses whose query-time
 13geo-locations (via IP_LOCATION) span at least two countries, are separated by at least 500 km, and imply travel
 14faster than 800 km/h within a short (~15-minute) lookback. Unlike login-only impossible travel, this rule covers any
 15successful user-actor activity and can surface session cookie replay or concurrent session reuse when no new
 16authentication events appear.
 17"""
 18false_positives = [
 19    """
 20    Users on VPN or proxy egress that geo-resolves through a region distant from the user's physical location. Mobile
 21    clients on cellular networks that peer through regional hubs may geo-resolve differently than the user's location.
 22    """,
 23    """
 24    Cloud egress, split-tunnel, or dual-homed clients that present different public IPs for concurrent Anthropic
 25    sessions (for example browser and API tooling) can look like impossible travel when both resolve far apart.
 26    """,
 27]
 28from = "now-16m"
 29interval = "5m"
 30language = "esql"
 31license = "Elastic License v2"
 32name = "Anthropic Session Reuse Impossible Travel"
 33note = """## Triage and analysis
 34
 35### Investigating Anthropic Session Reuse Impossible Travel
 36
 37Successful `user_actor` activity for the same email spans distant countries too quickly for travel. Unlike login-only
 38impossible travel, this covers any successful audit action — useful when cookie replay or concurrent sessions produce
 39distant activity without a matching login.
 40
 41A-B-A returns may not alert; sort raw Timeline events. No nearby `magic_link_login_succeeded` /
 42`sso_login_succeeded` with distant successful non-login actions strengthens a session-reuse hypothesis.
 43
 44Escalate when countries differ, UAs diverge (browser vs curl/python), login events are absent, or admin/export
 45actions appear on the distant IP. Close as FP for documented dual-homed VPN/cloud egress with consistent corporate
 46UAs.
 47
 48#### Possible investigation steps
 49
 50- Use `Esql.country_count`, distance/speed, and `Esql.event_action_values` to see whether the distant hop was
 51  privileged (exports, role changes) vs routine chat views.
 52- Timeline-sort events; compare IP, geo, UA, and action. Concurrent browser + tooling on far-apart cloud egress often
 53  explains FP; identical session cookies across continents without login does not.
 54- Check for nearby logins. Absence + privileged distant actions → treat as cookie replay until proven otherwise.
 55- Pair with **Anthropic Impossible Travel Login** / auth-failure rules when takeover is suspected.
 56
 57### False positive analysis
 58
 59- Split-tunnel / dual-homed clients (browser + API tooling) and VPN geo noise are the main FPs — require ASN/UA
 60  corroboration.
 61
 62### Response and remediation
 63
 64- On suspected replay/compromise: revoke sessions, reset credentials/MFA, hunt concurrent sessions, and review
 65  compliance/export and role changes for the same `user.email` after the distant events.
 66"""
 67references = [
 68    "https://platform.claude.com/docs/en/api/compliance/activities/list",
 69]
 70risk_score = 73
 71rule_id = "291fc461-0dc8-4583-be5e-929467b3dd2e"
 72severity = "high"
 73tags = [
 74    "Domain: GenAI",
 75    "Domain: Identity",
 76    "Platform: Anthropic",
 77    "Data Source: Anthropic Audit Logs",
 78    "Use Case: Identity and Access Audit",
 79    "Use Case: Threat Detection",
 80    "Resources: Investigation Guide",
 81    "Rule Type: ES|QL",
 82    "Tactic: Credential Access",
 83    "Tactic: Initial Access",
 84]
 85timestamp_override = "event.ingested"
 86type = "esql"
 87
 88query = '''
 89from logs-anthropic.audit-*
 90| where
 91    data_stream.dataset == "anthropic.audit" and
 92    event.outcome == "success" and
 93    anthropic.audit.actor.type == "user_actor" and
 94    user.email is not null and
 95    source.ip is not null
 96| IP_LOCATION geo = source.ip with { "properties": ["country_name", "city_name", "location"] }
 97| eval
 98    Esql.source_geo_lat = st_y(geo.location),
 99    Esql.source_geo_lon = st_x(geo.location)
100| where Esql.source_geo_lat is not null and Esql.source_geo_lon is not null
101| stats
102    Esql.first_lat = first(Esql.source_geo_lat, @timestamp),
103    Esql.first_lon = first(Esql.source_geo_lon, @timestamp),
104    Esql.last_lat = last(Esql.source_geo_lat, @timestamp),
105    Esql.last_lon = last(Esql.source_geo_lon, @timestamp),
106    Esql.event_count = count(*),
107    Esql.country_count = count_distinct(geo.country_name),
108    Esql.event_id_values = values(event.id),
109    Esql.event_action_values = values(event.action),
110    Esql.source_ip_values = values(source.ip),
111    Esql.source_geo_country_name_values = values(geo.country_name),
112    Esql.source_geo_city_name_values = values(geo.city_name),
113    Esql.user_agent_original_values = values(user_agent.original),
114    Esql.anthropic_audit_actor_type_values = values(anthropic.audit.actor.type),
115    Esql.timestamp_first_seen = min(@timestamp),
116    Esql.timestamp_last_seen = max(@timestamp)
117  by user.email
118| where Esql.event_count >= 2 and Esql.country_count >= 2
119| eval
120    Esql.p1 = to_geopoint(concat("POINT(", to_string(Esql.first_lon), " ", to_string(Esql.first_lat), ")")),
121    Esql.p2 = to_geopoint(concat("POINT(", to_string(Esql.last_lon), " ", to_string(Esql.last_lat), ")"))
122| eval
123    Esql.distance_km = round(st_distance(Esql.p1, Esql.p2) / 1000.0, 0),
124    Esql.window_minutes = date_diff("minute", Esql.timestamp_first_seen, Esql.timestamp_last_seen),
125    Esql.travel_kmh = case(Esql.window_minutes > 0, round(Esql.distance_km * 60.0 / Esql.window_minutes, 0), null)
126| where Esql.distance_km >= 500 and Esql.travel_kmh >= 800
127| keep user.email, Esql.*
128'''
129
130
131[[rule.threat]]
132framework = "MITRE ATT&CK"
133[[rule.threat.technique]]
134id = "T1539"
135name = "Steal Web Session Cookie"
136reference = "https://attack.mitre.org/techniques/T1539/"
137
138
139[rule.threat.tactic]
140id = "TA0006"
141name = "Credential Access"
142reference = "https://attack.mitre.org/tactics/TA0006/"
143
144[[rule.threat]]
145framework = "MITRE ATT&CK"
146[[rule.threat.technique]]
147id = "T1078"
148name = "Valid Accounts"
149reference = "https://attack.mitre.org/techniques/T1078/"
150[[rule.threat.technique.subtechnique]]
151id = "T1078.004"
152name = "Cloud Accounts"
153reference = "https://attack.mitre.org/techniques/T1078/004/"
154
155
156[rule.threat.tactic]
157id = "TA0001"
158name = "Initial Access"
159reference = "https://attack.mitre.org/tactics/TA0001/"
160
161[rule.alert_suppression]
162group_by = ["user.email"]
163duration = {value = 15, unit = "m"}
164missing_fields_strategy = "suppress"
165
166[rule.investigation_fields]
167field_names = [
168    "user.email",
169    "Esql.distance_km",
170    "Esql.travel_kmh",
171    "Esql.window_minutes",
172    "Esql.country_count",
173    "Esql.event_count",
174    "Esql.event_id_values",
175    "Esql.event_action_values",
176    "Esql.source_ip_values",
177    "Esql.source_geo_country_name_values",
178    "Esql.source_geo_city_name_values",
179    "Esql.first_lat",
180    "Esql.first_lon",
181    "Esql.last_lat",
182    "Esql.last_lon",
183    "Esql.user_agent_original_values",
184    "Esql.anthropic_audit_actor_type_values",
185    "Esql.timestamp_first_seen",
186    "Esql.timestamp_last_seen",
187]

Triage and analysis

Investigating Anthropic Session Reuse Impossible Travel

Successful user_actor activity for the same email spans distant countries too quickly for travel. Unlike login-only impossible travel, this covers any successful audit action — useful when cookie replay or concurrent sessions produce distant activity without a matching login.

A-B-A returns may not alert; sort raw Timeline events. No nearby magic_link_login_succeeded / sso_login_succeeded with distant successful non-login actions strengthens a session-reuse hypothesis.

Escalate when countries differ, UAs diverge (browser vs curl/python), login events are absent, or admin/export actions appear on the distant IP. Close as FP for documented dual-homed VPN/cloud egress with consistent corporate UAs.

Possible investigation steps

  • Use Esql.country_count, distance/speed, and Esql.event_action_values to see whether the distant hop was privileged (exports, role changes) vs routine chat views.
  • Timeline-sort events; compare IP, geo, UA, and action. Concurrent browser + tooling on far-apart cloud egress often explains FP; identical session cookies across continents without login does not.
  • Check for nearby logins. Absence + privileged distant actions → treat as cookie replay until proven otherwise.
  • Pair with Anthropic Impossible Travel Login / auth-failure rules when takeover is suspected.

False positive analysis

  • Split-tunnel / dual-homed clients (browser + API tooling) and VPN geo noise are the main FPs — require ASN/UA corroboration.

Response and remediation

  • On suspected replay/compromise: revoke sessions, reset credentials/MFA, hunt concurrent sessions, and review compliance/export and role changes for the same user.email after the distant events.

References

Related rules

to-top