Query Details

Foundry Off Hours Or Geo Anomaly

Query

id: 8192a3b4-7777-4a01-9204-0123456789d4
name: Foundry - Off-hours or anomalous-geo agent activity
description: |
  Surfaces Foundry / Agent Service runs that happen outside the
  configured business-hours window, or from a country/region that is not
  on the expected list. A common indicator of stolen API keys, automated
  abuse running on a scheduler, or a geographically displaced caller.

  Foundry telemetry has no human-user identity, so the pivot is the agent
  (gen_ai.agent.name) and conversation (gen_ai.conversation.id). The
  client geo / IP columns (ClientCountryOrRegion, ClientCity, ClientIP)
  are only populated when the caller is an HTTP front end; for server-SDK
  spans they may be empty, so the geo check is best-effort and skipped
  when the allowed-country list is left empty. Tune businessStart /
  businessEnd (UTC hours) and allowedCountries to your tenant.
severity: Low
requiredDataConnectors:
- connectorId: ApplicationInsights
  dataTypes:
  - AppDependencies
queryFrequency: PT1H
queryPeriod: PT1H
triggerOperator: gt
triggerThreshold: 0
enabled: true
tactics:
- InitialAccess
- DefenseEvasion
relevantTechniques:
- T1078
query: |
  // Business hours expressed in UTC. Leave allowedCountries empty to
  // disable the geo check (off-hours only).
  let businessStart = 7;
  let businessEnd = 20;
  let allowedCountries = dynamic([]);
  AppDependencies
  | where isnotempty(Properties["gen_ai.agent.name"])
  | extend
      Agent     = tostring(Properties["gen_ai.agent.name"]),
      Model     = tostring(Properties["gen_ai.request.model"]),
      ConvId    = tostring(Properties["gen_ai.conversation.id"]),
      ProjectId = tostring(Properties["microsoft.foundry.project.id"]),
      Country   = tostring(column_ifexists("ClientCountryOrRegion", "")),
      City      = tostring(column_ifexists("ClientCity", "")),
      SrcIp     = tostring(column_ifexists("ClientIP", ""))
  | extend Hour = hourofday(TimeGenerated)
  | extend
      OffHours     = Hour < businessStart or Hour >= businessEnd,
      AnomalousGeo = array_length(allowedCountries) > 0
                     and isnotempty(Country)
                     and not(set_has_element(allowedCountries, Country))
  | where OffHours or AnomalousGeo
  | summarize
      Runs       = count(),
      Countries  = make_set(Country, 20),
      Cities     = make_set(City, 20),
      SrcIps     = make_set(SrcIp, 20),
      OffHoursRuns   = countif(OffHours),
      AnomalousGeoRuns = countif(AnomalousGeo),
      FirstSeen  = min(TimeGenerated),
      LastSeen   = max(TimeGenerated)
      by Agent, Model, ProjectId, ConvId
  | extend AccountName = iff(isempty(Agent), "unknown-agent", Agent)
  | extend SrcIpAddr = tostring(SrcIps[0])
  | project
      LastSeen, AccountName, Agent, Model, ProjectId, ConvId,
      Runs, OffHoursRuns, AnomalousGeoRuns, Countries, Cities,
      SrcIpAddr, FirstSeen
  | order by LastSeen desc
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: AccountName
- entityType: CloudApplication
  fieldMappings:
  - identifier: Name
    columnName: Model
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SrcIpAddr
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT6H
    matchingMethod: Selected
    groupByEntities:
    - Account
    groupByAlertDetails: []
    groupByCustomDetails: []
version: 1.0.0
kind: Scheduled
tags:
- Sentinel-As-Code
- Custom
- Foundry
- AI

Explanation

This query is designed to detect unusual activity related to the Foundry or Agent Service. It identifies instances where these services are accessed outside of normal business hours or from unexpected geographic locations. This can help in spotting potential misuse, such as stolen API keys or unauthorized automated access.

Here's a simplified breakdown of the query:

  1. Business Hours and Geography: The query checks if the service runs outside of specified business hours (7 AM to 8 PM UTC) or from countries not on an approved list. If the list is empty, the geographic check is skipped.

  2. Data Source: It uses data from AppDependencies in Application Insights, focusing on specific properties like agent name, model, conversation ID, and client location details.

  3. Conditions: It flags activities as suspicious if they occur off-hours or from an anomalous geographic location.

  4. Summarization: The query summarizes the data by counting the number of runs, identifying unique countries, cities, and IP addresses involved, and noting the first and last time these activities were seen.

  5. Output: The results include details such as the agent name, model, project ID, conversation ID, number of runs, and whether they were off-hours or from anomalous locations.

  6. Alerting: If any suspicious activity is detected, an alert is triggered, and incidents are created for further investigation.

  7. Entity Mapping: The query maps the data to entities like Account, Cloud Application, and IP for better organization and analysis.

Overall, this query helps in monitoring and alerting on potentially unauthorized or suspicious access to Foundry services, aiding in early detection of security threats.

Details

David Alonso profile picture

David Alonso

Released: June 8, 2026

Tables

AppDependencies

Keywords

FoundryAgentServiceAPIKeysAbuseHTTPServerSDKGeoIPAccountCloudApplication

Operators

letisnotemptytostringcolumn_ifexistshourofdayarray_lengthset_has_elementorandnotsummarizecountmake_setcountifminmaxbyiffisemptyprojectorder bydesc

Actions