Classic painting used as the article cover
← Back to blog

AGENT DISCOVERY

Why Enterprises Need Agent Discovery

One unregistered vendor agent, walked end to end: where the OAuth grant actually lands, how long the evidence survives, and the two API calls that find it before an incident does.

•Jun 8, 2026•Updated Oct 13, 2026•15 min
Agent DiscoverySecurityGovernance

TL;DR

The real test of a discovery program is not whether it catches an agent at the moment of creation. It is whether anyone can name that agent on the day something goes wrong. Most enterprises fail that test because the record of creation has already aged out of the log that held it.

Microsoft Entra ID keeps audit logs for seven days on the free tier and 30 days on P1 or P2, and the documentation is blunt that expired data "can't be recovered unless it was previously archived". Google's OAuth Token log runs six months. Those windows, not your intentions, set the deadline.

The usual reading is that discovery is a quarterly compliance exercise. Run it quarterly against a seven-day audit log and you are sampling, not discovering.

We are not arguing that agents should be harder to create. Useful ones get created by a single OAuth click all the time, and stopping that is not the goal. The problem is that the click leaves no record anybody owns.

Start here: pull GET /oauth2PermissionGrants from Microsoft Graph and tokens.list from the Google Admin SDK, diff both against whatever you currently call your registry, and schedule the job to run inside your shortest retention window.

Overview

A "why you need discovery" argument usually collapses into a list of abstract virtues: faster incident response, cleaner compliance posture, fewer duplicate agents. All true, none of it landing the way one specific failure does. So here is one, with the actual API calls and retention windows attached.

Take a supplier that ships an onboarding assistant embedded in its own portal. A procurement employee grants it access with a single OAuth click, because the portal asks and approving the grant unlocks a faster onboarding flow. The assistant now holds a standing token scoped to whatever documents and contacts that grant covers. Nobody outside procurement knows it exists. This is a hypothetical, built to be typical rather than dramatic, and that is the point: nothing in it required anyone to do something wrong.

What agent discovery is gets covered separately in What Is Agent Discovery?, and the inventory you feed from it in Building an Enterprise Agent Inventory. This post is about the economics of the gap: which signal fires first, how long it survives, and what it costs to look late.

What we would flag at the moment of the click: not the vendor, not the employee, just the grant. An OAuth consent event is the earliest moment this agent can be seen and the cheapest. Every day after it is more expensive than the last, and after a few weeks the cheap evidence is simply gone.

The Agent Nobody Registered

At the moment of that click, three things become true at once. An external agent gains standing access to internal systems. No inventory entry gets created, because nothing in the flow asks for one. And the only durable trace is a grant object inside the identity provider, a place almost nobody reads unless they already suspect something.

None of this is unusual. It is the default path by which a third-party agent enters an enterprise, and it looks identical whether the vendor is trustworthy or not. The grant carries no signal about which one this will turn out to be.

It is also, in most tenants, allowed by default. Microsoft's guidance states that "by default, all users are allowed to consent to applications for permissions that don't require administrator consent", and recommends narrowing that to apps from a verified publisher. Google Workspace ships the equivalent default. Neither is careless. Both mean the first governed moment in the agent's life is one you have to go looking for.

Where the Grant Actually Lands

The useful question is not philosophical. It is: which object, in which system, under which name, and what can I read out of it today.

Where the agent connectsWhat the grant is calledHow you enumerate itWhat the record gives you
Microsoft 365 or Entra tenant, acting for a user (aact:delegation-model/on-behalf-of-user)delegated permission grant (oAuth2PermissionGrant)GET /oauth2PermissionGrants on Microsoft GraphclientId, resourceId, principalId, consentType, and the full space-delimited scope string
Microsoft 365, acting as itself (aact:delegation-model/autonomous)app role assignment on a service principalGET /servicePrincipals/{id}/appRoleAssignedTothe application permission, with no user attached to it at all
Google Workspace, per usera token issued to a third-party appGET /admin/directory/v1/users/{userKey}/tokensclient ID, the granted scopes, and the app's display text
Any of the above, as historyan audit eventEntra audit log, or the Google OAuth Token logwho consented, when, and to what, for as long as retention lasts

Table 1 — The four places an agent's access is actually recorded, and the call that reads each one.

