Individuare gli agenti di intelligenza artificiale locali con Microsoft Defender per endpoint (anteprima)

Importante

Alcune informazioni in questo articolo fanno riferimento alle caratteristiche di un prodotto prima del rilascio, che possono essere modificate sostanzialmente prima della distribuzione al pubblico. Microsoft non fornisce alcuna garanzia, esplicita o implicita, in relazione alle informazioni contenute in questo documento.

Microsoft Defender individua automaticamente gli agenti di intelligenza artificiale locali supportati e i server MCP configurati nei dispositivi caricati e li inserisce nell'inventario degli agenti di intelligenza artificiale e nella ricerca avanzata. Per altre informazioni sull'individuazione dell'agente di intelligenza artificiale locale, vedere Individuazione dell'agente di intelligenza artificiale locale con Microsoft Defender per endpoint.

In questo articolo impari come visualizzare gli agenti scoperti nell'inventario, esaminarne la configurazione e il rischio, esplorare le loro relazioni con dispositivi e identità e indagare sulla presenza degli agenti utilizzando la caccia avanzata.

Prerequisiti

Prima di individuare gli agenti di intelligenza artificiale locali negli endpoint, assicurarsi di soddisfare i requisiti seguenti:

  • L'ambiente è nel cloud commerciale. I cloud sovrani e nazionali non sono supportati.
  • La tua organizzazione ha una licenza Piano 2 di Microsoft Defender per endpoint. Per altre informazioni, vedere Licenze.
  • I dispositivi sono registrati in Microsoft Defender per endpoint.
  • I dispositivi eseguono una versione supportata di Windows o macOS e Microsoft Defender Antivirus viene aggiornato con gli aggiornamenti mensili correnti della piattaforma e del motore.
  • Microsoft Defender Antivirus è in modalità attiva sui tuoi dispositivi, con la protezione in tempo reale attivata.

Non sono necessarie distribuzioni, configurazioni o script aggiuntivi oltre ai requisiti di onboarding del dispositivo. Se il dispositivo soddisfa tutti i prerequisiti, l'individuazione dell'agente viene avviata automaticamente.

Licenze

Microsoft Defender per endpoint Plan 2 è la licenza minima per la scoperta di agenti AI locali. Le funzionalità relative al livello di sicurezza per gli agenti individuati da Defender richiedono un'altra licenza, come descritto nella tabella seguente.

Capability Licenza necessaria
Scopri gli agenti AI locali e visualizzali nell'inventario degli agenti AI, inclusi i dettagli dell'agente, il dispositivo e l'account, e i server MCP configurati Microsoft Defender per Endpoint Piano 2
Interroga gli agenti di intelligenza artificiale locali nella ricerca avanzata, compresa la tabella AgentsInfo Microsoft Defender per Endpoint Piano 2
Visualizza il livello di rischio, gli indicatori di rischio e le raccomandazioni di sicurezza per gli agenti AI locali scoperti Microsoft 365 E7, o Microsoft Agent 365 insieme a Microsoft Defender per endpoint Plan 2

Microsoft 365 E5 e Microsoft 365 E7 includono entrambi Microsoft Defender per endpoint Plan 2. Se la tua organizzazione ha Microsoft Defender per endpoint Plan 2 senza Microsoft 365 E7 o Microsoft Agent 365, puoi scoprire agenti AI locali, visualizzare l'inventario e interrogare gli agenti in advanced hunting, ma il livello di rischio, gli indicatori di rischio e le raccomandazioni di sicurezza non sono disponibili.

Visualizzare gli agenti di intelligenza artificiale locali nell'inventario

  1. Accedere al portale di Microsoft Defender.

  2. Nel riquadro di navigazione sinistro, seleziona Asset>agenti di intelligenza artificiale.

  3. Seleziona la scheda Agenti Locali per vedere gli agenti AI locali scoperti sui tuoi dispositivi.

    Screenshot che mostra l'inventario degli agenti di intelligenza artificiale locali nel portale di Microsoft Defender con gli agenti individuati elencati.

Le schede di insight degli agenti riassumono il numero totale di agenti monitorati, il numero di agenti ad alto rischio e il numero di agenti critici.

