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.
Rule type: esql
Rule indices:
Rule Severity: high
Risk Score: 73
Runs every: 5m
Searches indices from: now-16m
Maximum alerts per execution: 100
References:
Tags:
- Domain: GenAI
- Domain: Identity
- Platform: Anthropic
- Data Source: Anthropic Audit Logs
- Use Case: Identity and Access Audit
- Use Case: Threat Detection
- Resources: Investigation Guide
- Rule Type: ES|QL
- Tactic: Credential Access
- Tactic: Initial Access
- Mitre Atlas: AML.T0012
Version: 1
Rule authors:
- Elastic
Rule license: Elastic License v2
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.
- 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.
- Split-tunnel / dual-homed clients (browser + API tooling) and VPN geo noise are the main FPs — require ASN/UA corroboration.
- 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.
from logs-anthropic.audit-*
| where
data_stream.dataset == "anthropic.audit" and
event.outcome == "success" and
anthropic.audit.actor.type == "user_actor" and
user.email is not null and
source.ip is not null
| IP_LOCATION geo = source.ip with { "properties": ["country_name", "city_name", "location"] }
| eval
Esql.source_geo_lat = st_y(geo.location),
Esql.source_geo_lon = st_x(geo.location)
| where Esql.source_geo_lat is not null and Esql.source_geo_lon is not null
| stats
Esql.first_lat = first(Esql.source_geo_lat, @timestamp),
Esql.first_lon = first(Esql.source_geo_lon, @timestamp),
Esql.last_lat = last(Esql.source_geo_lat, @timestamp),
Esql.last_lon = last(Esql.source_geo_lon, @timestamp),
Esql.event_count = count(*),
Esql.country_count = count_distinct(geo.country_name),
Esql.event_id_values = values(event.id),
Esql.event_action_values = values(event.action),
Esql.source_ip_values = values(source.ip),
Esql.source_geo_country_name_values = values(geo.country_name),
Esql.source_geo_city_name_values = values(geo.city_name),
Esql.user_agent_original_values = values(user_agent.original),
Esql.anthropic_audit_actor_type_values = values(anthropic.audit.actor.type),
Esql.timestamp_first_seen = min(@timestamp),
Esql.timestamp_last_seen = max(@timestamp)
by user.email
| where Esql.event_count >= 2 and Esql.country_count >= 2
| eval
Esql.p1 = to_geopoint(concat("POINT(", to_string(Esql.first_lon), " ", to_string(Esql.first_lat), ")")),
Esql.p2 = to_geopoint(concat("POINT(", to_string(Esql.last_lon), " ", to_string(Esql.last_lat), ")"))
| eval
Esql.distance_km = round(st_distance(Esql.p1, Esql.p2) / 1000.0, 0),
Esql.window_minutes = date_diff("minute", Esql.timestamp_first_seen, Esql.timestamp_last_seen),
Esql.travel_kmh = case(Esql.window_minutes > 0, round(Esql.distance_km * 60.0 / Esql.window_minutes, 0), null)
| where Esql.distance_km >= 500 and Esql.travel_kmh >= 800
| keep user.email, Esql.*
Framework: MITRE ATT&CK
Tactic:
- Name: Credential Access
- Id: TA0006
- Reference URL: https://attack.mitre.org/tactics/TA0006/
Technique:
- Name: Steal Web Session Cookie
- Id: T1539
- Reference URL: https://attack.mitre.org/techniques/T1539/
Framework: MITRE ATT&CK
Tactic:
- Name: Initial Access
- Id: TA0001
- Reference URL: https://attack.mitre.org/tactics/TA0001/
Technique:
- Name: Valid Accounts
- Id: T1078
- Reference URL: https://attack.mitre.org/techniques/T1078/
Sub Technique:
- Name: Cloud Accounts
- Id: T1078.004
- Reference URL: https://attack.mitre.org/techniques/T1078/004/