← Back to SOC feed Coverage →

AI Agents - Owner added to MCP-enabled agent

kql MEDIUM Azure-Sentinel
T1098
IdentityInfo
huntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-07-20T23:00:00Z · Confidence: medium

Hunt Hypothesis

This hunt targets adversaries leveraging MITRE technique T1098 to establish persistence by adding unauthorized owners to existing AI agents with MCP servers, effectively creating a new privileged access vector for lateral movement or data exfiltration. Proactive hunting in Azure Sentinel is critical because the medium severity of this event requires rapid validation within 48 hours to distinguish legitimate administrative changes from stealthy account manipulation before attackers can fully exploit these newly granted privileges.

KQL Query

let lookback = 14d;
let recent = 2d;
let IdentityIdtoUPN = materialize(
    IdentityInfo
    | extend ResolvedAccountUpn = tostring(column_ifexists("AccountUpn", column_ifexists("AccountUPN", ""))),
             IdentityTimestamp = todatetime(column_ifexists("Timestamp", column_ifexists("TimeGenerated", datetime(null))))
    | where IdentityTimestamp >= ago(lookback)
    | where isnotempty(AccountObjectId) and isnotempty(ResolvedAccountUpn)
    | summarize arg_max(IdentityTimestamp, ResolvedAccountUpn) by AccountObjectId
    | project AccountObjectId = tostring(AccountObjectId), AccountUpn = ResolvedAccountUpn);
let CurrentState = materialize(
    AgentsInfo
    | where Timestamp > ago(recent)
    | summarize arg_max(Timestamp, *) by AgentId
    | where LifecycleStatus != "Deleted"
    | where array_length(coalesce(McpServers, dynamic([]))) > 0
    | project AgentId, Timestamp, Name, Platform, CreatedDateTime,
              CurrentOwners = coalesce(Owners, dynamic([])), McpServers);
let CurrentMcp =
    CurrentState
    | mv-expand Mcp = McpServers
    | extend McpName = tostring(Mcp.name)
    | summarize McpServersConfigured = make_set_if(McpName, isnotempty(McpName)) by AgentId;
let BaselineState =
    AgentsInfo
    | where Timestamp between (ago(lookback) .. ago(recent))
    | where LifecycleStatus != "Deleted"
    | summarize arg_max(Timestamp, *) by AgentId
    | project AgentId, PreviousTimestamp = Timestamp,
              PreviousOwners = coalesce(Owners, dynamic([]));
CurrentState
| join kind=inner CurrentMcp on AgentId
| join kind=inner BaselineState on AgentId
| extend AddedOwners = set_difference(CurrentOwners, PreviousOwners)
| where array_length(AddedOwners) > 0
| mv-expand AddedOwnerId = AddedOwners to typeof(string)
| join kind=leftouter IdentityIdtoUPN on $left.AddedOwnerId == $right.AccountObjectId
| extend AddedOwnerUpn = AccountUpn,
         UnresolvedAddedOwnerId = iff(isempty(AccountUpn), AddedOwnerId, "")
| extend AddedOwnerAccountName = tostring(split(AddedOwnerUpn, "@")[0]),
         AddedOwnerAccountUPNSuffix = tostring(split(AddedOwnerUpn, "@")[1])
| project Timestamp, PreviousTimestamp, AgentId, Name, Platform, CreatedDateTime,
         PreviousOwners, CurrentOwners, AddedOwners, AddedOwnerId,
         UnresolvedAddedOwnerId, AddedOwnerUpn, AddedOwnerAccountName,
         AddedOwnerAccountUPNSuffix, McpServersConfigured
| sort by Timestamp desc

Analytic Rule Definition

id: c35b5a60-71ee-448c-935d-f60f4a4c7f2b
name: AI Agents - Owner added to MCP-enabled agent
description: |
  Identifies owners newly observed on existing AI agents with MCP servers configured. Review the added owners and audit records to confirm whether privileged access was authorized. Run within 2 days of a change to retain coverage.
requiredDataConnectors: []
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1098
query: |
  let lookback = 14d;
  let recent = 2d;
  let IdentityIdtoUPN = materialize(
      IdentityInfo
      | extend ResolvedAccountUpn = tostring(column_ifexists("AccountUpn", column_ifexists("AccountUPN", ""))),
               IdentityTimestamp = todatetime(column_ifexists("Timestamp", column_ifexists("TimeGenerated", datetime(null))))
      | where IdentityTimestamp >= ago(lookback)
      | where isnotempty(AccountObjectId) and isnotempty(ResolvedAccountUpn)
      | summarize arg_max(IdentityTimestamp, ResolvedAccountUpn) by AccountObjectId
      | project AccountObjectId = tostring(AccountObjectId), AccountUpn = ResolvedAccountUpn);
  let CurrentState = materialize(
      AgentsInfo
      | where Timestamp > ago(recent)
      | summarize arg_max(Timestamp, *) by AgentId
      | where LifecycleStatus != "Deleted"
      | where array_length(coalesce(McpServers, dynamic([]))) > 0
      | project AgentId, Timestamp, Name, Platform, CreatedDateTime,
                CurrentOwners = coalesce(Owners, dynamic([])), McpServers);
  let CurrentMcp =
      CurrentState
      | mv-expand Mcp = McpServers
      | extend McpName = tostring(Mcp.name)
      | summarize McpServersConfigured = make_set_if(McpName, isnotempty(McpName)) by AgentId;
  let BaselineState =
      AgentsInfo
      | where Timestamp between (ago(lookback) .. ago(recent))
      | where LifecycleStatus != "Deleted"
      | summarize arg_max(Timestamp, *) by AgentId
      | project AgentId, PreviousTimestamp = Timestamp,
                PreviousOwners = coalesce(Owners, dynamic([]));
  CurrentState
  | join kind=inner CurrentMcp on AgentId
  | join kind=inner BaselineState on AgentId
  | extend AddedOwners = set_difference(CurrentOwners, PreviousOwners)
  | where array_length(AddedOwners) > 0
  | mv-expand AddedOwnerId = AddedOwners to typeof(string)
  | join kind=leftouter IdentityIdtoUPN on $left.AddedOwnerId == $right.AccountObjectId
  | extend AddedOwnerUpn = AccountUpn,
           UnresolvedAddedOwnerId = iff(isempty(AccountUpn), AddedOwnerId, "")
  | extend AddedOwnerAccountName = tostring(split(AddedOwnerUpn, "@")[0]),
           AddedOwnerAccountUPNSuffix = tostring(split(AddedOwnerUpn, "@")[1])
  | project Timestamp, PreviousTimestamp, AgentId, Name, Platform, CreatedDateTime,
           PreviousOwners, CurrentOwners, AddedOwners, AddedOwnerId,
           UnresolvedAddedOwnerId, AddedOwnerUpn, AddedOwnerAccountName,
           AddedOwnerAccountUPNSuffix, McpServersConfigured
  | sort by Timestamp desc
entityMappings:
  - entityType: Account

Required Data Sources

Sentinel TableNotes
IdentityInfoEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Here are four specific false positive scenarios for the AI Agents - Owner added to MCP-enabled agent rule, including suggested filters and exclusions:

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/AI Agents/AgentsInfoOwnerAddedToMcpAgent.yaml