
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.
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 connects | What the grant is called | How you enumerate it | What 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 Graph | clientId, 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 principal | GET /servicePrincipals/{id}/appRoleAssignedTo | the application permission, with no user attached to it at all |
| Google Workspace, per user | a token issued to a third-party app | GET /admin/directory/v1/users/{userKey}/tokens | client ID, the granted scopes, and the app's display text |
| Any of the above, as history | an audit event | Entra audit log, or the Google OAuth Token log | who 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.
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":
DELETE /admin/directory/v1/users/alex@example.com/tokens/{clientId} HTTP/1.1
Host: admin.googleapis.comOn 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.
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.
| Signal | Where it lives | How long it survives | Practical catch |
|---|---|---|---|
| "Consent to application" and "Add delegated permission grant" audit events | Entra ID Free | Seven days | Below any monthly or quarterly review cadence |
| The same events | Entra ID P1 or P2 | 30 days | A monthly job that slips by a week loses the month |
OAuth Token log events (authorize, revoke, activity, request, deny) | Google Workspace | Six months | Lag before the event is queryable is "A couple of hours", so it is not a real-time control |
| Current grants and tokens | Entra and Google, as state | No expiry | Tells 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.
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 do | Microsoft Entra | Google Workspace |
|---|---|---|
| Tighten who may consent in future | Only 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 service | Requires 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 user | Delete the oAuth2PermissionGrant | DELETE .../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.
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.
- Agent Access Control Taxonomy — the delegation-model terms that separate a delegated grant from an app-only one, and the credential-model term for the key-based agents no consent log can see.
- View activity logs of application permissions, Microsoft Entra ID — the four
ApplicationManagementaudit activities, including "Consent to application" and "Add delegated permission grant". - Microsoft Entra data retention — seven days on Free, 30 days on P1 and P2, and the statement that expired data cannot be recovered unless previously archived.
- Configure how users consent to applications, Microsoft Entra ID — the default that all users may consent to permissions not requiring admin consent, and that changing the setting leaves existing grants unchanged.
- List oAuth2PermissionGrants, Microsoft Graph — the
GET /oauth2PermissionGrantsrequest, theAllPrincipalssample response, and the replication-delay note. - Method: tokens.list, Google Admin SDK Directory API — the per-user token endpoint and the
admin.directory.user.securityscope. - Method: tokens.delete, Google Admin SDK Directory API — revoking all access tokens a user issued to one application.
- OAuth Token audit activity events, Google Workspace — the
authorize,revoke,activity,requestanddenyevents and their parameters. - Data retention and lag times, Google Workspace Admin — six-month retention for Token log events and the couple-of-hours lag.
- Control which third-party and internal apps access Google Workspace data — trusted, limited and blocked access states, and token revocation when a service is restricted.
- Midnight Blizzard: Guidance for responders on nation-state attack, Microsoft — the legacy test OAuth application with elevated access and the
full_access_as_approle. - 2025 Cost of a Data Breach Report: Navigating the AI rush, IBM — the USD 670,000 shadow-AI cost delta and the 63% with no AI governance policies.
- NIST AI RMF Playbook, GOVERN function — GOVERN 1.6 on AI system inventories, their attributes, and partial versus full coverage.
- AI Act regulatory framework, European Commission — entry into force, the 2 August 2026 application date, and the Omnibus dates of 2 December 2027 and 2 August 2028.
- OWASP Top 10 for Agentic Applications 2026, OWASP GenAI Security Project — ASI03 Identity and Privilege Abuse and ASI10 Rogue Agents, published 9 December 2025. </content>