Azure DevOps Activity from New/Rare IP Outside Business Hours
Azure Dev Ops Activity From Newor Rare IP Outside Business Hours
Query
// List non-internal Azure DevOps activities excluding business hours
// Exclude business hours: Mon-Fri 06:30-18:30 and Monday 20:30-23:00
// Configuration
// Define your IP Range
let internalIpRange = "1.2.3.4/16";
// Define your default Business hours
let businessHoursStart = 6 * 60 + 30; // 06:30 in minutes
let businessHoursEnd = 18 * 60 + 30; // 18:30 in minutes
let mondayExtraStart = 20 * 60 + 30; // 20:30 in minutes
let mondayExtraEnd = 23 * 60; // 23:00 in minutes
let businessDaysStart = 1; // Monday
let businessDaysEnd = 5; // Friday
let monday = 1;
let lookbackPeriod = 1d;
let historicalLookback = 29d;
let minHistoricalDays = 3; // Minimum number of days for known IPs
// Build historical IP list with day count
let HistoricalIPDays = ADOAuditLogs_CL
| where TimeGenerated >= ago(historicalLookback)
| extend isInternalIp = ipv4_is_in_range(IpAddress, internalIpRange)
| where isInternalIp == false and isnotnull(IpAddress)
| summarize UniqueDays = dcount(format_datetime(TimeGenerated, 'yyyy-MM-dd')) by ActorUPN, IpAddress;
// Current activities outside of business hours
let CurrentActivities = ADOAuditLogs_CL
| where TimeGenerated > ago(lookbackPeriod)
| extend Dow = toint(dayofweek(TimeGenerated) / 1d)
| extend MinutesSinceMidnight = 60 * datetime_part("Hour", TimeGenerated) + datetime_part("Minute", TimeGenerated)
| where not(
// Mon-Fri 06:30-18:30
(Dow >= businessDaysStart and Dow <= businessDaysEnd and MinutesSinceMidnight >= businessHoursStart and MinutesSinceMidnight <= businessHoursEnd)
or
// Monday 20:30-23:00
(Dow == monday and MinutesSinceMidnight >= mondayExtraStart and MinutesSinceMidnight <= mondayExtraEnd)
)
| extend isInternalIp = ipv4_is_in_range(IpAddress, internalIpRange)
| where isInternalIp == false and isnotnull(IpAddress)
| summarize CountEvents=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Actions=make_set(ActionId, 20) by ActorUPN, ActorDisplayName, IpAddress, UserAgent;
// Join with historical data and filter for new/rare IPs
CurrentActivities
| join kind=leftouter HistoricalIPDays on ActorUPN, IpAddress
| extend
IPSeenDays = coalesce(UniqueDays, 0),
IPRiskLevel = case(
isempty(UniqueDays) or UniqueDays == 0, "High Risk - New IP",
UniqueDays < minHistoricalDays, "Medium Risk - Rare IP",
UniqueDays >= minHistoricalDays, "Lower Risk - Known IP",
"Unknown"
)
// Only alert for new or rare IPs (less than 3 days)
| where IPRiskLevel in ("High Risk - New IP", "Medium Risk - Rare IP")About this query
Azure DevOps Activity from New/Rare IP Outside Business Hours
Query Information
MITRE ATT&CK Technique(s)
| Technique ID | Title | Link |
|---|---|---|
| T1078.004 | Cloud Accounts | https://attack.mitre.org/techniques/T1078/004/ |
Description
This rule detects Azure DevOps activities originating from new or rarely seen IP addresses outside of defined business hours. It establishes a baseline of historical IP addresses and flags activities from IPs that have been seen for fewer than a specified number of days, indicating potentially suspicious access.
Author <Optional>
- Name: Benjamin Zulliger
- Github: https://github.com/benscha/KQLAdvancedHunting
- LinkedIn: https://www.linkedin.com/in/benjamin-zulliger/
Defender XDR
Explanation
This query is designed to detect potentially suspicious activities in Azure DevOps by identifying actions from new or rarely seen IP addresses outside of normal business hours. Here's a simplified breakdown of what the query does:
-
Configuration:
- It defines what constitutes an internal IP address and sets the business hours as Monday to Friday from 06:30 to 18:30, with an additional window on Monday from 20:30 to 23:00. - It specifies a lookback period of 1 day for current activities and 29 days for historical IP data.
- It sets a threshold of 3 days for an IP to be considered known.
-
Historical Data:
- The query first gathers historical data on IP addresses used in Azure DevOps activities over the past 29 days.
- It counts the number of unique days each IP address has been seen, excluding internal IPs.
-
Current Activities:
- It then looks at current Azure DevOps activities within the last day.
- It filters out activities that occur during the defined business hours and excludes internal IPs.
- It summarizes these activities by user and IP address, noting the number of events and the time range they occurred.
-
Risk Assessment:
- The query joins current activities with historical data to assess the risk level of each IP address.
- IPs are categorized as "High Risk" if they are new, "Medium Risk" if they are rare (seen on fewer than 3 days), and "Lower Risk" if they are known.
-
Alerting:
- Finally, it filters the results to only include activities from IPs deemed "High Risk" or "Medium Risk," which are considered potentially suspicious.
In summary, this query helps identify unusual Azure DevOps activities by flagging actions from new or infrequently seen IP addresses outside of normal working hours, which could indicate unauthorized access attempts.
Details

Benjamin Zulliger
Released: February 26, 2026
Tables
Keywords
Operators