L'elenco mostra una voce per ogni installazione dell'agente, quindi un agente che è eseguito su più dispositivi o con più account sullo stesso dispositivo appare più di una volta. Usa i filtri per restringere la lista, seleziona Personalizza colonne per cambiare quali colonne appaiono, oppure seleziona Esporta per scaricare la lista.

Column Descrzione
Nome dell'agente, Versione L'agente individuato e la versione installata.
Nome dispositivo, ID dispositivo, piattaforma del sistema operativo, tipo di dispositivo Il dispositivo dove l'agente è stato scoperto.
Nome dell'account, Dominio dell'account L'account sotto cui gestisce l'agente.
Livello di rischio Il livello complessivo di rischio dell'agente.
Indicatori di rischio Perché l'agente è considerato rischioso, come l'esecuzione su un dispositivo critico o l'uso da un utente critico.
Raccomandazioni Le raccomandazioni di sicurezza che si applicano all'agente.
Server MCP, MCP locali Il numero di server MCP remoti e di server MCP locali configurati per l'agente.
Prima vista Quando l'agente fu scoperto per la prima volta.

Nota

I livelli di rischio, gli indicatori di rischio e le raccomandazioni di sicurezza richiedono Microsoft 365 E7 o Microsoft Agent 365. Senza una di queste licenze, puoi comunque visualizzare l'inventario e i dettagli di ogni agente. Per altre informazioni, vedere Licenze.

Visualizza i dettagli di un agente AI locale

Dall'elenco degli agenti locali , seleziona un agente per aprire il suo pannello dettagli:

  • I dettagli mostrano il nome dell'agente, il fornitore, il modello, il processo correlato, se il processo host è affidabile, se l'agente approva automaticamente le proprie azioni e l'ID dell'agente sorgente.
  • Il rischio mostra il livello di rischio, gli indicatori di rischio e le raccomandazioni di sicurezza che si applicano all'agente.
  • I dettagli del dispositivo mostrano il dispositivo in cui l'agente è stato scoperto, inclusi la piattaforma e la versione del sistema operativo, il tipo e i ruoli del dispositivo, e l'ID dispositivo Microsoft Entra.
  • I dati utente mostrano l'ID, il nome e il dominio dell'account con cui l'agente opera.
  • I server MCP elencano i server MCP configurati per l'agente, con nome, tipo e endpoint di ciascun server.
  • Server MCP locali elenca le voci MCP locali rilevate sul dispositivo.

Per approfondire, seleziona Go hunt per eseguire una query sull'agente nella ricerca avanzata, Visualizza sulla mappa per vedere l'agente nella mappa della superficie di attacco, oppure Apri pagina agente per aprire la pagina completa dell'agente.

Esamina la superficie d'attacco e le raccomandazioni di un agente

La pagina dell'agente ha una scheda Panoramica e una scheda Raccomandazioni di Sicurezza . La mappa della superficie d'attacco nella scheda Panoramica mostra gli altri agenti, dispositivi, identità e risorse associate all'agente.

Per altre informazioni sull'uso dell'inventario degli agenti di intelligenza artificiale, vedere Individuare gli agenti di intelligenza artificiale e valutare il comportamento di sicurezza usando Microsoft Defender.

Eseguire query su agenti di intelligenza artificiale locali usando la ricerca avanzata

Usa la ricerca avanzata per indagare proattivamente la presenza di agenti AI locali, comprendere come sono configurati gli agenti e identificare gli agenti e gli utenti che comportano il maggior rischio. Queste query ti aiutano a inventariare gli agenti, a esaminare i server MCP a cui si collegano e a tracciare l'accesso a asset critici o sensibili.

Comprendere le tabelle

Tre tabelle di caccia avanzate descrivono gli agenti AI locali. Ognuno risponde a una domanda diversa, e sono più utili insieme.

Table Che cosa contiene Usalo per rispondere
AgentsInfo Un record del profilo per ogni agente di intelligenza artificiale rilevato da Microsoft Defender, in tutte le piattaforme per agenti. Per gli agenti AI locali, il record include l'editore, la versione, il processo host, le impostazioni di fiducia e auto-approvazione, i server MCP configurati, e il dispositivo e l'account in cui l'agente è stato visto. Cos'è questo agente e come è configurato?
ExposureGraphNodes Ogni entità nella tua organizzazione come nodo, inclusi agenti IA, dispositivi, identità e risorse cloud, insieme a proprietà come la criticità degli asset e se l'entità detiene dati sensibili. Cos'è questa entità e quanto conta?
ExposureGraphEdges Le relazioni tra i nodi, come il dispositivo su cui viene eseguito un agente o le risorse a cui un'identità può accedere. Cosa può raggiungere questo agente?