Two details in that table do most of the work. The first is consentType. Microsoft's own sample response for /oauth2PermissionGrants shows a grant with "consentType": "AllPrincipals" and a principalId of null, carrying a scope string long enough to include Mail.ReadWrite, Files.ReadWrite.All and Directory.ReadWrite.All together. That is one admin click covering every user in the tenant, and because there is no principal on it, a per-user reconciliation will never surface who is affected.

The second is that the current-state calls and the history calls answer different questions. Grant lists tell you what is true now. Audit logs tell you how it got that way, and only for a while.

http
GET /admin/directory/v1/users/alex@example.com/tokens HTTP/1.1
Host: admin.googleapis.com
Authorization: Bearer {admin access token}

The Admin SDK describes that method plainly: it "returns the set of tokens specified user has issued to 3rd party applications", and it needs the https://www.googleapis.com/auth/admin.directory.user.security scope. Revocation is the same path with a different verb, and the docs are equally plain that it "deletes all access tokens issued by a user for an application":

http
DELETE /admin/directory/v1/users/alex@example.com/tokens/{clientId} HTTP/1.1
Host: admin.googleapis.com

On the Microsoft side the reconciliation loop is short enough to fit in one function. Join each grant to the service principal that holds it, because the grant alone gives you a GUID and not a name.

python
def grants_with_owners(graph):
    for g in graph.get("/oauth2PermissionGrants").json()["value"]:
        sp = graph.get(f"/servicePrincipals/{g['clientId']}").json()
        yield {
            "app": sp["displayName"],
            "publisher": sp.get("publisherName"),
            "consent": g["consentType"],        # AllPrincipals covers the whole tenant
            "subject": g.get("principalId"),    # null whenever consent is AllPrincipals
            "scopes": g["scope"].split(),
            "verified": sp.get("verifiedPublisher", {}).get("displayName"),
        }

Diff that against your registry and the unregistered agents fall out of the difference. One wrinkle Microsoft flags in the docs: the list endpoint "might have replication delays" for recently created grants, and the delay "will be minimized if a filter on clientId is specified". A job that runs right after a suspected consent should query the specific client, not the whole collection.

The Day It Mattered

Months pass. The assistant does what it was granted to do: pull onboarding documents, confirm contacts, sync a status field back to the portal. The relationship deepens the way these things do, and the integration's real footprint drifts past what the original grant was meant to cover.

Then the procurement employee who approved it changes roles and, some months later, leaves. Nobody reassigns the agent, because nobody owned it in a record that survives a departure. It is not disabled either. An OAuth grant does not expire because the person who clicked "allow" is gone.

Later still, the vendor's platform has an incident of its own, unrelated to anything the enterprise did. The change on their end alters what the integration requests, and the assistant starts pulling contract terms across other suppliers. A security analyst notices unfamiliar volume against the contract repository, tied to a client ID nobody recognizes.

That is the entire signal to start from. A GUID. No name, no owner, no record of what normal looked like.

Why the Evidence Expires Before You Look

Here is the part that turns a governance preference into a deadline. The record of how the agent got its access has a shelf life, and it is shorter than most discovery cadences.

SignalWhere it livesHow long it survivesPractical catch
"Consent to application" and "Add delegated permission grant" audit eventsEntra ID FreeSeven daysBelow any monthly or quarterly review cadence
The same eventsEntra ID P1 or P230 daysA monthly job that slips by a week loses the month
OAuth Token log events (authorize, revoke, activity, request, deny)Google WorkspaceSix monthsLag before the event is queryable is "A couple of hours", so it is not a real-time control
Current grants and tokensEntra and Google, as stateNo expiryTells you what exists, never when or why it was granted

Table 2 — Retention is the constraint nobody budgets for. The consent event is the cheapest evidence you will ever have, and on a free Entra tenant it is gone in a week.

Microsoft is explicit about what that means once the window closes. Retention changes "aren't retroactive", and data that has already expired "can't be recovered unless it was previously archived". Upgrading from Free to P1 after the fact recovers whatever is still inside the seven-day window and nothing else.

The Entra activity names are worth writing down, because filtering on the wrong one returns an empty screen and reads like good news. Microsoft's documentation lists the four that matter, all under the ApplicationManagement category: "Consent to application" when a user grants consent, "Add delegated permission grant" for delegated access, and "Add app role assignment to the service principal" and its matching removal for app-only access.

Discovery frequency is not a maturity question. It is arithmetic. If your audit retention is seven days and your review is quarterly, twelve out of every thirteen weeks of creation history is unrecoverable by design. Either the job runs inside the window or the logs go to storage that outlives it.

