﻿---
title: Anthropic Impossible Travel Login
description: 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)...
url: https://www.elastic.co/docs/reference/security/prebuilt-rules/rules/integrations/anthropic/initial_access_anthropic_impossible_travel_login
products:
  - Elastic Security
---

# 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.
**Rule type**: esql
**Rule indices**:
**Rule Severity**: high
**Risk Score**: 73
**Runs every**: 1h
**Searches indices from**: `now-24h`
**Maximum alerts per execution**: 100
**References**:
- [[https://platform.claude.com/docs/en/api/compliance/activities/list](https://platform.claude.com/docs/en/api/compliance/activities/list)](https://platform.claude.com/docs/en/api/compliance/activities/list)

**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: Initial Access
- Mitre Atlas: AML.T0012

**Version**: 1
**Rule authors**:
- Elastic

**Rule license**: Elastic License v2

## Investigation guide


## 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.


## Rule Query

```esql
from logs-anthropic.audit-*
| where
    data_stream.dataset == "anthropic.audit" and
    mv_contains(event.category, "authentication") and
    event.outcome == "success" and
    event.action in ("magic_link_login_succeeded", "sso_login_succeeded") 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.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
| 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 >= 1000 and Esql.travel_kmh >= 800
| keep user.email, Esql.*
```

**Framework:** MITRE ATT&CK
- Tactic:
  - Name: Initial Access
  - Id: TA0001
  - Reference URL: [[https://attack.mitre.org/tactics/TA0001/](https://attack.mitre.org/tactics/TA0001/)](https://attack.mitre.org/tactics/TA0001/)
- Technique:
  - Name: Valid Accounts
  - Id: T1078
  - Reference URL: [[https://attack.mitre.org/techniques/T1078/](https://attack.mitre.org/techniques/T1078/)](https://attack.mitre.org/techniques/T1078/)
- Sub Technique:
  - Name: Cloud Accounts
  - Id: T1078.004
  - Reference URL: [[https://attack.mitre.org/techniques/T1078/004/](https://attack.mitre.org/techniques/T1078/004/)](https://attack.mitre.org/techniques/T1078/004/)