Splunk

M365 Copilot Session Origin Anomalies

Last updated 2 days ago on 2026-07-17

About

Detects M365 Copilot users accessing from multiple geographic locations to identify potential account compromise, credential sharing, or impossible travel patterns. The detection aggregates M365 Copilot Graph API events per user, calculating distinct cities and countries accessed, unique IP addresses, and the observation timeframe to compute a locations-per-day metric that measures geographic mobility. Users accessing Copilot from more than one city (cities_count > 1) are flagged and sorted by country and city diversity, surfacing accounts exhibiting anomalous geographic patterns that suggest compromised credentials being used from distributed locations or simultaneous access from impossible travel distances.
Platform
Splunk
Tags
Data Source: SplunkDomain: ApplicationRule Type: QueryOS: Any
Severity
medium
Risk Score
47
References
https://www.splunk.com/en_us/blog/artificial-intelligence/m365-copilot-log-analysis-splunk.html(external, opens in a new tab or window)
False Positives
Legitimate business travelers, remote workers using VPNs, users with corporate offices in multiple locations, or employees accessing Copilot during international travel may trigger false positives.
Source
View on GitHub(external, opens in a new tab or window)

Definition

Rule Language
SPL
Rule Type
query
text code block:
`m365_copilot_graph_api` (appDisplayName="*Copilot*" OR appDisplayName="M365ChatClient" OR appDisplayName="OfficeAIAppChatCopilot") | eval user = userPrincipalName | stats count as events, dc(location.city) as cities_count, values(location.city) as city_list, dc(location.countryOrRegion) as countries_count, values(location.countryOrRegion) as country_list, dc(ipAddress) as ip_count, values(ipAddress) as ip_addresses, min(_time) as first_seen, max(_time) as last_seen BY user | eval days_active = round((last_seen - first_seen)/86400, 1) | eval locations_per_day = if(days_active > 0, round(cities_count/days_active, 2), cities_count) | eval first_seen = strftime(first_seen, "%Y-%m-%d %H:%M:%S") | eval last_seen = strftime(last_seen, "%Y-%m-%d %H:%M:%S") | where cities_count > 1 | sort -countries_count, -cities_count | `m365_copilot_session_origin_anomalies_filter`

View this rule on Splunk

This rule was sourced from the official Splunk public repository. View the original rule on GitHub(external, opens in a new tab or window).