What Discovery Would Have Caught, and When

Run the same story with discovery in place and it breaks differently at every step.

OAuth grant approved
by procurement

No discovery

With discovery

No record created

Agent surfaced same day,
owner and scope on file

Owner leaves,
nothing flags the agent

Owner leaves,
agent flagged for review

Anomalous traffic:
unknown OAuth client ID

Anomalous traffic:
named agent, known scope

Investigation starts from zero,
and the consent event has expired

Investigation starts from a record:
hours to scope and contain

Figure 1 — The same incident, run twice. Discovery does not stop the vendor's platform from having a bad day. It decides whether the response starts from a name or from a GUID.

At the grant, the consent event alone would have surfaced the assistant the same day. When the employee left, an "owner departed" trigger would have flagged it for review, and that review is where someone notices the scope has drifted. By the time the anomalous traffic appeared, the responder would be opening a record with an owner, a scope list and a baseline on it.

There is a second payoff: discovery gives you somewhere to put the answer to "can we turn this off". The two platforms behave differently here, and the difference is worth deciding in advance.

What you doMicrosoft EntraGoogle Workspace
Tighten who may consent in futureOnly affects future consent. "Existing consent grants remain unchanged, and users continue to have access based on the permissions previously granted."Same direction of travel: a setting change governs what happens next
Restrict or block an app's access to a serviceRequires revoking the grant separately"any previously installed apps that you haven't trusted stop working, and tokens are revoked"
Remove one app's access for one userDelete the oAuth2PermissionGrantDELETE .../users/{userKey}/tokens/{clientId}

Table 3 — Tightening a consent setting is not a containment action on Entra. Assuming it is, is one of the more common ways an incident stays open.

What Discovery Still Misses

We would rather oversell the limits than the tool. Consent enumeration is the highest-yield signal available and it is nowhere near complete.

How did the agent
authenticate?

OAuth consent,
on behalf of a user

Admin consent,
tenant-wide

Static API key, PAT,
or service-account key

Grant list + consent event.
Owner is the consenting user

Grant list, principalId null.
Owner is whoever clicked

No consent object exists.
Gateway logs, egress, secret scanning

Figure 2 — The third branch is the blind spot. Nothing in the identity provider knows that agent exists.

Google's tokens.list covers tokens a user issued to third-party apps, so a domain-wide-delegation client is a different query. Anything on an employee's personal account is outside the tenant and outside every call in Table 1. And the Token log's couple-of-hours lag makes it a review signal rather than a gate.

Two failures also look identical in the data. An app in your grant list with no traffic may be abandoned or merely seasonal, and the activity events in the Token log separate those. Traffic you cannot attribute may be an unregistered app or a registered one whose vendor changed which client ID it calls from, and that one you settle by asking the vendor.

Why This Isn't Rare

We do not think this scenario is unusual, and that is the uncomfortable part. Nothing in it depended on carelessness. It depended on how easy an agent is to create relative to how much friction exists around noticing one, and that asymmetry is structural.

It has also already happened to a company with better telemetry than yours. In January 2024 Microsoft disclosed that the actor it calls Midnight Blizzard "compromised a legacy test OAuth application that had elevated access to the Microsoft corporate environment", and used it to grant itself the Office 365 Exchange Online full_access_as_app role, which allows access to mailboxes. Read the adjectives: legacy, test. An application nobody was using, nobody owned, and nobody had removed. That is the same object as the vendor assistant in this story, arrived at from a different direction.

The nearest thing to a market-wide figure comes from IBM's 2025 Cost of a Data Breach Report, which studied AI governance and access controls for the first time. It found that 20% of surveyed organizations reported a breach traced to incidents involving shadow AI, that a high level of shadow AI added roughly USD 670,000 to the average breach cost, and that 63% of those studied "have no AI governance policies in place". The report counts unapproved AI tools rather than unregistered agents, so treat it as adjacent rather than a measurement of this. It is the closest primary number we can point at, and it points the same way.

The same shape shows up as duplication and as abandonment. Different surface, same root cause: nothing forced the agent into a record at the moment it was created.

What the Frameworks Already Ask For

Discovery is also the prerequisite for two obligations that are already written down.

