Forum Discussion

Marcel_Graewer's avatar
Marcel_Graewer
Brass Contributor
Jun 09, 2026

Detecting AI agents and non-human identities in Microsoft Sentinel: the classic-agent blind spot

Build 2026 made the direction official. The industry is moving from the app era into the agent era, and Microsoft spent a real share of the keynote on securing agents across their lifecycle, from discovering what is exploitable to governing what is running in production. On the identity side the centerpiece is Microsoft Entra Agent ID, now generally available, which gives AI agents first-class identities and extends Conditional Access, Identity Protection, and full audit logging to them.

That is good news for agents you build the new way. It is not the whole picture, and the gap is where most SOCs will get hurt first.

Modern agents are covered. Classic agents are not.

Entra Agent ID draws a hard line between two kinds of agent.

Modern agents are created through the Agent ID platform, each backed by an agent identity blueprint. They carry a proper Agent ID, a full audit trail, and the complete set of governance capabilities, including Identity Protection for Agents, which establishes a baseline for an agent's normal activity and flags anomalies automatically.

Classic agents are everything that came before, or that gets built outside the platform: AI agents implemented as ordinary service principals or app registrations, for example Copilot Studio agents created before Agent ID was enabled, or any home-grown automation calling Graph with client credentials. In the Entra agent registry they appear with "Has Agent ID: No," and that flag matters, because the Agent ID protections apply to identities that actually hold an Agent ID. Classic agents sit outside Identity Protection for Agents and Conditional Access for Agents.

Here is the uncomfortable part. The non-human identities you already run, the service principals behind your pipelines, your integrations, your scripts, your pre-platform Copilot Studio bots, are almost all classic agents. They tend to outnumber your human accounts, they have no MFA in any meaningful sense, and a credential added to one does not show up in the Azure portal. The new platform protections do not reach them. Until you migrate them, the only place you get detection coverage on that population is your SIEM.

So this is the job Sentinel does that Agent ID does not: detect risky behavior on the classic, service-principal-backed agents that the platform cannot yet protect.

The telemetry you have, and the one switch people forget

Three tables carry most of the signal.

AADServicePrincipalSignInLogs records service principal authentications, the client-credentials sign-ins your agents and automation use. No user, no MFA, just an app proving it holds a secret or certificate.

AADManagedIdentitySignInLogs does the same for managed identities.

AuditLogs records directory changes, including the one that matters most for persistence: a new credential added to an application or service principal.

One practical warning before any of this works. Service principal and managed identity sign-in logs are not streamed by default. You have to enable those categories explicitly in the Entra diagnostic settings feeding your workspace. Plenty of teams write the detection, never check, and never notice the table is empty. Verify that first.

Detection 1: a new credential on a service principal or app

Adding a secret or certificate to an existing service principal is one of the cleanest persistence techniques in a Microsoft cloud. The attacker compromises a privileged user or app, drops a fresh credential on a service principal that already holds useful Graph permissions, and now has access that survives password resets and session revocation. It maps to MITRE T1098.001, Account Manipulation: Additional Cloud Credentials. For a classic agent it is especially nasty, because there is no Identity Protection baseline watching it.

// Detection 1: new secret or certificate added to an application or service principal
// MITRE T1098.001 - Account Manipulation: Additional Cloud Credentials
AuditLogs
| where OperationName has_any ("Add service principal", "Certificates and secrets management")
| where Result =~ "success"
| extend Initiator = coalesce(
        tostring(InitiatedBy.user.userPrincipalName),
        tostring(InitiatedBy.app.displayName))
| extend InitiatorIp = tostring(InitiatedBy.user.ipAddress)
| mv-apply Target = TargetResources on (
    where Target.type =~ "Application"
    | extend TargetName  = tostring(Target.displayName),
             TargetId    = tostring(Target.id),
             KeyChanges  = Target.modifiedProperties
  )
| mv-apply Prop = KeyChanges on (
    where tostring(Prop.displayName) =~ "KeyDescription"
    | extend NewKeys = parse_json(tostring(Prop.newValue)),
             OldKeys = parse_json(tostring(Prop.oldValue))
  )
| extend AddedKeys = set_difference(NewKeys, OldKeys)
| where array_length(AddedKeys) > 0
| project TimeGenerated, Initiator, InitiatorIp, TargetName, TargetId, AddedKeys
| order by TimeGenerated desc

The operation filter catches the three shapes this event takes in the log: "Add service principal," "Add service principal credentials," and "Update application - Certificates and secrets management." The modifiedProperties parsing isolates the KeyDescription change, and set_difference confirms a key was actually added rather than removed, so rotating out an old credential does not, on its own, fire the rule.

False positives come from legitimate rotation and from automation that provisions app credentials (CI/CD, infrastructure as code). The initiator is the discriminant. A credential added by your deployment pipeline's service account at the usual time is routine. The same change initiated by an interactive admin out of hours, or by an account that never normally touches app credentials, is what you want to surface. Allow-list the expected initiators, not the targets.

Detection 2: a classic agent signing in from a first-seen IP

A service principal that has only ever authenticated from your Azure regions and suddenly signs in from somewhere new is a strong signal that its credential has been lifted and is being used elsewhere. Service principals have stable, boring network behavior, which makes a first-seen IP a far cleaner indicator for them than it is for roaming human users. This is the behavioral baseline Identity Protection gives you for free on modern agents, rebuilt in KQL for the classic ones it ignores. MITRE T1078.004, Valid Accounts: Cloud Accounts.

// Detection 2: classic-agent service principal signing in from a previously unseen IP
// MITRE T1078.004 - Valid Accounts: Cloud Accounts
let baseline  = 14d;
let detection = 1d;
let KnownIPs =
    AADServicePrincipalSignInLogs
    | where TimeGenerated between (ago(baseline + detection) .. ago(detection))
    | where tostring(ResultType) == "0"
    | summarize KnownIPSet = make_set(IPAddress) by AppId;
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(detection)
| where tostring(ResultType) == "0"
| lookup kind=leftouter KnownIPs on AppId
| where set_has_element(KnownIPSet, IPAddress) == false
| summarize FirstSeen = min(TimeGenerated),
            Resources = make_set(ResourceDisplayName, 10)
  by ServicePrincipalName, AppId, IPAddress
| order by FirstSeen desc

The query builds a per-application baseline of source IPs over the previous two weeks, then flags any successful sign-in today from an address outside that set. Two tuning notes. Brand-new service principals have no baseline, so they surface on first use. That is usually worth seeing once, but you can exclude AppIds younger than the baseline window if it gets noisy. And if your agents egress through shifting cloud IP ranges, widen the comparison from an exact IP to the autonomous system number or a known-range allow-list, otherwise you will chase your own infrastructure.

This complements Agent ID, it does not replace it!

The endgame is not to run these rules forever. It is to shrink the population they apply to. Inventory your tenant for agents marked "Has Agent ID: No," prioritize the ones holding sensitive Graph permissions, and migrate them onto the Agent ID platform, where Identity Protection and Conditional Access take over the baselining you are doing here by hand. Microsoft has signaled a migration path from classic to modern agents. Treat these two detections as the coverage you need in the meantime, and as a permanent safety net for anything that never makes the move.

If you do one thing this week: enable the service principal sign-in log category, deploy detection 1, and pull a list of every service principal that had a credential added in the last 90 days. That list alone tends to be more interesting than people expect.

 

Cheers, Marcel

No RepliesBe the first to reply