In breve, AgentsInfo descrive cos'è un agente, e il grafico di esposizione descrive ciò che un agente può raggiungere.

Entrambe le fonti di dati contengono agenti da ogni piattaforma, inclusi gli agenti cloud, quindi ognuna ha bisogno del proprio filtro.

In AgentsInfo, filtra in base alla colonna Platform:

AgentsInfo
| where Platform == "LocalAgents"

In ExposureGraphNodes, gli agenti di IA usano l’etichetta di nodo ai-agent. Filtro sulla piattaforma riportato nelle proprietà del nodo:

ExposureGraphNodes
| where NodeLabel == "ai-agent"
| where tostring(NodeProperties.rawData.aiAgentMetadata.platform) == "LocalAgents"

Importante

La ExposureGraphEdges tabella non include una proprietà che identifichi gli agenti AI locali. Filtrando solo in base a SourceNodeLabel == "ai-agent", vengono restituiti gli edge per tutti gli agenti di intelligenza artificiale del tenant, inclusi gli agenti cloud. Ricava sempre prima il set di agenti locali da ExposureGraphNodes, quindi esegui il join con ExposureGraphEdges in base all'ID del nodo, come mostrato nelle query seguenti.

Combina le tabelle

Un agente ha due identificatori, e le query che combinano le tabelle necessitano di entrambi:

  • AgentId identifica il profilo dell'agente in AgentsInfo. Il grafo di esposizione memorizza lo stesso valore nel nodo agente, come NodeProperties.rawData.aiAgentMetadata.id.
  • NodeId identifica il nodo dell'agente nel grafo di esposizione. Questo è il valore che ExposureGraphEdges si riferisce a, in SourceNodeId e TargetNodeId.
Da partire Per Corrispondenza su
AgentsInfo ExposureGraphNodes tostring(AgentId) e tostring(NodeProperties.rawData.aiAgentMetadata.id)
ExposureGraphNodes ExposureGraphEdges NodeId e SourceNodeId o TargetNodeId

Nota

AgentId è una guid colonna in AgentsInfo, ma il grafico di esposizione memorizza lo stesso valore di una stringa. Convertilo con tostring() su entrambi i lati del join. Una giunzione tra colonne di tipi diversi non corrisponde a nessuna riga.

I nodi degli agenti di IA locali si collegano al resto del grafo di esposizione tramite i seguenti archi:

Etichetta del bordo Etichetta del nodo di destinazione Descrzione
runs on device, ec2.instance, microsoft.compute/virtualmachines Il dispositivo dove l'agente è stato scoperto.
uses mcp/server Un server MCP configurato per l'agente.
used by user L'identità che utilizza l'agente.

Gli agenti cloud AI utilizzano anche un can authenticate as edge che punta a un service principal o a un'app Microsoft Entra OAuth. Gli agenti locali di IA non usano quel vantaggio, quindi usano used by per risolvere l'identità dietro un agente di IA locale.

La criticità degli asset è immagazzinata in ExposureGraphNodes, in NodeProperties.rawData.criticalityLevel.criticalityLevel, dove 0 è il livello più alto (molto alto) e 3 il più basso (basso). La ruleNames proprietà elenca le regole di classificazione che hanno reso l'asset critico.

Lavora con i profili locali degli agenti AI

AgentsInfo Aggiunge un record ogni volta che un profilo di agente viene aggiornato, quindi un singolo agente di solito ha più record. Per restituire solo il record più recente per ogni agente, riassumi con arg_max su Timestamp:

AgentsInfo
| where Platform == "LocalAgents"
| summarize arg_max(Timestamp, Name, Version, LifecycleStatus, RawAgentInfo) by AgentId

LifecycleStatus Segnala se l'agente è ancora presente sul dispositivo. Un agente che viene reinstallato può anche essere riemesso con un nuovo AgentId, che lascia il record precedente contrassegnato come Deleted, quindi filtra i record Deleted e Uninstalled quando desideri un inventario aggiornato.