NIST's AI Risk Management Framework puts the inventory in the GOVERN function. GOVERN 1.6 defines an AI system inventory as "an organized database of artifacts relating to an AI system or model", and the Playbook's suggested actions name the attributes: documentation, incident response plans, data dictionaries, source-code links, AI actor contact details. It also says the quiet part. Partial inventories "may not provide the value of a full inventory", with high-risk systems as the floor rather than the target.

On the regulatory side, the calendar moved but the obligation did not. The EU AI Act entered into force on 1 August 2024 and became applicable on 2 August 2026. The AI Omnibus, proposed on 19 November 2025 and in force since 27 July 2026, pushed the rules for systems used in certain high-risk areas to 2 December 2027, and for AI integrated into products such as lifts or toys to 2 August 2028. If you are a deployer, that is extra time to build an enumeration you can stand behind, not a reason to stop. Nobody has ever documented their way out of not knowing what is connected.

Where Fabriq Fits

For agents that run through Agentic Fabriq, most of this is answered by construction rather than by a reconciliation job. Credentials live in a vault per user and are attached server-side at the moment of the call, so the agent never handles the secret. Every call carries two identities, the agent and the acting user, and lands an audit record. An agent's effective tool list is the intersection of the user's scopes and the agent's own, so the question "what can this reach" has an answer that does not depend on anyone having written it down.

What it does not do is find the agents that never went through it. The vendor assistant in this story consented directly in the tenant, and the calls in Table 1 are still how you would catch it. Clearing a vault copy of a credential is also not the same act as invalidating a grant upstream, so the DELETE .../tokens/{clientId} above stays yours to make.

Frequently Asked Questions

What is agent discovery, and how is it different from an agent inventory? Discovery finds agents that already have access, by reading identity and platform signals rather than asking people. The inventory is the record you write the findings into. An inventory without discovery is a self-report list, and self-reporting is exactly what an unregistered agent bypasses.

Why do enterprises need agent discovery rather than a registration requirement? Registration governs the agents that comply with it. Default consent settings on both major platforms let one user grant a third-party app access with no administrator in the loop, so the population with live access will always be larger than the population that registered. Discovery measures the gap.

How do I find AI agents already connected to Microsoft 365? Start with GET /oauth2PermissionGrants on Microsoft Graph for delegated grants, and appRoleAssignedTo on each service principal for app-only access. Join both to the service principal for a human-readable name and publisher. Then read the Entra audit log filtered to the ApplicationManagement category and the activities "Consent to application" and "Add delegated permission grant" to find out when each one arrived, if it is still inside retention.

How do I find third-party apps with access to Google Workspace? Iterate the directory and call GET /admin/directory/v1/users/{userKey}/tokens for each user, which returns the tokens that user issued to third-party apps. Cross-check against the OAuth Token log's authorize events for the history, and use App access control to set each app to trusted, limited or blocked.

How often should discovery run? Inside your shortest retention window, which on a free Entra tenant means weekly at worst. If you want a slower cadence, first export the audit log somewhere that outlives the default, because the cadence question is really a retention question.

Does blocking an app revoke the access it already has? On Google Workspace, restricting a service revokes tokens for apps you have not trusted, which is a real containment action. On Entra, changing user consent settings does not touch existing grants at all. Deleting the grant is the containment action there.

Can discovery find an agent that uses an API key instead of OAuth? Not through consent logs, because aact:credential-model/static-secret-in-agent creates no consent object. Those agents show up in gateway logs, egress telemetry and secret-scanning results instead. It is worth saying out loud that this is a genuinely weaker signal, and it is the main reason we would not describe consent enumeration as complete coverage.

Who should own agent discovery? Whoever already owns identity, because every call in Table 1 needs directory-level read permissions and nobody wants a second team holding Directory.Read.All.

Conclusion

The vendor assistant in this story did nothing malicious, and neither did the procurement employee who approved it. That is what makes it worth sitting with rather than dismissing. Ordinary decisions, made in good faith, produced an agent nobody could name on the day it mattered.

Discovery does not add a gate in front of the first OAuth click. It makes sure the click leaves a record that survives the employee who made it. And because the platforms hold the cheap version of that record for seven days, thirty days, or six months depending on where you are looking, the only real decision is whether you read it before it expires.

The cost of skipping discovery is not paid at creation. It is paid on the day something forces the question, by whoever has to answer it from an audit log that no longer goes back that far.

Sources

All URLs read 2026-09-29.

</invoke>