LoFP LoFP / cloud egress, split-tunnel, or dual-homed clients that present different public ips for concurrent sessions.

Sample rules

OpenAI ChatGPT Enterprise User Activity From High Number of Countries

Description

Detects ChatGPT Enterprise activity for the same user email from at least three countries and two unique user agents within a four-hour interval across any compliance data stream. Stolen session cookies often produce post-auth activity (conversation views, deletions, downloads) without a new login event, so this rule is not limited to authentication logs.

Detection logic

from logs-openai_chatgpt_enterprise.*
| where
    user.email is not null and
    source.ip is not null and
    source.geo.country_iso_code is not null
| stats
    Esql.country_count = count_distinct(source.geo.country_iso_code),
    Esql.region_count = count_distinct(source.geo.region_name),
    Esql.source_ip_count = count_distinct(source.ip),
    Esql.user_agent_count = count_distinct(user_agent.original),
    Esql.event_count = count(*),
    Esql.event_action_values = values(event.action),
    Esql.source_ip_values = values(source.ip),
    Esql.source_geo_country_iso_code_values = values(source.geo.country_iso_code),
    Esql.source_geo_region_name_values = values(source.geo.region_name),
    Esql.source_geo_city_name_values = values(source.geo.city_name),
    Esql.user_agent_original_values = values(user_agent.original),
    Esql.timestamp_first_seen = min(@timestamp),
    Esql.timestamp_last_seen = max(@timestamp)
  by user.email
| where Esql.country_count >= 3 and Esql.user_agent_count >= 2
| keep user.email, Esql.*

Anthropic Session Reuse Impossible Travel

Description

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.

Detection logic

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

Anthropic User Activity From High Number of Countries

Description

Detects Anthropic activity for the same user email from at least three countries and two unique user agents within a four-hour interval. This highly unusual activity may indicate leaked session cookies being abused by a remote adversary using a VPN.

Detection logic

FROM logs-anthropic.audit-* 
| where user.email IS NOT NULL and source.ip IS NOT NULL and  `user.email` != "unknown@invalid.internal"
| IP_LOCATION geo = source.ip with { "properties": ["country_name", "city_name", "location"] }
| stats Esql.country_count = COUNT_DISTINCT(geo.country_name), 
        Esql.source_ip_count = COUNT_DISTINCT(source.ip), 
        Esql.user_agent_count = COUNT_DISTINCT(`user_agent`.original),
        Esql.event_count = count(*),
        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.timestamp_first_seen = min(@timestamp), 
        Esql.timestamp_last_seen = max(@timestamp) by user.email 
| where Esql.country_count >= 3 AND Esql.user_agent_count >= 2 AND 
  (MV_CONTAINS(Esql.event_action_values, "claude_chat_created") or MV_CONTAINS(Esql.event_action_values, "claude_chat_deleted") or MV_CONTAINS(Esql.event_action_values, "claude_user_settings_updated") or MV_CONTAINS(Esql.event_action_values, "claude_file_deleted") or MV_CONTAINS(Esql.event_action_values, "claude_chat_updated")) 
| Keep user.email, Esql.*