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, andEsql.event_action_valuesto 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.emailafter the distant events.
References
Related rules
- Anthropic Impossible Travel Login
- Anthropic Multiple Authentication Failures
- Anthropic Magic Link Second Factor Disabled
- Anthropic SSO Disabled or Connection Removed
- Anthropic Admin API Key Created