Molte AgentsInfo colonne descrivono gli agenti cloud e sono vuote per gli agenti AI locali. Le colonne che contengono i dati degli agenti IA locali sono AgentId, Name, Version, PublishedStatus, LifecycleStatus, LastUpdatedDateTime, McpServers, DeclaredTools e RawAgentInfo.

La postura dell'agente IA locale è annidata nella RawAgentInfo colonna, sotto localAgentMetadata:

Proprietà Descrzione
vendor L'editore dell'agente, come Anthropic, Google o OpenAI.
relatedProcess Il processo che ospita l'agente, come code.exe.
trustedProcess Indica se il processo host è attendibile. Riportato come la stringa "true" o "false".
autoApprove Se l'agente agisce senza chiedere all'utente di approvare. Riportato come la stringa "true" o "false".
deviceName, aadDeviceId Il dispositivo dove l'agente è stato scoperto.
accountName, accountDomain, accountSid L'account con cui gestiva l'agente.
localMcps Server MCP che vengono eseguiti localmente sul dispositivo, incluso il comando che avvia ciascuno.

Nota

trustedProcess e autoApprove sono riportate come stringhe, non come valori booleani. Confrontali con "true" o "false" invece di usare tobool().

Ciascuna delle query seguenti è autosufficiente. Le query che utilizzano il grafo di esposizione iniziano con un insieme di istruzioni let che individuano gli agenti di IA locali e i dispositivi su cui vengono eseguiti.

Ottieni un inventario degli agenti AI locali

Questa query elenca gli agenti di intelligenza artificiale locali individuati nella tua organizzazione, insieme all'editore, al processo host, alle versioni in uso e al grado di diffusione di ciascun agente:

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

Ognuno AgentId rappresenta un profilo di agente, che è un singolo agente su un singolo dispositivo per un singolo account. Installations Conta quei profili, quindi un agente che due persone usano sullo stesso dispositivo conta due volte, mentre DeviceCount conta il dispositivo una volta.

Rivedi i server MCP e gli strumenti che gli agenti AI locali utilizzano

Questa query elenca i server MCP e gli strumenti configurati per gli agenti AI locali, gli agenti e i dispositivi che li utilizzano, e dove ciascuno di essi viene eseguito:

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

La Origin colonna distingue i tre modi in cui un server o uno strumento viene riportato:

  • Un server MCP remoto viene raggiunto tramite la rete e Location contiene il suo endpoint.
  • Un server MCP locale viene eseguito come processo sul dispositivo e Location contiene il comando che lo avvia. I server MCP locali sono segnalati solo in AgentsInfo.
  • Uno strumento dichiarato è uno strumento che l'agente pubblicizza. Gli strumenti dichiarati sono spesso supportati da un server MCP, ma non sempre segnalano un endpoint.

A differenza delle altre query, questa esamina tutti i record del profilo per ciascun agente, in modo da includere i server MCP segnalati in qualunque momento. La LastSeen colonna mostra quando ogni server è stato segnalato l'ultima volta.

Trova agenti AI locali con configurazioni rischiose

Questa query restituisce agenti IA locali che agiscono senza chiedere l'approvazione dell'utente o che vengono eseguiti in un processo host non affidabile:

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

Un agente che approva automaticamente le proprie azioni esegue strumenti e raggiunge risorse senza che una persona confermi ogni passaggio, così l'account e il dispositivo su cui l'agente esegue definiscono cosa può fare senza supervisione.

Trova agenti AI locali rischiosi su dispositivi critici

Questa query combina la configurazione degli agenti da AgentsInfo con la criticità degli asset dal grafico di esposizione, così puoi partire dagli agenti che agiscono senza supervisione e funzionano su dispositivi critici per il business:

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

Per esaminare ogni agente AI locale su un dispositivo critico invece che solo quelli rischiosi, rimuovi la where AutoApprove =~ "true" or TrustedProcess =~ "false" linea.

Classifica gli utenti i cui agenti AI locali raggiungono asset critici o sensibili

Questa query classifica le identità che utilizzano agenti AI locali in base a quante risorse critiche o sensibili possono raggiungere, così puoi dare priorità agli utenti con il raggio di esplosione più ampio. La UserCriticality colonna mostra se l'identità stessa è classificata come un asset critico, come un Amministratore Globale:

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
    | 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