Discover local AI agents with Microsoft Defender for Endpoint (Preview)
In brief
The documentation now covers Windows and macOS, adds agent risk and configuration details, and clarifies licensing: Defender for Endpoint Plan 2 supports discovery and hunting, while risk insights and recommendations require Microsoft 365 E7 or Microsoft Agent 365 with Defender for Endpoint Plan 2.
What Defender admins need to know
Review licensing if administrators need agent risk levels, indicators, or recommendations. Discovery is accessed through Assets > AI agents > Local agents; no required action is stated.
Summaries are generated from the documentation change itself.
Documentation change
The comparison below shows only the changed extract. Use the full-page view for complete context.
title: Discover local AI agents with Microsoft Defender for Endpoint (Preview)
description: Learn how to discover, view, and investigate local AI agents on Windows and macOS devices by using Microsoft Defender.
author: lwainstein
ms.author: lwainstein
ms.service: defender-endpoint
ms.topic: overviewhow-to
ms.date: 05/27/2026
ai-usage: ai-assisted
#customer intent: As a security administrator, I want to discover and monitor local AI agents on my organization's devices so that I can manage AI-related security risks on endpoints.
Microsoft Defender automatically discovers supported local AI agents and MCP servers configured on onboarded devices and surfaces them in the AI agent inventory and advanced hunting. To learn more about local AI agent discovery, see Local AI agent discovery with Microsoft Defender for Endpoint.
In this article, you learn how to view discovered agents in the inventory, review their configuration and risk, explore their relationships with devices and identities, and investigate agent presence using advanced hunting.
Prerequisites
Before you can discover local AI agents on endpoints, make sure you meet the following requirements:
- Your environment is in the commercial cloud. Sovereign and national clouds aren't supported.
- Your organization has a Microsoft Defender for Endpoint Plan 2, Microsoft 365 E5, Microsoft Agent 365, or Microsoft 365 E7 license.
- Your devices are onboarded to Microsoft Defender for Endpoint
Prerequisites
Before you can discover local AI agents on endpoints, make sure you meet the following requirements:
- Your environment is in the commercial cloud. Sovereign and national clouds aren't supported.
- Your organization has a Microsoft Defender for Endpoint Plan 2 license. For more information, see Licensing.
- Your devices are onboarded to Microsoft Defender for Endpoint.
- Your devices run a supported version of Windows or macOS, and Microsoft Defender Antivirus is updated with current monthly platform and engine updates.
Your devices are runningMicrosoft Defender Antivirus is running in active mode on your devices, with real-time protectionenabled in Windows Security.enabled.
You don't need additional deployment, configuration, or scripts beyond the device onboarding requirements. If the device meets all of the prerequisites, agent discovery begins automatically.
Licensing
Microsoft Defender for Endpoint Plan 2 is the minimum license for local AI agent discovery. Security posture capabilities for the agents that Defender discovers require another license, as described in the following table.
| Capability | Required license |
|---|---|
| Discover local AI agents and view them in the AI agent inventory, including the agent details, the device and account, and the configured MCP servers | Microsoft Defender for Endpoint Plan 2 |
Query local AI agents in advanced hunting, including the AgentsInfo table | Microsoft Defender for Endpoint Plan 2 |
| View the risk level, risk indicators, and security recommendations for discovered local AI agents | Microsoft 365 E7, or Microsoft Agent 365 together with Microsoft Defender for Endpoint Plan 2 |
Microsoft 365 E5 and Microsoft 365 E7 both include Microsoft Defender for Endpoint Plan 2. If your organization has Microsoft Defender for Endpoint Plan 2 without Microsoft 365 E7 or Microsoft Agent 365, you can discover local AI agents, view the inventory, and query agents in advanced hunting, but the risk level, risk indicators, and security recommendations aren't available.
View local AI agents in the inventory
To view a list of all local AI agents discovered on endpoints:Sign in to the Microsoft Defender portal.
In the left navigation pane, select Assets > AI
Agentsagents.Select the Local agents tab to see
a filtered list ofthe local AI agents discovered onendpointyour devices.
To view a specific agent's details:From theLocal agentslist, select an agent to open theAgentpane.:::image type="content" source="media/local-agent-discovery-overview/discovery-overview.png" alt-text="Screenshot showing the local AI agents inventory in the Microsoft Defender portal with discovered agents listed." lightbox="media/local-agent-discovery-overview/discovery-overview.png":::
ReviewThe Agents insights cards summarize the total number of monitored agents, the number of agents at a high risk level, and the number of critical agents.The list shows one entry for each agent installation, so an agent that runs on several devices, or under several accounts on the same device, appears more than once. Use the filters to narrow the list, select Customize columns to change which columns appear, or select Export to download the list.
Column Description Agent name, Version The discovered agent, and the version installed. Device name, Device ID, OS platform, Device type The device where the agent details, including:was discovered.Account name, Account domain The account that the agent runs under. Risk level The overall risk level of the agent. Risk indicators Why the agent is considered risky, such as Running on a Critical Device or Used by a Critical User. Recommendations The security recommendations that apply to the agent. MCP servers, Local MCPs The number of remote MCP servers, and of local MCP servers, configured for the agent. First seen When the agent was first discovered. View the details of a local AI agent
From the Local agents list, select an agent to open its details pane:
AgentDetails shows the agent name,version,vendor, model, related process, whether the host process is trusted, whether the agent automatically approves its own actions, andrelated processthe source agent ID.AssociatedRisk shows the risk level, the risk indicators, and the security recommendations that apply to the agent.- Device details shows the device where the agent was discovered, including the OS platform and
userversion, the device type and roles, and the Microsoft Entra device ID. First seenUser details shows the ID, name, andlast updated timestampsdomain of the account that the agent runs under.Integrity level, auto-approve status,MCP servers lists the MCP servers configured for the agent, with the name, type, andtrust indicatorendpoint of each server.ConfiguredLocal MCPservers, when detectedservers lists the local MCP entries discovered on the device.
To investigate further, select Go hunt to query the agent in advanced hunting, View on map to see the agent in the attack surface map, or Open Agent page to open the full agent page.
Review an agent's attack surface and recommendations
The agent page has an Overview tab and a Security recommendations tab. The Attack surface map on the Overview tab shows the other agents, devices, identities, and resources associated with the agent.
For more information on using the AI agent inventory, see Discover AI agents and assess security posture using Microsoft Defender.
Query local AI agents using advanced hunting
Use advanced hunting to proactively investigate local AI agent presence, understand which users and resourceshow agents can reach,are configured, and identify high-risk configurations.the agents and users that carry the most risk. These queries help you assess exposure,inventory agents, review the MCP servers they connect to, and trace access paths to critical or sensitive assets,assets.
Understand the tables
Three advanced hunting tables describe local AI agents. Each answers a different question, and prioritize security response.they're most useful together.
The ExposureGraphNodes and ExposureGraphEdges tables represent the security graph
| Table | What it contains | Use it to answer |
|---|---|---|
| AgentsInfo | A profile record for every AI agent that Microsoft Defender discovers, across all agent platforms. For local AI agents, the record includes the publisher, version, host process, trust and auto-approve settings, configured MCP servers, and the device and account where the agent was seen. | What is this agent, and how is it configured? |
| ExposureGraphNodes | Every entity in your organization as a node, including | What is this entity, and how much does it matter? |
| ExposureGraphEdges | The relationships between nodes, such as the device an agent runs on, or the resources an identity can access. | What can this agent reach? |
In short, AgentsInfo describes what an agent is, and the exposure graph describes what an agent can reach.
Both data sources contain agents from every platform, including cloud agents, so each one needs its own filter.
In AgentsInfo, filter on the Platform column:
AgentsInfo
| where Platform == "LocalAgents"
In ExposureGraphNodes, AI agents use the ai-agent node label. Filter on the platform reported in the node properties:
ExposureGraphNodes
| where NodeLabel == "ai-agent"
| where tostring(NodeProperties.rawData.aiAgentMetadata.platform) == "LocalAgents"
Combine the tables
An agent has two identifiers, and queries that combine the tables need both:
AgentIdidentifies the agent profile inAgentsInfo. The exposure graph stores the same value in the agent node, asNodeProperties.rawData.aiAgentMetadata.id.NodeIdidentifies the agent's node in the exposure graph. This is the value thatExposureGraphEdgesrefers to, inSourceNodeIdandTargetNodeId.
| To go from | To | Match on |
|---|---|---|
AgentsInfo |
ExposureGraphNodes |
tostring(AgentId) and tostring(NodeProperties.rawData.aiAgentMetadata.id) |
ExposureGraphNodes |
ExposureGraphEdges |
NodeId and SourceNodeId or TargetNodeId |
Local AI agent nodes connect to the rest of the exposure graph through the following edges:
| Edge label | Target node label | Description |
|---|---|---|
runs on |
device, ec2.instance, microsoft.compute/virtualmachines |
The device where the agent was discovered. |
uses |
mcp/server |
An MCP server that's configured for the agent. |
used by |
user |
The identity that uses the agent. |
Cloud AI agents also use a can authenticate as edge that points to a service principal or a Microsoft Entra OAuth app. Local AI agents don't use that edge, so use used by to resolve the identity behind a local AI agent.
Asset criticality is stored in ExposureGraphNodes, in NodeProperties.rawData.criticalityLevel.criticalityLevel, where 0 is the highest level (very high) and 3 is the lowest (low). The ruleNames property lists the classification rules that made the asset critical.
Work with local AI agent profiles
AgentsInfo adds a record each time an agent profile is updated, so a single agent usually has several records. To return only the most recent record for each agent, summarize with arg_max on Timestamp:
AgentsInfo
| where Platform == "LocalAgents"
| summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo) by AgentId
LifecycleStatus reports whether the agent is still present on the device. An agent that's reinstalled can also be reissued with a new AgentId, which leaves the earlier record marked as Deleted, so filter out Deleted and Uninstalled records when you want a current inventory.
Many AgentsInfo columns describe cloud agents and are empty for local AI agents. The columns that carry local AI agent data are AgentId, Name, Version, PublishedStatus, LifecycleStatus, LastUpdatedDateTime, McpServers, DeclaredTools, and RawAgentInfo.
Local AI agent posture is nested in the RawAgentInfo column, under localAgentMetadata:
| Property | Description |
|---|---|
vendor |
The publisher of the agent, such as Anthropic, Google, or OpenAI. |
relatedProcess |
The process that hosts the agent, such as code.exe. |
trustedProcess |
Whether the host process is trusted. Reported as the string "true" or "false". |
autoApprove |
Whether the agent acts without prompting the user for approval. Reported as the string "true" or "false". |
deviceName, aadDeviceId |
The device where the agent was discovered. |
accountName, accountDomain, accountSid |
The account that the agent ran under. |
localMcps |
MCP servers that run locally on the device, including the command that starts each one. |
Each of the following queries is self-contained. The queries that use the exposure graph start with a set of let statements that resolve local AI agents and the devices they run on.
Get an inventory of local AI agents
This query lists the local AI agents discovered in your organization, together with the publisher, the host process, the versions in use, and how widely each agent is deployed:
AgentsInfo
| where Platform == "LocalAgents"
| summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo)
by AgentId
| where LifecycleStatus !in~ ("Deleted", "Uninstalled")
| extend AgentMetadata = RawAgentInfo.localAgentMetadata
| extend Vendor = tostring(AgentMetadata.vendor),
Process = tostring(AgentMetadata.relatedProcess),
Device = tostring(AgentMetadata.deviceName),
Account = tostring(AgentMetadata.accountName)
| summarize Installations = count(),
DeviceCount = dcount(Device),
Devices = make_set(Device, 100),
Versions = make_set(Version, 20),
Accounts = make_set_if(Account, isnotempty(Account), 50)
by Agent = Name, Vendor, Process
| sort by DeviceCount desc, Installations desc
Each AgentId represents one agent profile, which is a single agent on a single device for a single account. Installations counts those profiles, so an agent that two people use on the same device counts twice, while DeviceCount counts the device once.
Review the MCP servers and tools that local AI agents use
This query lists the MCP servers and tools configured for local AI agents, the agents and devices that use them, and where each one runs:
let localAgentProfiles =
AgentsInfo
| where Platform == "LocalAgents"
| extend AgentMetadata = RawAgentInfo.localAgentMetadata
| project Timestamp,
Agent = Name,
Device = tostring(AgentMetadata.deviceName),
Account = tostring(AgentMetadata.accountName),
McpServers,
DeclaredTools,
LocalServers = AgentMetadata.localMcps;
let remoteMcpServers =
localAgentProfiles
| mv-expand Server = McpServers
| project Timestamp, Agent, Device, Account,
McpServer = tostring(Server.name),
Origin = "Remote MCP server",
Transport = tostring(Server.type),
Location = tostring(Server.endpoint);
let agentDeclaredTools =
localAgentProfiles
| mv-expand Tool = DeclaredTools
| project Timestamp, Agent, Device, Account,
McpServer = tostring(Tool.name),
Origin = "Declared tool",
Transport = tostring(Tool.type),
Location = tostring(Tool.endpoint);
let localMcpServers =
localAgentProfiles
| mv-expand Server = LocalServers
| project Timestamp, Agent, Device, Account,
McpServer = tostring(Server.name),
Origin = "Local MCP server",
Transport = tostring(Server.transportType),
Location = tostring(Server.commandName);
remoteMcpServers
| union agentDeclaredTools, localMcpServers
| where isnotempty(McpServer)
| summarize LastSeen = max(Timestamp),
Agents = make_set(Agent, 20),
Devices = make_set(Device, 20),
Accounts = make_set_if(Account, isnotempty(Account), 20)
by McpServer, Origin, Transport, Location
| sort by McpServer asc, Origin asc
The Origin column distinguishes the three ways a server or tool is reported:
- A remote MCP server is reached over the network, and
Locationholds its endpoint. - A local MCP server runs as a process on the device, and
Locationholds the command that starts it. Local MCP servers are reported only inAgentsInfo. - A declared tool is a tool the agent advertises. Declared tools are often backed by an MCP server, but they don't always report an endpoint.
Unlike the other queries, this one reads every profile record for each agent, so that MCP servers reported at any point are included. The LastSeen column shows when each server was last reported.
Find local AI agents with risky configurations
This query returns local AI agents that act without asking the user for approval, or that run in a host process that isn't trusted:
AgentsInfo
| where Platform == "LocalAgents"
| summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo)
by AgentId
| where LifecycleStatus !in~ ("Deleted", "Uninstalled")
| extend AgentMetadata = RawAgentInfo.localAgentMetadata
| extend AutoApprove = tostring(AgentMetadata.autoApprove),
TrustedProcess = tostring(AgentMetadata.trustedProcess),
Vendor = tostring(AgentMetadata.vendor),
Process = tostring(AgentMetadata.relatedProcess),
Device = tostring(AgentMetadata.deviceName),
Account = tostring(AgentMetadata.accountName)
| where AutoApprove =~ "true" or TrustedProcess =~ "false"
| extend RiskReason = case(
AutoApprove =~ "true" and TrustedProcess =~ "false",
"Acts without approval, and the host process isn't trusted",
AutoApprove =~ "true",
"Acts without approval",
"The host process isn't trusted")
| project Agent = Name, Vendor, Version, Process, Device, Account,
AutoApprove, TrustedProcess, RiskReason, LastSeen = Timestamp
| sort by Device asc, Agent asc
An agent that auto-approves its own actions runs tools and reaches resources without a person confirming each step, so the account and device that the agent runs on define what it can do unsupervised.
Find risky local AI agents on critical devices
This query combines agent configuration from AgentsInfo with asset criticality from the exposure graph, so you can start with the agents that both act unsupervised and run on business-critical devices:
let deviceLabels = dynamic(["device", "ec2.instance",
"microsoft.compute/virtualmachines"]);
let riskyAgentProfiles =
AgentsInfo
| where Platform == "LocalAgents"
| summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo)
by AgentId
| where LifecycleStatus !in~ ("Deleted", "Uninstalled")
| extend AgentMetadata = RawAgentInfo.localAgentMetadata
| extend AutoApprove = tostring(AgentMetadata.autoApprove),
TrustedProcess = tostring(AgentMetadata.trustedProcess)
| where AutoApprove =~ "true" or TrustedProcess =~ "false"
| project AgentId = tostring(AgentId),
Agent = Name,
Version,
Vendor = tostring(AgentMetadata.vendor),
Account = tostring(AgentMetadata.accountName),
AutoApprove,
TrustedProcess;
let localAgentNodes =
ExposureGraphNodes
| where NodeLabel == "ai-agent"
| where tostring(NodeProperties.rawData.aiAgentMetadata.platform) == "LocalAgents"
| project AgentNodeId = NodeId,
AgentId = tostring(NodeProperties.rawData.aiAgentMetadata.id);
let agentDeviceEdges =
ExposureGraphEdges
| where SourceNodeLabel == "ai-agent"
| where EdgeLabel =~ "runs on"
| where TargetNodeLabel in (deviceLabels)
| project AgentNodeId = SourceNodeId, DeviceId = TargetNodeId,
Device = TargetNodeName, DeviceType = TargetNodeLabel;
let criticalDevices =
ExposureGraphNodes
| where NodeLabel in (deviceLabels)
| where NodeProperties has "criticalityLevel"
| extend CriticalityLevel =
toint(NodeProperties.rawData.criticalityLevel.criticalityLevel)
| where CriticalityLevel between (0 .. 3)
| extend Criticality = case(
CriticalityLevel == 0, "Very high",
CriticalityLevel == 1, "High",
CriticalityLevel == 2, "Medium",
"Low")
| project DeviceId = NodeId, CriticalityLevel, Criticality,
CriticalityReason =
tostring(NodeProperties.rawData.criticalityLevel.ruleNames);
riskyAgentProfiles
| join kind=inner localAgentNodes on AgentId
| join kind=inner agentDeviceEdges on AgentNodeId
| join kind=inner criticalDevices on DeviceId
| project Device, DeviceType, Criticality, CriticalityReason,
Agent, Vendor, Version, Account, AutoApprove, TrustedProcess,
CriticalityLevel
| sort by CriticalityLevel asc, Device asc, Agent asc
| project-away CriticalityLevel
To review every local AI agent on a critical device instead of only the risky ones, remove the where AutoApprove =~ "true" or TrustedProcess =~ "false" line.
Rank the users whose local AI agents reach critical or sensitive assets
This query ranks the identities that use local AI agents by how many critical or sensitive resources they can reach, so you can prioritize the users with the widest blast radius. The UserCriticality column shows whether the identity is itself classified as a critical asset, such as a Global Administrator:
let deviceLabels = dynamic(["device", "ec2.instance",
"microsoft.compute/virtualmachines"]);
let localAgents =
ExposureGraphNodes
| where NodeLabel == "ai-agent"
| where tostring(NodeProperties.rawData.aiAgentMetadata.platform) == "LocalAgents"
| project AgentNodeId = NodeId, AIAgent = NodeName;
let agentDeviceEdges =
ExposureGraphEdges
| where SourceNodeLabel == "ai-agent"
| where EdgeLabel =~ "runs on"
| where TargetNodeLabel in (deviceLabels)
| project AgentNodeId = SourceNodeId, DeviceId = TargetNodeId,
Device = TargetNodeName;
let agentUserEdges =
ExposureGraphEdges
| where SourceNodeLabel == "ai-agent"
| where EdgeLabel =~ "used by"
| project AgentNodeId = SourceNodeId, UserId = TargetNodeId,
User = TargetNodeName;
let userReach =
ExposureGraphEdges
| where EdgeLabel in~ ("has permissions to", "has role on")
| project UserId = SourceNodeId, AssetId = TargetNodeId
| union (
ExposureGraphNodes
| where NodeLabel == "user"
| project UserId = NodeId, AssetId = NodeId
);
let sensitiveAssets =
ExposureGraphNodes
\ No newline at end of file
| where NodeProperties has "criticalityLevel"
or NodeProperties has "containsSensitiveData"
| extend
CriticalityLevel =
toint(NodeProperties.rawData.criticalityLevel.criticalityLevel),
SensitiveDataRaw = tostring(NodeProperties.rawData.containsSensitiveData)
| extend HasSensitiveData =
iff(isnotempty(SensitiveDataRaw) and SensitiveDataRaw !~ "false", "Yes", "No")
| where CriticalityLevel between (0 .. 3) or HasSensitiveData == "Yes"
| extend CriticalityRank =
iff(CriticalityLevel between (0 .. 3), CriticalityLevel, 4)
| project AssetId = NodeId, AssetName = NodeName,
CriticalityRank, HasSensitiveData;
let agentUsers =
localAgents
| join kind=inner agentDeviceEdges on AgentNodeId
| join kind=inner agentUserEdges on AgentNodeId
| project AIAgent, Device, UserId, User;
agentUsers
| join kind=inner userReach on UserId
| join kind=inner sensitiveAssets on AssetId
| summarize AIAgents = make_set(AIAgent, 20),
Devices = make_set(Device, 20),
ReachableAssets = dcountif(AssetId, AssetId != UserId),
SensitiveAssets = dcountif(AssetId,
AssetId != UserId and HasSensitiveData == "Yes"),
Assets = make_set_if(AssetName, AssetId != UserId, 50),
UserRank = minif(CriticalityRank, AssetId == UserId),
AssetRank = minif(CriticalityRank, AssetId != UserId)
by User
| extend UserCriticality = case(
UserRank == 0, "Very high",
UserRank == 1, "High",
UserRank == 2, "Medium",
UserRank == 3, "Low",
"Not classified"),
HighestAssetCriticality = case(
AssetRank == 0, "Very high",
AssetRank == 1, "High",
AssetRank == 2, "Medium",
AssetRank == 3, "Low",
AssetRank == 4, "Sensitive data",
"None")
| extend SortRank = coalesce(AssetRank, 99)
| project User, UserCriticality, ReachableAssets, SensitiveAssets,
HighestAssetCriticality, AIAgents, Devices, Assets, SortRank
| sort by SortRank asc, ReachableAssets desc
| project-away SortRank
@@ -1,10 +1,10 @@ --- title: Discover local AI agents with Microsoft Defender for Endpoint (Preview)-description: Learn how to discover, view, and investigate local AI agents on Windows devices by using Microsoft Defender.+description: Learn how to discover, view, and investigate local AI agents on Windows and macOS devices by using Microsoft Defender. author: lwainstein ms.author: lwainstein ms.service: defender-endpoint-ms.topic: overview+ms.topic: how-to ms.date: 05/27/2026 ai-usage: ai-assisted #customer intent: As a security administrator, I want to discover and monitor local AI agents on my organization's devices so that I can manage AI-related security risks on endpoints.@@ -16,182 +16,436 @@ ai-usage: ai-assisted Microsoft Defender automatically discovers supported local AI agents and MCP servers configured on onboarded devices and surfaces them in the AI agent inventory and advanced hunting. To learn more about local AI agent discovery, see [Local AI agent discovery with Microsoft Defender for Endpoint](local-agent-discovery-overview.md). -In this article, you learn how to view discovered agents in the inventory, explore their relationships with devices and identities, and investigate agent presence using advanced hunting.--> [!NOTE]-> Local AI agent discovery provides discovery and investigation capabilities only, and doesn't include security posture assessment or alerts for endpoint agents.+In this article, you learn how to view discovered agents in the inventory, review their configuration and risk, explore their relationships with devices and identities, and investigate agent presence using advanced hunting. ## Prerequisites Before you can discover local AI agents on endpoints, make sure you meet the following requirements: - Your environment is in the commercial cloud. Sovereign and national clouds aren't supported.-- Your organization has a Microsoft Defender for Endpoint Plan 2, Microsoft 365 E5, Microsoft Agent 365, or Microsoft 365 E7 license.+- Your organization has a Microsoft Defender for Endpoint Plan 2 license. For more information, see [Licensing](#licensing). - Your devices are [onboarded to Microsoft Defender for Endpoint](onboard-configure.md). - Your devices run a supported version of Windows or macOS, and Microsoft Defender Antivirus is updated with current monthly platform and engine updates.-- Your devices are running Microsoft Defender Antivirus in active mode with real-time protection enabled in Windows Security.+- Microsoft Defender Antivirus is running in active mode on your devices, with real-time protection enabled. You don't need additional deployment, configuration, or scripts beyond the device onboarding requirements. If the device meets all of the prerequisites, agent discovery begins automatically. +### Licensing++Microsoft Defender for Endpoint Plan 2 is the minimum license for local AI agent discovery. Security posture capabilities for the agents that Defender discovers require another license, as described in the following table.++| Capability | Required license |+| --- | --- |+| Discover local AI agents and view them in the AI agent inventory, including the agent details, the device and account, and the configured MCP servers | Microsoft Defender for Endpoint Plan 2 |+| Query local AI agents in advanced hunting, including the `AgentsInfo` table | Microsoft Defender for Endpoint Plan 2 |+| View the risk level, risk indicators, and security recommendations for discovered local AI agents | Microsoft 365 E7, or Microsoft Agent 365 together with Microsoft Defender for Endpoint Plan 2 |++Microsoft 365 E5 and Microsoft 365 E7 both include Microsoft Defender for Endpoint Plan 2. If your organization has Microsoft Defender for Endpoint Plan 2 without Microsoft 365 E7 or Microsoft Agent 365, you can discover local AI agents, view the inventory, and query agents in advanced hunting, but the risk level, risk indicators, and security recommendations aren't available.+ ## View local AI agents in the inventory -- To view a list of all local AI agents discovered on endpoints:+1. Sign in to the [Microsoft Defender portal](https://security.microsoft.com/).++1. In the left navigation pane, select **Assets** > **AI agents**.++1. Select the **Local agents** tab to see the local AI agents discovered on your devices.++ :::image type="content" source="media/local-agent-discovery-overview/discovery-overview.png" alt-text="Screenshot showing the local AI agents inventory in the Microsoft Defender portal with discovered agents listed." lightbox="media/local-agent-discovery-overview/discovery-overview.png"::: - 1. Sign in to the [Microsoft Defender portal](https://security.microsoft.com/).+The **Agents insights** cards summarize the total number of monitored agents, the number of agents at a high risk level, and the number of critical agents. - 1. In the left navigation pane, select **Assets** > **AI Agents**.+The list shows one entry for each agent installation, so an agent that runs on several devices, or under several accounts on the same device, appears more than once. Use the filters to narrow the list, select **Customize columns** to change which columns appear, or select **Export** to download the list. - 1. Select **Local agents** to see a filtered list of local AI agents discovered on endpoint devices.- -- To view a specific agent's details:+| Column | Description |+| --- | --- |+| **Agent name**, **Version** | The discovered agent, and the version installed. |+| **Device name**, **Device ID**, **OS platform**, **Device type** | The device where the agent was discovered. |+| **Account name**, **Account domain** | The account that the agent runs under. |+| **Risk level** | The overall risk level of the agent. |+| **Risk indicators** | Why the agent is considered risky, such as **Running on a Critical Device** or **Used by a Critical User**. |+| **Recommendations** | The security recommendations that apply to the agent. |+| **MCP servers**, **Local MCPs** | The number of remote MCP servers, and of local MCP servers, configured for the agent. |+| **First seen** | When the agent was first discovered. | - 1. From the **Local agents** list, select an agent to open the **Agent** pane.- - :::image type="content" source="media/local-agent-discovery-overview/discovery-overview.png" alt-text="Screenshot showing the local AI agents inventory in the Microsoft Defender portal with discovered agents listed." lightbox="media/local-agent-discovery-overview/discovery-overview.png":::+> [!NOTE]+> Risk levels, risk indicators, and security recommendations require Microsoft 365 E7 or Microsoft Agent 365. Without one of these licenses, you can still view the inventory and the details of each agent. For more information, see [Licensing](#licensing).++### View the details of a local AI agent++From the **Local agents** list, select an agent to open its details pane:++- **Details** shows the agent name, vendor, model, related process, whether the host process is trusted, whether the agent automatically approves its own actions, and the source agent ID.+- **Risk** shows the risk level, the risk indicators, and the security recommendations that apply to the agent.+- **Device details** shows the device where the agent was discovered, including the OS platform and version, the device type and roles, and the Microsoft Entra device ID.+- **User details** shows the ID, name, and domain of the account that the agent runs under.+- **MCP servers** lists the MCP servers configured for the agent, with the name, type, and endpoint of each server.+- **Local MCP servers** lists the local MCP entries discovered on the device.++To investigate further, select **Go hunt** to query the agent in advanced hunting, **View on map** to see the agent in the attack surface map, or **Open Agent page** to open the full agent page. - 1. Review the agent details, including:+### Review an agent's attack surface and recommendations - - Agent name, version, and related process- - Associated device and user- - First seen and last updated timestamps- - Integrity level, auto-approve status, and trust indicator- - Configured MCP servers, when detected+The agent page has an **Overview** tab and a **Security recommendations** tab. The **Attack surface** map on the **Overview** tab shows the other agents, devices, identities, and resources associated with the agent. - For more information on using the AI agent inventory, see [Discover AI agents and assess security posture using Microsoft Defender](/defender-xdr/security-for-ai/ai-agent-inventory).+For more information on using the AI agent inventory, see [Discover AI agents and assess security posture using Microsoft Defender](/defender-xdr/security-for-ai/ai-agent-inventory). ## Query local AI agents using advanced hunting -Use advanced hunting to proactively investigate local AI agent presence, understand which users and resources agents can reach, and identify high-risk configurations. These queries help you assess exposure, trace access paths to sensitive assets, and prioritize security response.+Use advanced hunting to proactively investigate local AI agent presence, understand how agents are configured, and identify the agents and users that carry the most risk. These queries help you inventory agents, review the MCP servers they connect to, and trace access to critical or sensitive assets.++### Understand the tables++Three advanced hunting tables describe local AI agents. Each answers a different question, and they're most useful together.++| Table | What it contains | Use it to answer |+| --- | --- | --- |+| [AgentsInfo](/defender-xdr/advanced-hunting-agentsinfo-table) | A profile record for every AI agent that Microsoft Defender discovers, across all agent platforms. For local AI agents, the record includes the publisher, version, host process, trust and auto-approve settings, configured MCP servers, and the device and account where the agent was seen. | What is this agent, and how is it configured? |+| [ExposureGraphNodes](/defender-xdr/advanced-hunting-exposuregraphnodes-table) | Every entity in your organization as a node, including AI agents, devices, identities, and cloud resources, along with properties such as asset criticality and whether the entity holds sensitive data. | What is this entity, and how much does it matter? |+| [ExposureGraphEdges](/defender-xdr/advanced-hunting-exposuregraphedges-table) | The relationships between nodes, such as the device an agent runs on, or the resources an identity can access. | What can this agent reach? |++In short, `AgentsInfo` describes what an agent *is*, and the exposure graph describes what an agent can *reach*.++Both data sources contain agents from every platform, including cloud agents, so each one needs its own filter.++In `AgentsInfo`, filter on the `Platform` column:++```kusto+AgentsInfo+| where Platform == "LocalAgents"+```++In `ExposureGraphNodes`, AI agents use the `ai-agent` node label. Filter on the platform reported in the node properties:++```kusto+ExposureGraphNodes+| where NodeLabel == "ai-agent"+| where tostring(NodeProperties.rawData.aiAgentMetadata.platform) == "LocalAgents"+```++> [!IMPORTANT]+> The `ExposureGraphEdges` table doesn't include a property that identifies local AI agents. Filtering only on `SourceNodeLabel == "ai-agent"` returns edges for every AI agent in your tenant, including cloud agents. Always resolve the local agent set from `ExposureGraphNodes` first, and then join to `ExposureGraphEdges` on the node ID, as shown in the following queries.++### Combine the tables++An agent has two identifiers, and queries that combine the tables need both:++- `AgentId` identifies the agent profile in `AgentsInfo`. The exposure graph stores the same value in the agent node, as `NodeProperties.rawData.aiAgentMetadata.id`.+- `NodeId` identifies the agent's node in the exposure graph. This is the value that `ExposureGraphEdges` refers to, in `SourceNodeId` and `TargetNodeId`.++| To go from | To | Match on |+| --- | --- | --- |+| `AgentsInfo` | `ExposureGraphNodes` | `tostring(AgentId)` and `tostring(NodeProperties.rawData.aiAgentMetadata.id)` |+| `ExposureGraphNodes` | `ExposureGraphEdges` | `NodeId` and `SourceNodeId` or `TargetNodeId` |++> [!NOTE]+> `AgentId` is a `guid` column in `AgentsInfo`, but the exposure graph stores the same value as a string. Convert it with `tostring()` on both sides of the join. A join between columns of different types doesn't match any rows.++Local AI agent nodes connect to the rest of the exposure graph through the following edges: -The [ExposureGraphNodes](/defender-xdr/advanced-hunting-exposuregraphnodes-table) and [ExposureGraphEdges](/defender-xdr/advanced-hunting-exposuregraphedges-table) tables represent the security graph for local AI agents, including entities and relationships.+| Edge label | Target node label | Description |+| --- | --- | --- |+| `runs on` | `device`, `ec2.instance`, `microsoft.compute/virtualmachines` | The device where the agent was discovered. |+| `uses` | `mcp/server` | An MCP server that's configured for the agent. |+| `used by` | `user` | The identity that uses the agent. |++Cloud AI agents also use a `can authenticate as` edge that points to a service principal or a Microsoft Entra OAuth app. Local AI agents don't use that edge, so use `used by` to resolve the identity behind a local AI agent.++Asset criticality is stored in `ExposureGraphNodes`, in `NodeProperties.rawData.criticalityLevel.criticalityLevel`, where `0` is the highest level (very high) and `3` is the lowest (low). The `ruleNames` property lists the classification rules that made the asset critical.++### Work with local AI agent profiles++`AgentsInfo` adds a record each time an agent profile is updated, so a single agent usually has several records. To return only the most recent record for each agent, summarize with `arg_max` on `Timestamp`:++```kusto+AgentsInfo+| where Platform == "LocalAgents"+| summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo) by AgentId+```++`LifecycleStatus` reports whether the agent is still present on the device. An agent that's reinstalled can also be reissued with a new `AgentId`, which leaves the earlier record marked as `Deleted`, so filter out `Deleted` and `Uninstalled` records when you want a current inventory.++Many `AgentsInfo` columns describe cloud agents and are empty for local AI agents. The columns that carry local AI agent data are `AgentId`, `Name`, `Version`, `PublishedStatus`, `LifecycleStatus`, `LastUpdatedDateTime`, `McpServers`, `DeclaredTools`, and `RawAgentInfo`.++Local AI agent posture is nested in the `RawAgentInfo` column, under `localAgentMetadata`:++| Property | Description |+| --- | --- |+| `vendor` | The publisher of the agent, such as Anthropic, Google, or OpenAI. |+| `relatedProcess` | The process that hosts the agent, such as `code.exe`. |+| `trustedProcess` | Whether the host process is trusted. Reported as the string `"true"` or `"false"`. |+| `autoApprove` | Whether the agent acts without prompting the user for approval. Reported as the string `"true"` or `"false"`. |+| `deviceName`, `aadDeviceId` | The device where the agent was discovered. |+| `accountName`, `accountDomain`, `accountSid` | The account that the agent ran under. |+| `localMcps` | MCP servers that run locally on the device, including the command that starts each one. | > [!NOTE]-> The following queries use the `ExposureGraphEdges` table to query the device associated with the AI agent. You can also use the `ExposureGraphNodes` table to query the actual agent.+> `trustedProcess` and `autoApprove` are reported as strings, not as boolean values. Compare them to `"true"` or `"false"` rather than using `tobool()`.++Each of the following queries is self-contained. The queries that use the exposure graph start with a set of `let` statements that resolve local AI agents and the devices they run on. -### Get an inventory of AI agents across endpoints+### Get an inventory of local AI agents -This query lists all discovered local AI agents and the devices they run on:+This query lists the local AI agents discovered in your organization, together with the publisher, the host process, the versions in use, and how widely each agent is deployed: ```kusto-ExposureGraphEdges-| where SourceNodeLabel == "endpointAiAgent"-| where EdgeLabel =~ "runs on"-| summarize Devices = make_set(TargetNodeName),- DeviceCount = dcount(TargetNodeName)- by AIAgent = SourceNodeName-| sort by DeviceCount desc+AgentsInfo+| where Platform == "LocalAgents"+| summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo)+ by AgentId+| where LifecycleStatus !in~ ("Deleted", "Uninstalled")+| extend AgentMetadata = RawAgentInfo.localAgentMetadata+| extend Vendor = tostring(AgentMetadata.vendor),+ Process = tostring(AgentMetadata.relatedProcess),+ Device = tostring(AgentMetadata.deviceName),+ Account = tostring(AgentMetadata.accountName)+| summarize Installations = count(),+ DeviceCount = dcount(Device),+ Devices = make_set(Device, 100),+ Versions = make_set(Version, 20),+ Accounts = make_set_if(Account, isnotempty(Account), 50)+ by Agent = Name, Vendor, Process+| sort by DeviceCount desc, Installations desc ``` -### Map AI agents to users+Each `AgentId` represents one agent profile, which is a single agent on a single device for a single account. `Installations` counts those profiles, so an agent that two people use on the same device counts twice, while `DeviceCount` counts the device once. -This query maps local AI agents to the users with credentials on the devices they run on:+### Review the MCP servers and tools that local AI agents use++This query lists the MCP servers and tools configured for local AI agents, the agents and devices that use them, and where each one runs: ```kusto-let accessEdges = dynamic(["contains", "has credentials of", "has permissions to",- "has role on", "can authenticate as", "can authenticate to"]);-ExposureGraphEdges-| where SourceNodeLabel == "endpointAiAgent"-| project AIAgent = SourceNodeName, DeviceId = TargetNodeId, Device = TargetNodeName-| join kind=inner (- ExposureGraphEdges- | where EdgeLabel in (accessEdges)- | where TargetNodeLabel in ("device", "ec2.instance", "microsoft.compute/virtualmachines")- | project UserId = SourceNodeId, User = SourceNodeName,- UserType = SourceNodeLabel, DeviceId = TargetNodeId-) on DeviceId-| summarize AIAgents = make_set(AIAgent) by Device, User, UserType-| sort by Device asc+let localAgentProfiles =+ AgentsInfo+ | where Platform == "LocalAgents"+ | extend AgentMetadata = RawAgentInfo.localAgentMetadata+ | project Timestamp,+ Agent = Name,+ Device = tostring(AgentMetadata.deviceName),+ Account = tostring(AgentMetadata.accountName),+ McpServers,+ DeclaredTools,+ LocalServers = AgentMetadata.localMcps;+let remoteMcpServers =+ localAgentProfiles+ | mv-expand Server = McpServers+ | project Timestamp, Agent, Device, Account,+ McpServer = tostring(Server.name),+ Origin = "Remote MCP server",+ Transport = tostring(Server.type),+ Location = tostring(Server.endpoint);+let agentDeclaredTools =+ localAgentProfiles+ | mv-expand Tool = DeclaredTools+ | project Timestamp, Agent, Device, Account,+ McpServer = tostring(Tool.name),+ Origin = "Declared tool",+ Transport = tostring(Tool.type),+ Location = tostring(Tool.endpoint);+let localMcpServers =+ localAgentProfiles+ | mv-expand Server = LocalServers+ | project Timestamp, Agent, Device, Account,+ McpServer = tostring(Server.name),+ Origin = "Local MCP server",+ Transport = tostring(Server.transportType),+ Location = tostring(Server.commandName);+remoteMcpServers+| union agentDeclaredTools, localMcpServers+| where isnotempty(McpServer)+| summarize LastSeen = max(Timestamp),+ Agents = make_set(Agent, 20),+ Devices = make_set(Device, 20),+ Accounts = make_set_if(Account, isnotempty(Account), 20)+ by McpServer, Origin, Transport, Location+| sort by McpServer asc, Origin asc ``` -### Find AI agents on devices of users with broad access+The `Origin` column distinguishes the three ways a server or tool is reported: -This query identifies which users on local AI agent devices have permissions to the most resources, helping you prioritize risk based on scope of access:+- A **remote MCP server** is reached over the network, and `Location` holds its endpoint.+- A **local MCP server** runs as a process on the device, and `Location` holds the command that starts it. Local MCP servers are reported only in `AgentsInfo`.+- A **declared tool** is a tool the agent advertises. Declared tools are often backed by an MCP server, but they don't always report an endpoint.++Unlike the other queries, this one reads every profile record for each agent, so that MCP servers reported at any point are included. The `LastSeen` column shows when each server was last reported.++### Find local AI agents with risky configurations++This query returns local AI agents that act without asking the user for approval, or that run in a host process that isn't trusted: ```kusto-let accessEdges = dynamic(["contains", "has credentials of", "has permissions to",- "has role on", "can authenticate as", "can authenticate to",- "member of", "can impersonate as"]);-let biEdges =- ExposureGraphEdges- | where EdgeLabel in (accessEdges)- | project src = SourceNodeId, tgt = TargetNodeId,- srcName = SourceNodeName, tgtName = TargetNodeName, edge = EdgeLabel- | union (- ExposureGraphEdges- | where EdgeLabel in (accessEdges)- | project src = TargetNodeId, tgt = SourceNodeId,- srcName = TargetNodeName, tgtName = SourceNodeName, edge = EdgeLabel- );-let userAccess =+AgentsInfo+| where Platform == "LocalAgents"+| summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo)+ by AgentId+| where LifecycleStatus !in~ ("Deleted", "Uninstalled")+| extend AgentMetadata = RawAgentInfo.localAgentMetadata+| extend AutoApprove = tostring(AgentMetadata.autoApprove),+ TrustedProcess = tostring(AgentMetadata.trustedProcess),+ Vendor = tostring(AgentMetadata.vendor),+ Process = tostring(AgentMetadata.relatedProcess),+ Device = tostring(AgentMetadata.deviceName),+ Account = tostring(AgentMetadata.accountName)+| where AutoApprove =~ "true" or TrustedProcess =~ "false"+| extend RiskReason = case(+ AutoApprove =~ "true" and TrustedProcess =~ "false",+ "Acts without approval, and the host process isn't trusted",+ AutoApprove =~ "true",+ "Acts without approval",+ "The host process isn't trusted")+| project Agent = Name, Vendor, Version, Process, Device, Account,+ AutoApprove, TrustedProcess, RiskReason, LastSeen = Timestamp+| sort by Device asc, Agent asc+```++An agent that auto-approves its own actions runs tools and reaches resources without a person confirming each step, so the account and device that the agent runs on define what it can do unsupervised.++### Find risky local AI agents on critical devices++This query combines agent configuration from `AgentsInfo` with asset criticality from the exposure graph, so you can start with the agents that both act unsupervised and run on business-critical devices:++```kusto+let deviceLabels = dynamic(["device", "ec2.instance",+ "microsoft.compute/virtualmachines"]);+let riskyAgentProfiles =+ AgentsInfo+ | where Platform == "LocalAgents"+ | summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo)+ by AgentId+ | where LifecycleStatus !in~ ("Deleted", "Uninstalled")+ | extend AgentMetadata = RawAgentInfo.localAgentMetadata+ | extend AutoApprove = tostring(AgentMetadata.autoApprove),+ TrustedProcess = tostring(AgentMetadata.trustedProcess)+ | where AutoApprove =~ "true" or TrustedProcess =~ "false"+ | project AgentId = tostring(AgentId),+ Agent = Name,+ Version,+ Vendor = tostring(AgentMetadata.vendor),+ Account = tostring(AgentMetadata.accountName),+ AutoApprove,+ TrustedProcess;+let localAgentNodes =+ ExposureGraphNodes+ | where NodeLabel == "ai-agent"+ | where tostring(NodeProperties.rawData.aiAgentMetadata.platform) == "LocalAgents"+ | project AgentNodeId = NodeId,+ AgentId = tostring(NodeProperties.rawData.aiAgentMetadata.id);+let agentDeviceEdges = ExposureGraphEdges- | where SourceNodeLabel == "user"- | where EdgeLabel in ("has permissions to", "has role on")- | summarize - ResourceCount = dcount(TargetNodeId),- ResourceTypes = make_set(TargetNodeLabel)- by UserId = SourceNodeId, User = SourceNodeName;-ExposureGraphEdges-| where SourceNodeLabel == "endpointAiAgent"-| project AIAgent = SourceNodeName, n1 = TargetNodeId, Device = TargetNodeName-| join kind=inner (- biEdges | project n1 = src, UserId = tgt, User = tgtName-) on n1-| join kind=inner userAccess on UserId-| summarize AIAgents = make_set(AIAgent) by Device, User, ResourceCount,- tostring(ResourceTypes)-| sort by ResourceCount desc+ | where SourceNodeLabel == "ai-agent"+ | where EdgeLabel =~ "runs on"+ | where TargetNodeLabel in (deviceLabels)+ | project AgentNodeId = SourceNodeId, DeviceId = TargetNodeId,+ Device = TargetNodeName, DeviceType = TargetNodeLabel;+let criticalDevices =+ ExposureGraphNodes+ | where NodeLabel in (deviceLabels)+ | where NodeProperties has "criticalityLevel"+ | extend CriticalityLevel =+ toint(NodeProperties.rawData.criticalityLevel.criticalityLevel)+ | where CriticalityLevel between (0 .. 3)+ | extend Criticality = case(+ CriticalityLevel == 0, "Very high",+ CriticalityLevel == 1, "High",+ CriticalityLevel == 2, "Medium",+ "Low")+ | project DeviceId = NodeId, CriticalityLevel, Criticality,+ CriticalityReason =+ tostring(NodeProperties.rawData.criticalityLevel.ruleNames);+riskyAgentProfiles+| join kind=inner localAgentNodes on AgentId+| join kind=inner agentDeviceEdges on AgentNodeId+| join kind=inner criticalDevices on DeviceId+| project Device, DeviceType, Criticality, CriticalityReason,+ Agent, Vendor, Version, Account, AutoApprove, TrustedProcess,+ CriticalityLevel+| sort by CriticalityLevel asc, Device asc, Agent asc+| project-away CriticalityLevel ``` -### Find AI agents with paths to critical or sensitive assets+To review every local AI agent on a critical device instead of only the risky ones, remove the `where AutoApprove =~ "true" or TrustedProcess =~ "false"` line. -This query traces access paths from local AI agents through the exposure graph to resources marked as critical or containing sensitive data:+### Rank the users whose local AI agents reach critical or sensitive assets++This query ranks the identities that use local AI agents by how many critical or sensitive resources they can reach, so you can prioritize the users with the widest blast radius. The `UserCriticality` column shows whether the identity is itself classified as a critical asset, such as a Global Administrator: ```kusto-let accessEdges = dynamic(["contains", "has credentials of", "has permissions to",- "has role on", "can authenticate as", "can authenticate to",- "member of", "can impersonate as"]);-let biEdges =+let deviceLabels = dynamic(["device", "ec2.instance",+ "microsoft.compute/virtualmachines"]);+let localAgents =+ ExposureGraphNodes+ | where NodeLabel == "ai-agent"+ | where tostring(NodeProperties.rawData.aiAgentMetadata.platform) == "LocalAgents"+ | project AgentNodeId = NodeId, AIAgent = NodeName;+let agentDeviceEdges = ExposureGraphEdges- | where EdgeLabel in (accessEdges)- | project src = SourceNodeId, tgt = TargetNodeId,- srcName = SourceNodeName, tgtName = TargetNodeName, edge = EdgeLabel+ | where SourceNodeLabel == "ai-agent"+ | where EdgeLabel =~ "runs on"+ | where TargetNodeLabel in (deviceLabels)+ | project AgentNodeId = SourceNodeId, DeviceId = TargetNodeId,+ Device = TargetNodeName;+let agentUserEdges =+ ExposureGraphEdges+ | where SourceNodeLabel == "ai-agent"+ | where EdgeLabel =~ "used by"+ | project AgentNodeId = SourceNodeId, UserId = TargetNodeId,+ User = TargetNodeName;+let userReach =+ ExposureGraphEdges+ | where EdgeLabel in~ ("has permissions to", "has role on")+ | project UserId = SourceNodeId, AssetId = TargetNodeId | union (- ExposureGraphEdges- | where EdgeLabel in (accessEdges)- | project src = TargetNodeId, tgt = SourceNodeId,- srcName = TargetNodeName, tgtName = SourceNodeName, edge = EdgeLabel+ ExposureGraphNodes+ | where NodeLabel == "user"+ | project UserId = NodeId, AssetId = NodeId ); let sensitiveAssets = ExposureGraphNodes- | extend CriticalityLevel = toint(NodeProperties.rawData.criticalityLevel.criticalityLevel)- | extend HasSensitiveData = iff(isnotempty(NodeProperties.rawData.containsSensitiveData),- "Yes", "No")- | extend CriticalityReason = tostring(NodeProperties.rawData.criticalityLevel.ruleNames)- | where CriticalityLevel > 0 or HasSensitiveData == "Yes"- | extend Criticality = case(- CriticalityLevel == 1, "Critical",- CriticalityLevel == 2, "High",- CriticalityLevel == 3, "Medium",- CriticalityLevel == 4, "Low",- "Sensitive Data"- )- | project AssetId = NodeId, AssetName = NodeName, AssetType = NodeLabel,- Criticality, HasSensitiveData, CriticalityReason;-ExposureGraphEdges-| where SourceNodeLabel == "endpointAiAgent"-| project AIAgent = SourceNodeName, n1 = TargetNodeId, Device = TargetNodeName-| join kind=inner (- biEdges | project n1 = src, n2 = tgt, Hop1 = tgtName, Via1 = edge-) on n1-| join kind=inner (- biEdges | project n2 = src, n3 = tgt, Via2 = edge-) on n2-| join kind=inner (- sensitiveAssets | project n3 = AssetId, AssetName, AssetType,- Criticality, HasSensitiveData, CriticalityReason-) on n3-| summarize ExposedVia = make_set(Hop1) by- AIAgent, Device, AssetName, AssetType,- Criticality, HasSensitiveData, CriticalityReason-| sort by Criticality asc, HasSensitiveData desc-```\ No newline at end of file+ | where NodeProperties has "criticalityLevel"+ or NodeProperties has "containsSensitiveData"+ | extend+ CriticalityLevel =+ toint(NodeProperties.rawData.criticalityLevel.criticalityLevel),+ SensitiveDataRaw = tostring(NodeProperties.rawData.containsSensitiveData)+ | extend HasSensitiveData =+ iff(isnotempty(SensitiveDataRaw) and SensitiveDataRaw !~ "false", "Yes", "No")+ | where CriticalityLevel between (0 .. 3) or HasSensitiveData == "Yes"+ | extend CriticalityRank =+ iff(CriticalityLevel between (0 .. 3), CriticalityLevel, 4)+ | project AssetId = NodeId, AssetName = NodeName,+ CriticalityRank, HasSensitiveData;+let agentUsers =+ localAgents+ | join kind=inner agentDeviceEdges on AgentNodeId+ | join kind=inner agentUserEdges on AgentNodeId+ | project AIAgent, Device, UserId, User;+agentUsers+| join kind=inner userReach on UserId+| join kind=inner sensitiveAssets on AssetId+| summarize AIAgents = make_set(AIAgent, 20),+ Devices = make_set(Device, 20),+ ReachableAssets = dcountif(AssetId, AssetId != UserId),+ SensitiveAssets = dcountif(AssetId,+ AssetId != UserId and HasSensitiveData == "Yes"),+ Assets = make_set_if(AssetName, AssetId != UserId, 50),+ UserRank = minif(CriticalityRank, AssetId == UserId),+ AssetRank = minif(CriticalityRank, AssetId != UserId)+ by User+| extend UserCriticality = case(+ UserRank == 0, "Very high",+ UserRank == 1, "High",+ UserRank == 2, "Medium",+ UserRank == 3, "Low",+ "Not classified"),+ HighestAssetCriticality = case(+ AssetRank == 0, "Very high",+ AssetRank == 1, "High",+ AssetRank == 2, "Medium",+ AssetRank == 3, "Low",+ AssetRank == 4, "Sensitive data",+ "None")+| extend SortRank = coalesce(AssetRank, 99)+| project User, UserCriticality, ReachableAssets, SensitiveAssets,+ HighestAssetCriticality, AIAgents, Devices, Assets, SortRank+| sort by SortRank asc, ReachableAssets desc+| project-away SortRank+``` 