<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: identity-security</title><description>Posts tagged identity-security.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 07 Jun 2026 04:13:08 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/identity-security/rss.xml" rel="self" type="application/rss+xml"/><item><title>The 28-Hour Bargain: How Continuous Access Evaluation Made Long-Lived Tokens Safe</title><link>https://paragmali.com/blog/the-28-hour-bargain-how-continuous-access-evaluation-made-lo/</link><guid isPermaLink="true">https://paragmali.com/blog/the-28-hour-bargain-how-continuous-access-evaluation-made-lo/</guid><description>How Microsoft Entra Continuous Access Evaluation lets access tokens safely live up to 28 hours by pairing them with a near-real-time revocation channel.</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Microsoft Entra Continuous Access Evaluation (CAE) lets access tokens safely live up to 28 hours.** It works by maintaining a push-subscription channel between Entra and Microsoft 365 resource providers, so that when a user is disabled, has their password reset, or has MFA enabled, the resource provider rejects the next request with a `401` and a claims challenge -- typically within 15 minutes for critical events, instantly for IP-location changes [@ms-cae-concept]. The same pattern was standardized by the OpenID Foundation on September 2, 2025 as SSF 1.0, CAEP 1.0, and RISC 1.0 Final Specifications [@openid-three-final-specs], opening the door to vendor-neutral cross-SaaS revocation. CAE does **not** solve token theft (use DPoP for that) and does **not** cover Microsoft Defender for Endpoint or Intune as resource providers (they are signal sources into Conditional Access, not CAE consumers).
&lt;h2&gt;1. Your Fired Employee Is Still Reading Email&lt;/h2&gt;
&lt;p&gt;09:00 Tuesday. The administrator disables the account at 09:01. At 09:23, the ex-employee&apos;s open Outlook for the Web tab refreshes -- and pulls down new mail. This is not a bug. This is RFC 6749 working exactly as designed. Until Microsoft Entra shipped a fix that took ten years and three standards bodies -- the IETF, the OpenID Foundation, and NIST -- to develop, the access token that user held at 09:00 stayed cryptographically valid until 10:00 at the latest, and there was nothing &lt;a href=&quot;https://paragmali.com/blog/who-decided-this-token-is-good-a-field-guide-to-conditional-/&quot; rel=&quot;noopener&quot;&gt;Conditional Access&lt;/a&gt; could do about it [@rfc-6749].&lt;/p&gt;
&lt;p&gt;The window has a name now. It did not, for most of cloud identity&apos;s history. Microsoft&apos;s own documentation calls it &quot;the lag between when conditions change for a user, and when policy changes are enforced&quot; [@ms-cae-concept]. Between sign-in (Conditional Access territory) and the next token refresh (refresh-token territory) sits a stretch of time in which Conditional Access decisions have no enforcement surface. That stretch ranged from 60 minutes to 24 hours, depending on tenant configuration. For every OAuth 2.0 deployment from 2012 onward, this was the security debt the industry carried.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &quot;Microsoft Entra ID&quot; is the rebranded name for what most engineers learned as &quot;Azure Active Directory&quot; or &quot;Azure AD.&quot; Microsoft announced the rename in July 2023 [@ms-entra-rename-2023]; the underlying service, tenants, app registrations, and APIs are unchanged. Throughout this article, &quot;Entra&quot; and the older &quot;Azure AD&quot; refer to the same identity platform.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This article explains the engineering pattern that lets a Microsoft 365 tenant do two things that look contradictory at the same time: extend access-token lifetime from 1 hour to up to 28 hours, &lt;em&gt;and&lt;/em&gt; revoke a disabled user&apos;s session in under 15 minutes [@ms-cae-concept]. The reconciling idea is a near-real-time push channel between the identity provider (Entra) and a small set of cooperating resource providers. When you can revoke a token in minutes rather than waiting for it to expire, expiry stops doing the security work, and the token can live as long as the user actually needs it.&lt;/p&gt;

Microsoft Entra&apos;s push-subscription channel between the identity provider and cooperating resource providers (Exchange Online, SharePoint Online, Teams, and Microsoft Graph). CAE lets a resource provider revoke an already-issued access token in near-real-time -- up to 15 minutes for critical events, instantly for IP-location changes -- without waiting for the token to expire [@ms-cae-concept].
&lt;p&gt;The trade has a price. The 15-minute critical-event service-level objective is the price the channel pays for fanning out events across hyperscale Microsoft 365 infrastructure. Sub-second revocation is possible -- other vendors demonstrate it at smaller scales -- but at Exchange-Online volume, 15 minutes is the engineering economics. We will earn that number by Section 8.&lt;/p&gt;
&lt;p&gt;For now: the OAuth 2.0 designers knew about this gap when they wrote RFC 6749 in 2012. They chose it on purpose. To see why, and to see why the obvious patches all failed, we have to walk back to the moment the trade was made.&lt;/p&gt;
&lt;h2&gt;2. The Static-Expiry Compromise&lt;/h2&gt;
&lt;p&gt;In October 2012, Dick Hardt of Microsoft published RFC 6749 -- &lt;em&gt;The OAuth 2.0 Authorization Framework&lt;/em&gt; -- as the editor of record for an IETF working group that had spent five years arguing about it [@rfc-6749]. Section 1.4 carries one of the most consequential adjectives in cloud-identity history. Access tokens, it says, are credentials &quot;usually with a short lifetime&quot; used by the client to access a protected resource. The word &lt;em&gt;usually&lt;/em&gt; is doing heavy lifting. Nothing in the protocol enforces it. Nothing in the protocol provides revocation. Nothing in the protocol stops a server from issuing 24-hour bearer tokens that, once minted, stay cryptographically valid until they expire on their own.&lt;/p&gt;
&lt;p&gt;This was a deliberate trade. To see why it was rational, remember what came before.&lt;/p&gt;
&lt;h3&gt;Web Access Management: the model OAuth replaced&lt;/h3&gt;

The pre-2012 enterprise-identity pattern in which every protected HTTP request synchronously queried a central policy decision point. Strength: instant revocation, because every request consulted authoritative state. Weakness: a chatty bottleneck that did not scale to cloud volumes and could not federate trust across organizations.
&lt;p&gt;Web Access Management dominated enterprise identity from the late 1990s into the early 2010s. Every protected HTTP request to a WAM-fronted application made a synchronous round-trip to a Policy Decision Point. The PDP held authoritative session and policy state. Revoke a user? The next request failed, immediately, because the PDP said no. No token-lifetime window. No gap between policy change and enforcement.&lt;/p&gt;
&lt;p&gt;WAM was correct. WAM was also unworkable for the web that was coming. It did not scale: every request was a network hop. It did not federate: cross-organization SaaS meant the PDP could not live inside any one company&apos;s network. And it required every protected resource to participate in a single trust domain. By the time enterprises were running cross-organization SaaS at scale, the WAM model had run out of road.&lt;/p&gt;
&lt;p&gt;The OAuth 2.0 authors made the opposite trade. Replace the chatty PDP round-trip with a self-contained signed bearer token -- a JWT the resource server validates locally. Validation becomes O(1) cryptographic verification with no round-trip. Throughput scales horizontally. Federation works, because the JWT carries its own attestation of the issuer. Revocation becomes...approximated. By expiry. The token is valid until it isn&apos;t, and you trust that the lifetime is short enough.&lt;/p&gt;
&lt;p&gt;For a 2012 web of forum logins and consumer mashups, &quot;short enough&quot; was a defensible answer. For a 2020 enterprise running compliance-bound SaaS across thousands of employees, it was not.&lt;/p&gt;
&lt;h3&gt;The Zero Trust pressure&lt;/h3&gt;
&lt;p&gt;Two intellectual pressures forced the question. The first came from Google. In December 2014, Rory Ward and Betsy Beyer published &lt;em&gt;BeyondCorp: A New Approach to Enterprise Security&lt;/em&gt; in USENIX &lt;code&gt;;login:&lt;/code&gt; [@ward-beyer-2014-beyondcorp].Beyer would later co-author &lt;em&gt;Site Reliability Engineering&lt;/em&gt; (O&apos;Reilly, 2016); BeyondCorp came out of the same Google culture of evidence-driven infrastructure engineering. The argument was philosophical: a session is not a one-shot decision at sign-in. It is a time-varying authorization. Trust signals -- device posture, network location, behavioral risk -- change continuously, and the access decision should change with them. BeyondCorp was not a CAE implementation; it predates the term. But it planted the seed that login-time enforcement was not enough.&lt;/p&gt;
&lt;p&gt;The second pressure was bureaucratic. In August 2020, NIST published Special Publication 800-207, &lt;em&gt;Zero Trust Architecture&lt;/em&gt;, by Scott Rose, Oliver Borchert, Stu Mitchell, and Sean Connelly [@nist-sp-800-207]. SP 800-207 codified the BeyondCorp philosophy as U.S. federal guidance. One sentence made the engineering investment commercially rational: &lt;em&gt;&quot;Authentication and authorization (both subject and device) are discrete functions performed before a session to an enterprise resource is established.&quot;&lt;/em&gt; A federal mandate for continuous re-evaluation pushed every cloud vendor with U.S. government contracts to find an implementation. The gap RFC 6749 had left was now a procurement problem.&lt;/p&gt;
&lt;h3&gt;A name for the problem&lt;/h3&gt;
&lt;p&gt;The third moment named the gap. On February 21, 2019, Atul Tulshibagwale, then an engineer at Google, published &lt;em&gt;Re-thinking federated identity with the Continuous Access Evaluation Protocol&lt;/em&gt; on the Google Cloud blog [@tulshibagwale-2019-google-blog]. The post introduced a term -- CAEP -- and a framing: publish-and-subscribe between identity providers and resource providers, as a third option between WAM&apos;s per-request chattiness and OAuth&apos;s fire-and-forget expiry. We return to Tulshibagwale&apos;s actual proposal in Section 5. For now what matters: 2019 was the year the industry got a vocabulary for a problem it had been carrying for seven years.&lt;/p&gt;
&lt;p&gt;The OpenID Foundation working group that grew out of Tulshibagwale&apos;s proposal was originally chartered as the &lt;em&gt;Shared Signals &amp;amp; Events&lt;/em&gt; (SSE) working group. It was renamed &lt;em&gt;Shared Signals&lt;/em&gt; in subsequent years, but older industry write-ups from 2020-2022 still use the SSE abbreviation [@idsalliance-2022-11-cae].&lt;/p&gt;

gantt
    title CAE and Shared Signals timeline (2012-2025)
    dateFormat YYYY-MM
    axisFormat %Y
    section IETF standards
    RFC 6749 OAuth 2.0           :done, a1, 2012-10, 30d
    RFC 7009 Token Revocation    :done, a2, 2013-08, 30d
    RFC 7662 Token Introspection :done, a3, 2015-10, 30d
    RFC 8417 SET                 :done, a4, 2018-07, 30d
    RFC 8935 SET Push            :done, a5, 2020-11, 30d
    RFC 8936 SET Poll            :done, a6, 2020-11, 30d
    section Zero Trust thinking
    BeyondCorp paper             :done, b1, 2014-12, 30d
    NIST SP 800-207 Final        :done, b2, 2020-08, 30d
    section CAEP origin and OIDF
    Tulshibagwale CAEP post      :done, c1, 2019-02, 30d
    OIDF Shared Signals WG       :done, c2, 2019-09, 30d
    SSF 1.0 CAEP 1.0 RISC 1.0    :done, c3, 2025-09, 30d
    section Microsoft Entra CAE
    Limited preview Weinert      :done, d1, 2020-04, 30d
    Expanded preview Simons      :done, d2, 2020-10, 30d
    General Availability         :done, d3, 2022-01, 30d
&lt;p&gt;The OAuth 2.0 designers traded revocation latency for throughput on purpose [@rfc-6749]. Once that gap proved unacceptable, three obvious patches were tried. None of them worked. To see &lt;em&gt;why&lt;/em&gt; none of them worked is to understand the negative space CAE was designed to fill.&lt;/p&gt;
&lt;h2&gt;3. Three Patches, Three Failures&lt;/h2&gt;
&lt;p&gt;Between 2013 and the late 2010s, the OAuth community published three patches for RFC 6749&apos;s revocation gap. Each was rationally adopted; each was rationally abandoned at hyperscale. This section is the genealogy of those failures, because what each one got wrong defines the shape of the design that finally worked.&lt;/p&gt;
&lt;h3&gt;Patch 1: RFC 7009 -- the &lt;code&gt;/revoke&lt;/code&gt; endpoint (August 2013)&lt;/h3&gt;
&lt;p&gt;In August 2013, Torsten Lodderstedt of Deutsche Telekom, Stefanie Dronia, and Marius Scurtescu of Google published RFC 7009, &lt;em&gt;OAuth 2.0 Token Revocation&lt;/em&gt; [@rfc-7009]. The contribution was a standardized HTTP endpoint, &lt;code&gt;/revoke&lt;/code&gt;, that a client could POST a token to in order to invalidate it. The mental model is the logout button: when a user signs out, the client tells the authorization server &quot;I&apos;m done with this token, please retire it.&quot;&lt;/p&gt;
&lt;p&gt;The failure mode is in the threat model. RFC 7009 is &lt;em&gt;client-initiated&lt;/em&gt;. The token holder asks for revocation. But the scenario that motivates CAE is precisely the one where the token holder is uncooperative. A fired employee will not POST their access token to &lt;code&gt;/revoke&lt;/code&gt; on the way out the door. An attacker who has stolen a token will certainly not. The administrator on the other side cannot use the endpoint either, because they do not possess the bearer token.&lt;/p&gt;
&lt;p&gt;Worse, RFC 7009&apos;s Implementation Note (Section 3) is candid about self-contained tokens: the only standardized recourse is &quot;some (currently non-standardized) backend interaction between the authorization server and the resource server&quot; when immediate revocation is desired [@rfc-7009]. Read that carefully. The spec admits there is no spec. The JWT in flight at the resource server is &lt;em&gt;cryptographically valid until it expires&lt;/em&gt;. The authorization server can mark it revoked in a local database, but the resource server never asks. It validates the signature locally. The revocation event never crosses the wire.&lt;/p&gt;
&lt;p&gt;RFC 7009 works for opaque tokens with a token-introspection back-channel. It does not, by itself, solve revocation for self-contained JWT bearers -- which by the mid-2010s were the dominant pattern in the cloud.&lt;/p&gt;
&lt;h3&gt;Patch 2: RFC 7662 -- the &lt;code&gt;/introspect&lt;/code&gt; endpoint (October 2015)&lt;/h3&gt;
&lt;p&gt;Two years later, in October 2015, Justin Richer published RFC 7662, &lt;em&gt;OAuth 2.0 Token Introspection&lt;/em&gt; [@rfc-7662]. The mechanism: on every request, the resource server calls a &lt;code&gt;/introspect&lt;/code&gt; endpoint on the authorization server with the bearer token. The AS replies with the token&apos;s current state. If the token has been revoked, &lt;code&gt;/introspect&lt;/code&gt; returns &lt;code&gt;active: false&lt;/code&gt;, and the resource server denies the request.&lt;/p&gt;
&lt;p&gt;This is correct. It also reintroduces the WAM bottleneck that OAuth was designed to escape.&lt;/p&gt;
&lt;p&gt;For an AS serving billions of requests per day -- Microsoft Graph as one example, Google&apos;s IdP as another -- making &lt;code&gt;/introspect&lt;/code&gt; the per-request critical path turns the authorization server into a synchronous dependency on every API call against every resource server in the estate. Latency adds up. Availability becomes shared. If the AS has a bad five minutes, every resource server has a bad five minutes simultaneously. The architecture OAuth bought with self-contained tokens -- resource server scales independently of AS -- gets traded back for exactly the WAM property that motivated OAuth&apos;s existence.&lt;/p&gt;

RFC 7662 introspection is alive and well. It remains the right choice for opaque-token systems and on-premises IdPs where the resource server count is small, the per-request latency budget is generous, and the AS is well within capacity. The criticism here is structural and only applies at hyperscale public-cloud volumes. RFC 7662 was not killed by RFC 7009 or by CAE; it is a parallel path that continues to serve a substantial fraction of the deployed OAuth surface.
&lt;h3&gt;Patch 3: Make the token life so short revocation does not matter&lt;/h3&gt;
&lt;p&gt;The third patch was the obvious one. If you cannot revoke a token mid-life, make its life short. Issue access tokens with a minutes-long lifetime, the way early Microsoft experiments did. The revocation window collapses. Problem solved.&lt;/p&gt;
&lt;p&gt;Microsoft tried it. The retrospective is unusually candid. On April 21, 2020, Alex Weinert, then Director of Identity Security at Microsoft, published &lt;em&gt;Moving towards real time policy and security enforcement&lt;/em&gt; on the Azure Active Directory Identity Blog [@weinert-2020-04-real-time]. (The original lives at post ID 1276933 on Microsoft&apos;s tech community; the full body is preserved in Microsoft&apos;s Japanese translation on the jpazureid GitHub mirror [@jpazureid-blog-1-japanese].) The post names the failure mode in one sentence:&lt;/p&gt;

&quot;We have experimented with the &quot;blunt object&quot; approach of reduced token lifetimes but found they can degrade user experiences and reliability without eliminating risks.&quot; -- Alex Weinert, Microsoft, April 21, 2020 [@weinert-2020-04-real-time]
&lt;p&gt;Two things break. First, &lt;em&gt;user experience and reliability&lt;/em&gt;. Every short-lifetime boundary forces every active client to round-trip the IdP for a fresh token. For Outlook, Teams, Word Online, OneDrive, and every other client an enterprise user has open at once, that is a wave of token requests per user per cycle. Multiplied by Microsoft 365 active users, the load profile creates real outages. Network blips that would otherwise be invisible surface as failed refreshes, with user-visible re-authentication prompts. Second, &lt;em&gt;it does not eliminate the risk&lt;/em&gt;. A minutes-long window is still a window. A fired employee can read or exfiltrate a great deal of email in that window. You have paid the full user-experience cost and still left a non-trivial breach surface.&lt;/p&gt;
&lt;p&gt;This was the third failure. The negative space across the three patches defines the shape any real solution has to take: it must be &lt;em&gt;server-initiated&lt;/em&gt; (not RFC 7009), it must be &lt;em&gt;push-based&lt;/em&gt; rather than per-request poll (not RFC 7662), and it must &lt;em&gt;separate revocation from expiry&lt;/em&gt; so the IdP does not pay for every revocation with a refresh-load spike (not the short-lifetime patch). The three failures exhaust the surface of the obvious fix.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Each of the three patches fails for a different reason; together they rule out everything except server-initiated push subscription that decouples revocation from expiry.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the patches all fail, the next move has to be architectural. The first published statement of that architecture was Atul Tulshibagwale&apos;s February 2019 Google blog post -- and the move he proposed is the one Microsoft would ship three years later.&lt;/p&gt;
&lt;h2&gt;4. Four Generations of Session Enforcement&lt;/h2&gt;
&lt;p&gt;Walk forward through the genealogy of session enforcement and the breakthrough in Section 5 stops looking like a stroke of genius and starts looking like the only move the design space had left. Four generations, each killed by a documented limit of the previous one.&lt;/p&gt;
&lt;h3&gt;Generation 0: WAM (pre-2012)&lt;/h3&gt;
&lt;p&gt;Per-request synchronous round-trip to a Policy Decision Point. Instant revocation; chatty bottleneck; no federation. Killed by cloud-scale request rates and the rise of cross-organization SaaS, where the protected resource and the policy authority no longer lived in the same trust domain. WAM remains valuable in single-tenant enterprise contexts, but for the public-cloud API mesh it cannot scale.&lt;/p&gt;
&lt;h3&gt;Generation 1: Static-expiry JWT (2012-2020)&lt;/h3&gt;
&lt;p&gt;Self-contained signed bearer tokens validated locally at the resource server. Revocation approximated by expiry per RFC 6749 [@rfc-6749]. Throughput scales; federation works; revocation is acceptable when the lifetime is short and the threat model is benign. Killed by (a) the fired-employee window, (b) the three failed Section 3 patches, and (c) the philosophical pressure from &lt;a href=&quot;https://paragmali.com/blog/the-thirteen-months-that-made-zero-trust-unavoidable-the-win/&quot; rel=&quot;noopener&quot;&gt;Zero Trust&lt;/a&gt; to treat sessions as continuously re-evaluated.&lt;/p&gt;
&lt;h3&gt;Generation 2: Microsoft CAE (limited preview April 2020, GA January 10, 2022)&lt;/h3&gt;
&lt;p&gt;The first production solution. Limited preview launched in April 2020 with Alex Weinert&apos;s &lt;em&gt;Moving towards real time policy and security enforcement&lt;/em&gt; announcement [@weinert-2020-04-real-time]. Expanded public preview October 2020 [@simons-2020-10-expanded-preview; @vansurksum-2020-10-10]. General Availability January 10, 2022, announced by Alex Simons, Corporate VP for Program Management in the Microsoft Identity Division [@simons-2022-01-ga-rss].&lt;/p&gt;
&lt;p&gt;The architecture is a private push-subscription channel between Entra and a small set of Microsoft 365 resource providers, with a wire-level handshake (the &lt;code&gt;claims&lt;/code&gt; challenge) for telling the client to re-acquire a token reflecting new state. Access-token lifetime extends from the default 1 hour to up to 28 hours specifically for CAE-aware sessions [@ms-cae-concept]. We will unpack the mechanism in Section 5.&lt;/p&gt;
&lt;p&gt;The Gen-2 limitation that motivated Gen 3: the wire format is &lt;em&gt;Microsoft-internal&lt;/em&gt;. A SaaS vendor that wants the same revocation properties for its own resource provider cannot use Microsoft&apos;s CAE channel. The protocol does not federate.&lt;/p&gt;
&lt;h3&gt;Generation 3: OpenID SSF 1.0 + CAEP 1.0 + RISC 1.0 (Final Specifications, September 2, 2025)&lt;/h3&gt;
&lt;p&gt;The OpenID Foundation generalized the Microsoft pattern into a vendor-neutral specification. On September 2, 2025, three Final Specifications were approved: the Shared Signals Framework 1.0 (SSF), the Continuous Access Evaluation Profile 1.0 (CAEP), and the Risk and Incident Sharing and Coordination 1.0 (RISC) [@openid-three-final-specs; @openid-sharedsignals-wg].&lt;/p&gt;
&lt;p&gt;The wire envelope is IETF RFC 8417&apos;s Security Event Token (SET), published in July 2018 by Phil Hunt (Oracle), Michael Jones (Microsoft), William Denniss (Google), and Morteza Ansari (Cisco) [@rfc-8417]. A SET is a signed JWT carrying a single security event. The transport layer is RFC 8935 push (POST over TLS from transmitter to receiver) and RFC 8936 poll (recipient-initiated retrieval), both published November 2020 by Annabelle Backman and collaborators [@rfc-8935; @rfc-8936]. SSF defines the subscription model -- streams, subjects, transmitter and receiver metadata endpoints. CAEP and RISC define the &lt;em&gt;vocabulary&lt;/em&gt; of events that can ride that envelope.&lt;/p&gt;

IETF RFC 8417&apos;s standardized signed-JWT envelope for transmitting security-relevant events between systems. Each SET carries exactly one event with a well-defined event-type URI; the envelope is signature-protected and timestamp-bearing. SET is the wire format underlying CAEP, SSF, and RISC, as well as Microsoft&apos;s internal CAE protocol [@rfc-8417].
&lt;p&gt;RFC 8417 was a cross-vendor IETF effort that pre-dated the OpenID Shared Signals working group by a year. Phil Hunt was at Oracle; Michael Jones at Microsoft; William Denniss at Google; Morteza Ansari at Cisco. The envelope-only design -- leaving event vocabularies to higher-layer profiles -- is what allowed both Microsoft&apos;s internal protocol and the OpenID profiles to converge on the same wire format without coordination [@rfc-8417].&lt;/p&gt;

flowchart TD
    L4[&quot;Layer 4: Event vocabularies&lt;br /&gt;CAEP 1.0 (session) and RISC 1.0 (account)&quot;]
    L3[&quot;Layer 3: Subscription and stream model&lt;br /&gt;OpenID SSF 1.0&quot;]
    L2[&quot;Layer 2: HTTP transport&lt;br /&gt;RFC 8935 push, RFC 8936 poll&quot;]
    L1[&quot;Layer 1: Signed event envelope&lt;br /&gt;RFC 8417 Security Event Token (SET)&quot;]
    L4 --&amp;gt; L3
    L3 --&amp;gt; L2
    L2 --&amp;gt; L1
&lt;p&gt;The generation chain has a documented engineering reason for each transition. The comparison matrix below pulls the essentials together.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Revocation latency&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Weaknesses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;WAM (Gen 0)&lt;/td&gt;
&lt;td&gt;pre-2012&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;td&gt;Authoritative state, instant enforcement&lt;/td&gt;
&lt;td&gt;No federation, per-request bottleneck&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static-expiry JWT (Gen 1)&lt;/td&gt;
&lt;td&gt;2012-2020&lt;/td&gt;
&lt;td&gt;Up to token lifetime (1h-24h)&lt;/td&gt;
&lt;td&gt;O(1) RP validation, federation works&lt;/td&gt;
&lt;td&gt;No revocation; fired-employee window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Short-lifetime patch&lt;/td&gt;
&lt;td&gt;mid-2010s&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Conceptually simple&lt;/td&gt;
&lt;td&gt;Load amplification, window remains, UX degradation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 7662 introspection&lt;/td&gt;
&lt;td&gt;2015 onward&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;td&gt;Standardized, works for opaque tokens&lt;/td&gt;
&lt;td&gt;AS becomes per-request critical path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft CAE (Gen 2)&lt;/td&gt;
&lt;td&gt;2020-2022&lt;/td&gt;
&lt;td&gt;Up to 15 min critical; instant IP&lt;/td&gt;
&lt;td&gt;Push, decoupled from request rate, long tokens safe&lt;/td&gt;
&lt;td&gt;Microsoft-internal protocol; tiny RP set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenID SSF/CAEP (Gen 3)&lt;/td&gt;
&lt;td&gt;2025 onward&lt;/td&gt;
&lt;td&gt;Vendor-dependent&lt;/td&gt;
&lt;td&gt;Vendor-neutral standard, cross-SaaS&lt;/td&gt;
&lt;td&gt;Receiver adoption still early&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

flowchart LR
    G0[&quot;Gen 0: WAM&lt;br /&gt;per-request PDP&quot;]
    G1[&quot;Gen 1: Static-expiry JWT&lt;br /&gt;RFC 6749 (2012)&quot;]
    G2[&quot;Gen 2: Microsoft CAE&lt;br /&gt;GA January 2022&quot;]
    G3[&quot;Gen 3: OpenID SSF and CAEP&lt;br /&gt;Final September 2025&quot;]
    G0 -- &quot;cloud scale and federation&quot; --&amp;gt; G1
    G1 -- &quot;fired-employee window, patches fail&quot; --&amp;gt; G2
    G2 -- &quot;Microsoft-only, no cross-SaaS&quot; --&amp;gt; G3
&lt;p&gt;Knowing the lineage is not knowing the trick. What is the actual mechanism CAE deploys -- the thing that turns this standards-history arc into a feature that ships and makes 28-hour tokens defensible? It has three parts, and once you see them together, you understand why long tokens are safe.&lt;/p&gt;
&lt;h2&gt;5. Subscription, Claims Challenge, Extended Lifetime&lt;/h2&gt;
&lt;p&gt;Three innovations, none new in isolation, all unprecedented in combination. This is the section where you see the trick.&lt;/p&gt;
&lt;p&gt;Atul Tulshibagwale&apos;s 2019 framing names the move: &quot;Our vision for continuous access evaluation is based on a publish-and-subscribe (&apos;pub-sub&apos;) approach... It&apos;s complementary to federated or cert-based authentication... It&apos;s not as chatty as WAM... It doesn&apos;t impact latency for user access&quot; [@tulshibagwale-2019-google-blog]. Pub-sub is the third option between WAM&apos;s per-request chattiness and RFC 6749&apos;s fire-and-forget. Subscription is the channel; claims challenge is the wire-level handshake; extended lifetime is the user-experience prize.&lt;/p&gt;
&lt;h3&gt;Part 1: Subscription&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s CAE concept page describes the architecture in one sentence that rewards close reading:&lt;/p&gt;

Timely response to policy violations or security issues really requires a &apos;conversation&apos; between the token issuer Microsoft Entra, and the relying party (enlightened app). -- Microsoft Learn, *Continuous access evaluation in Microsoft Entra* [@ms-cae-concept]
&lt;p&gt;The word &lt;em&gt;conversation&lt;/em&gt; is the architecture. The relying party (a CAE-aware Microsoft 365 workload such as Exchange Online) subscribes to a finite, documented set of &lt;em&gt;critical events&lt;/em&gt; for the subjects it cares about. Entra pushes events to the RP as state changes. State is cached at the RP. On the hot path -- the per-request data plane -- the RP does an O(1) JWT signature verification plus an O(1) hash-table lookup of cached revocation state. No back-channel round-trip on the hot path. The 28-hour token costs no more to validate than the 1-hour token it replaced [@ms-cae-concept].&lt;/p&gt;
&lt;p&gt;This is the move that defeats RFC 7662. The state lives at the RP, not at the AS. The control-plane cost scales with the rate of &lt;em&gt;events&lt;/em&gt;, not the rate of &lt;em&gt;requests&lt;/em&gt;. Push, not poll.&lt;/p&gt;
&lt;h3&gt;Part 2: The claims challenge&lt;/h3&gt;
&lt;p&gt;When state at the RP changes -- because a push event has arrived saying &quot;this user&apos;s password has been reset&quot; -- the RP cannot reach into a request that has already been accepted and is being served. CAE is in-band with the &lt;em&gt;next&lt;/em&gt; request, not the current one. The next time the client presents the stale token, the RP rejects it with &lt;code&gt;HTTP 401&lt;/code&gt; and a specific header:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error=&quot;insufficient_claims&quot;,
                  claims=&quot;eyJhY2Nlc3NfdG9rZW4iOnsiYWNyc...&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;claims&lt;/code&gt; parameter is a base64url-encoded JSON object that tells the client what to re-acquire from the IdP. The Microsoft Authentication Library (MSAL) on the client decodes the challenge transparently and requests a new access token from Entra with the indicated claims. Entra either issues a fresh CAE-aware token (if authorization still holds) or rejects, forcing interactive re-authentication. The client retries the original API call with the new token [@ms-cae-app-resilience].&lt;/p&gt;

The HTTP-level mechanism by which a CAE-aware resource provider signals to a client that the presented token must be re-acquired with fresh state. The challenge is conveyed as a `WWW-Authenticate: Bearer error=&quot;insufficient_claims&quot;` header with a base64url-encoded `claims` parameter; current Microsoft Authentication Library (MSAL) releases decode and handle it automatically when the client app registration declares the `xms_cc` capability `[&quot;cp1&quot;]` [@ms-cae-app-resilience].
&lt;p&gt;This is the move that defeats RFC 7009. Revocation is initiated by the &lt;em&gt;resource provider&apos;s view of the IdP&apos;s state&lt;/em&gt;, not by the token holder. A fired employee&apos;s client cannot opt out of the claims challenge; the RP will not serve any further request until a fresh token arrives that reflects the post-revocation state.&lt;/p&gt;
&lt;p&gt;{`
// A real-shape WWW-Authenticate header from a CAE-aware resource provider.
// The &apos;claims&apos; parameter is base64url-encoded JSON.
const header = &apos;Bearer error=&quot;insufficient_claims&quot;, claims=&quot;eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTcyMDQ4MDA0MyJ9fX0=&quot;&apos;;&lt;/p&gt;
&lt;p&gt;// Extract the claims parameter
const match = header.match(/claims=&quot;([^&quot;]+)&quot;/);
const b64 = match ? match[1] : null;&lt;/p&gt;
&lt;p&gt;// base64url decode (Node &apos;Buffer&apos; would work; here we use the browser-safe approach)
function b64urlDecode(s) {
  s = s.replace(/-/g, &apos;+&apos;).replace(/_/g, &apos;/&apos;);
  while (s.length % 4) s += &apos;=&apos;;
  return atob(s);
}&lt;/p&gt;
&lt;p&gt;const claimsJson = b64urlDecode(b64);
console.log(JSON.parse(claimsJson));
// {
//   &quot;access_token&quot;: {
//     &quot;nbf&quot;: {
//       &quot;essential&quot;: true,
//       &quot;value&quot;: &quot;1720480043&quot;
//     }
//   }
// }
// MSAL reads this and requests a new token whose &apos;nbf&apos; (not-before) is at least
// the supplied timestamp -- i.e., a token issued after the state change.
`}&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;nbf&lt;/code&gt; (not-before) claim challenge is the most common shape: the RP is telling the client &quot;give me a token issued after this moment.&quot; The client requests one. Entra checks current state -- did the user get disabled? did the password get reset? did the risk score elevate? -- and either issues or denies. The wire format is simple enough to inspect in a browser tab, which is part of why the architecture has been able to standardize: there is no magic to reverse-engineer.&lt;/p&gt;
&lt;h3&gt;Part 3: Extended lifetime, the prize&lt;/h3&gt;
&lt;p&gt;The first two parts buy you the third. Once revocation is push-based and the claims challenge gives the RP a way to evict stale tokens within seconds of seeing a control-plane event, the expiry timer stops carrying the security weight. Tokens can live longer because the expiry is no longer the only revocation mechanism.&lt;/p&gt;
&lt;p&gt;Microsoft documents the upper bound as &quot;up to 28 hours&quot; for CAE-aware sessions [@ms-cae-concept; @ms-cae-app-resilience]. The default for non-CAE-capable clients remains 1 hour. This is the move that defeats the short-lifetime patch: the IdP load profile collapses because tokens refresh once a day, not on a per-minute cycle, and the revocation window is dramatically smaller -- not because expiry shrank, but because the channel now does the revocation work expiry used to do.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Long-lived access tokens are safe only when paired with a near-real-time revocation channel. CAE is the channel. Subscription provides the push, the claims challenge is the in-band handshake the push enables, and the 28-hour lifetime is what the channel buys -- not what the channel costs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The full round trip&lt;/h3&gt;
&lt;p&gt;The three parts interlock. The complete flow, from a state change at Entra to a re-validated request, runs end-to-end through every layer the article has named.&lt;/p&gt;

sequenceDiagram
    participant Admin
    participant Entra as Microsoft Entra
    participant Client as Client (MSAL)
    participant RP as Resource Provider (e.g. Exchange Online)
    Admin-&amp;gt;&amp;gt;Entra: Disable user account
    Entra-&amp;gt;&amp;gt;RP: Push critical-event SET (account disabled)
    Note over RP: Updates cached revocation state for (sub, tenant)
    Client-&amp;gt;&amp;gt;RP: GET /me/messages (Authorization Bearer old token)
    Note over RP: Validates JWT signature O(1), checks cached state
    RP--&amp;gt;&amp;gt;Client: 401 plus WWW-Authenticate insufficient_claims
    Note over Client: MSAL parses claims challenge from header
    Client-&amp;gt;&amp;gt;Entra: Token request with claims
    Note over Entra: Checks current user state, account is disabled
    Entra--&amp;gt;&amp;gt;Client: 400 invalid_grant or interactive re-auth required
    Note over Client: User cannot recover, session terminates
&lt;p&gt;Three moves, one design. Remove any one and the system collapses. Subscription without a claims challenge gives you push events the RP cannot act on at the wire. Claims challenge without subscription gives you a 401 mechanism with no information to decide when to fire it. Extended lifetime without either gives you Generation 1&apos;s fired-employee window. The 28-hour token is not the &lt;em&gt;cost&lt;/em&gt; of CAE; it is what CAE &lt;em&gt;purchases&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is the design. What does it actually do in production today, and where does it stop?&lt;/p&gt;
&lt;h2&gt;6. CAE as Deployed in Microsoft Entra (2026)&lt;/h2&gt;
&lt;p&gt;Concrete answers to concrete questions. Which events trigger CAE? Who participates? What is the actual SLA? How long do tokens actually live? No marketing language; only what Microsoft Learn currently documents.&lt;/p&gt;
&lt;h3&gt;Critical event evaluation events&lt;/h3&gt;
&lt;p&gt;Microsoft Learn lists exactly five events that drive &lt;em&gt;critical event evaluation&lt;/em&gt; at the IdP-to-RP boundary [@ms-cae-concept]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user account is deleted or disabled.&lt;/li&gt;
&lt;li&gt;A password for a user is changed or reset.&lt;/li&gt;
&lt;li&gt;Multi-factor authentication is enabled for the user.&lt;/li&gt;
&lt;li&gt;An administrator explicitly revokes all refresh tokens for a user.&lt;/li&gt;
&lt;li&gt;High user risk is detected by Microsoft Entra ID Protection.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These five events propagate from Entra to the participating CAE-aware resource providers via the push channel. Microsoft&apos;s published service-level objective is &quot;up to 15 minutes&quot; for critical-event propagation [@ms-cae-concept]. That is not the same as &quot;instant.&quot; The phrase to avoid is &quot;CAE delivers instant revocation&quot;; the accurate phrase is &quot;CAE delivers near-real-time revocation, typically within 15 minutes for critical events.&quot;&lt;/p&gt;
&lt;p&gt;A separate scenario -- &lt;em&gt;Conditional Access policy evaluation&lt;/em&gt; -- covers network and IP-location changes. Here the SLA is different: IP-location enforcement is &lt;strong&gt;instant&lt;/strong&gt; per Microsoft&apos;s published documentation [@ms-cae-concept]. The difference is mechanical. IP location is a property the RP sees directly on every request (the source IP of the incoming HTTP connection); the RP can compare it against the location constraints attached to the session and reject locally with no propagation delay. Critical events have to travel from Entra to the RP through the event channel, and that travel has a 15-minute budget at Microsoft 365 scale.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Propagation&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Account deleted or disabled&lt;/td&gt;
&lt;td&gt;Entra ID directory&lt;/td&gt;
&lt;td&gt;Up to 15 min&lt;/td&gt;
&lt;td&gt;Honored by Exchange Online, SharePoint Online, Teams, Graph (CA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password changed or reset&lt;/td&gt;
&lt;td&gt;Entra ID directory&lt;/td&gt;
&lt;td&gt;Up to 15 min&lt;/td&gt;
&lt;td&gt;Same RP set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MFA enabled for user&lt;/td&gt;
&lt;td&gt;Entra ID directory&lt;/td&gt;
&lt;td&gt;Up to 15 min&lt;/td&gt;
&lt;td&gt;Same RP set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All refresh tokens revoked (admin)&lt;/td&gt;
&lt;td&gt;Entra ID admin action&lt;/td&gt;
&lt;td&gt;Up to 15 min&lt;/td&gt;
&lt;td&gt;Same RP set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High user risk detected&lt;/td&gt;
&lt;td&gt;Entra ID Protection&lt;/td&gt;
&lt;td&gt;Up to 15 min&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SharePoint Online does not honor user-risk events&lt;/strong&gt; [@ms-cae-concept]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP location changed (CA policy)&lt;/td&gt;
&lt;td&gt;Resource-provider observation&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;td&gt;Conditional Access policy evaluation path; strict location enforcement [@ms-strict-location-enforcement]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Microsoft Defender for Endpoint and Microsoft Intune (MDM) are &lt;em&gt;signal sources&lt;/em&gt; into Conditional Access. They contribute to the risk score and device-compliance state that drive CA policy decisions, but they are &lt;strong&gt;not&lt;/strong&gt; CAE-consuming resource providers. They do not subscribe to Entra critical-event notifications and they do not enforce the claims-challenge handshake on token-bearing requests. The CAE-aware RP set is exactly: Exchange Online, SharePoint Online, Microsoft Teams, and Microsoft Graph (the last only for Conditional Access policy evaluation) [@ms-cae-concept]. If you read older deck slides or vendor blog posts that list MDE or Intune as CAE participants, they are conflating the signal-source role with the resource-provider role.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The SharePoint Online user-risk caveat is a concrete example of why &quot;CAE-aware&quot; is not a binary property at the workload level. SharePoint Online is fully CAE-aware for the first four critical events on the list; it just does not subscribe to user-risk events specifically. The lesson is that you must read the per-workload documentation carefully when designing controls that depend on a specific event&apos;s enforcement [@ms-cae-concept].&lt;/p&gt;
&lt;h3&gt;Workloads that participate&lt;/h3&gt;
&lt;p&gt;The CAE-aware resource-provider set, per Microsoft Learn [@ms-cae-concept]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exchange Online&lt;/strong&gt; -- full CAE consumer (initial implementation, October 2020).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SharePoint Online&lt;/strong&gt; -- full CAE consumer, with the user-risk caveat noted above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Teams&lt;/strong&gt; -- full CAE consumer (initial implementation), per Alex Simons&apos;s January 2022 GA announcement [@simons-2022-01-ga-rss].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Graph&lt;/strong&gt; -- consumes Conditional Access policy evaluation events (the IP-location instant path); narrower scope than the M365 productivity workloads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Client-side support is also explicit. Microsoft&apos;s compatibility tables in the CAE concept page enumerate which client and server combinations are &lt;em&gt;Supported&lt;/em&gt;, &lt;em&gt;Partially supported&lt;/em&gt;, or &lt;em&gt;Not Supported&lt;/em&gt; on every major operating system and form factor [@ms-cae-concept]. Office web apps against SharePoint Online and Exchange Online are documented as &lt;em&gt;Not Supported&lt;/em&gt; on several combinations; every Teams client surface shows as &lt;em&gt;Partially supported&lt;/em&gt;. The point is not that CAE is broken on these surfaces -- it is that Microsoft documents the rough edges in primary source, and tenant administrators who care about specific scenarios must read the table.&lt;/p&gt;
&lt;h3&gt;Tokens and clients&lt;/h3&gt;
&lt;p&gt;The default access-token lifetime for CAE-aware sessions is up to 28 hours; the default for non-CAE-capable clients remains 1 hour [@ms-cae-concept; @ms-cae-app-resilience]. Client support requires a current Microsoft Authentication Library (MSAL) release on the target platform: the 4.x line for .NET and JavaScript; the appropriate current line for Python, Java, Android, iOS, or macOS, per each SDK&apos;s own release stream. Microsoft Learn&apos;s &lt;em&gt;Use Continuous Access Evaluation enabled APIs&lt;/em&gt; page enumerates per-SDK guidance [@ms-cae-app-resilience]. The app registration must also declare the &lt;code&gt;xms_cc&lt;/code&gt; client capability with value &lt;code&gt;[&quot;cp1&quot;]&lt;/code&gt; to advertise CAE-handling support to the IdP [@ms-cae-app-resilience].&lt;/p&gt;

An app-registration claim by which a client advertises support for CAE-aware token issuance. The canonical wire-level value in the issued JWT is lowercase `&quot;cp1&quot;` (Microsoft&apos;s developer docs show both `&quot;cp1&quot;` and `&quot;CP1&quot;`; negotiation is case-insensitive but the token claim is lowercase). It signals that the client&apos;s MSAL implementation can decode and act on a `WWW-Authenticate: Bearer error=&quot;insufficient_claims&quot;` response by parsing the `claims` parameter and re-acquiring a token. Without it, Entra issues the default 1-hour token and the resource provider falls back to standard expiry [@ms-cae-app-resilience].

A Microsoft 365 workload (Exchange Online, SharePoint Online, Teams, or Microsoft Graph for Conditional Access policy) that consumes Entra&apos;s critical-event notifications and enforces them on subsequent token-bearing requests via the claims-challenge handshake. This is a narrower meaning than the generic OAuth 2.0 sense of &quot;resource server&quot;; in CAE, &quot;resource provider&quot; specifically means a workload that has implemented the CAE participation contract with Entra [@ms-cae-concept].

Microsoft documents an *upper bound* on token lifetime. The actual lifetime issued for any given session is variable and can be shorter. CAE-aware sessions can also be refreshed silently as long as the channel signals nothing has changed. Practically, this means most users with CAE-aware clients on M365 productivity workloads almost never see an interactive re-authentication prompt during normal working hours [@ms-cae-concept].
&lt;h3&gt;A migration note for older tenants&lt;/h3&gt;
&lt;p&gt;Tenant administrators with Conditional Access policies that pre-date GA may carry legacy &quot;strict location enforcement&quot; preview settings. Microsoft has since migrated the feature into GA, and the current Microsoft Learn page &lt;em&gt;Strictly enforce location policies using continuous access evaluation&lt;/em&gt; documents the post-migration configuration model [@ms-strict-location-enforcement]. Administrators should verify their policies after each major Conditional Access feature wave to ensure preview-to-GA migrations have been picked up.&lt;/p&gt;
&lt;p&gt;CAE is one approach among several. Where does it sit relative to introspection-per-request, identity-aware proxies, DPoP, and the cross-vendor OpenID standard? The design space is small enough to map cleanly.&lt;/p&gt;
&lt;h2&gt;7. Competing Approaches and Their Relation to CAE&lt;/h2&gt;
&lt;p&gt;Five named methods occupy adjacent positions in the design space. Some compete; some compose. The map matters because deployments that confuse the two get wrong answers.&lt;/p&gt;
&lt;h3&gt;CAE versus OpenID SSF and CAEP 1.0&lt;/h3&gt;
&lt;p&gt;Same architecture, different implementations. Microsoft CAE solves the Microsoft estate via a Microsoft-internal protocol; OpenID SSF and CAEP solve the cross-vendor SaaS long tail via a public standard atop RFC 8417 [@openid-three-final-specs; @openid-ssf-1_0-final; @openid-caep-1_0]. The two are convergent rather than rivalrous: Microsoft is moving toward also acting as an SSF transmitter and receiver alongside its first-party CAE protocol, and other vendors are building SSF receivers that can consume signals from any transmitter, including Microsoft.&lt;/p&gt;
&lt;p&gt;The Authenticate 2025 interop event in October 2025 was the first whose tested text was the Final-Specification version of SSF [@openid-authenticate-2025-interop]. Multi-vendor SSF and CAEP interoperability has been demonstrated at successive Gartner IAM Summit interop events as well. At the March 2024 London summit, SGNL&apos;s CAEP Hub interoperated as both transmitter and receiver with Cisco Duo, Okta, SailPoint, and Helisoft on the &lt;code&gt;session-revoked&lt;/code&gt; CAEP event [@sgnl-2024-04-interop]. Okta&apos;s own blog characterizes the March 2025 London summit as &quot;a significant industry shift toward interconnected, real-time security&quot; with &quot;interoperable implementations from pioneers like Okta, Google, IBM, Omnissa, SailPoint, and Thales&quot; [@okta-shared-signals].&lt;/p&gt;
&lt;p&gt;Tim Cappalli, who joined Okta after his time at Microsoft, co-chairs the OpenID Shared Signals Working Group alongside Atul Tulshibagwale (SGNL, formerly Google) [@tulshibagwale-sgnl-2023-08-qanda; @openid-sharedsignals-wg]. The cross-vendor co-chair arrangement is part of why the Final Specifications passed without significant vendor pushback: the people doing the standardization had visibility into both Microsoft&apos;s and Google&apos;s prior implementations.&lt;/p&gt;
&lt;h3&gt;CAE versus RFC 7662 introspection&lt;/h3&gt;
&lt;p&gt;Parallel paths, not competitors. RFC 7662 introspection [@rfc-7662] continues to be the right answer for opaque-token systems and on-premises IdPs where the AS-to-RP per-request round-trip is acceptable. CAE wins at hyperscale public-cloud volumes specifically because it inverts the per-request dependency: state pushes to the RP once and lives in cache; the data plane does not consult the AS on every request. If you are building a B2B integration with a small RP count and a few hundred requests per second, RFC 7662 is fine. If you are building Exchange Online, it is not.&lt;/p&gt;
&lt;h3&gt;CAE versus DPoP and mTLS-bound tokens&lt;/h3&gt;
&lt;p&gt;Complementary, not competitive. The threat model for CAE is &lt;em&gt;stale authorization&lt;/em&gt;: the authorization decision at sign-in is no longer accurate, because the user has been disabled, their password has been reset, their risk score has changed, or their network location has shifted. The threat model for proof-of-possession is &lt;em&gt;stolen tokens&lt;/em&gt;: an attacker holding a bearer token that was legitimately issued to a different party.&lt;/p&gt;
&lt;p&gt;RFC 9449, &lt;em&gt;OAuth 2.0 Demonstrating Proof of Possession (DPoP)&lt;/em&gt;, published September 2023 by Daniel Fett and collaborators [@rfc-9449-dpop], binds an access token to a client-held key pair: a DPoP-bound token can only be replayed by an attacker who also stole the private key. RFC 8705, &lt;em&gt;OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens&lt;/em&gt;, published February 2020 by Brian Campbell and collaborators [@rfc-8705-mtls], does the same thing using mTLS certificates. Both are sender-constrained-token mechanisms; both close the bearer-token-replay attack surface.&lt;/p&gt;
&lt;p&gt;CAE does not address token theft. A stolen CAE-aware token is still usable by the attacker until the IdP or RP becomes aware of the compromise. A DPoP-bound CAE-aware token closes both gaps: the attacker cannot replay it, and even if they could, the channel can revoke it within minutes. The correct deployment pattern is to combine CAE with DPoP or mTLS-binding where the application threat model warrants both.&lt;/p&gt;
&lt;h3&gt;CAE versus BeyondCorp-style identity-aware proxies&lt;/h3&gt;
&lt;p&gt;Different architectural layer. Identity-aware proxies (Google IAP, Cloudflare Access, AWS Verified Access) sit &lt;em&gt;in front of&lt;/em&gt; the resource server and enforce policy at the proxy. They have full visibility into per-request state and can do instant revocation by terminating the connection at the proxy when policy changes. This is correct for proxy-fronted workloads but does not scale to the long tail of API surfaces that cannot or will not sit behind a proxy. CAE pushes the enforcement into the resource server itself, which is what lets it work for native cloud APIs and federated SaaS where the proxy model would not.&lt;/p&gt;
&lt;h3&gt;A note on PRT theft&lt;/h3&gt;
&lt;p&gt;CAE does not address attacks at the &lt;a href=&quot;https://paragmali.com/blog/inside-the-primary-refresh-token-the-cryptographic-seam-betw/&quot; rel=&quot;noopener&quot;&gt;Primary Refresh Token (PRT)&lt;/a&gt; layer. The PRT is a long-lived refresh credential Windows uses to mint access tokens silently from a logged-in session. A stolen PRT can mint CAE-aware access tokens that are, from Entra&apos;s perspective, legitimately issued -- the attacker holds a credential the IdP still recognizes. CAE will only catch this if the user is revoked, the password is reset, or one of the other critical events fires &lt;em&gt;after&lt;/em&gt; the PRT theft. The Pass-the-PRT attack class therefore bypasses CAE entirely; defenses for that layer are out of scope here and are a separate engineering problem.&lt;/p&gt;
&lt;h3&gt;Mapping the design space&lt;/h3&gt;
&lt;p&gt;The table is the cleanest way to see who competes with whom and who composes with whom.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Solves&lt;/th&gt;
&lt;th&gt;Composes with CAE&lt;/th&gt;
&lt;th&gt;Competes with CAE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;OpenID SSF/CAEP 1.0&lt;/td&gt;
&lt;td&gt;Cross-vendor revocation&lt;/td&gt;
&lt;td&gt;Yes (CAE is a Microsoft implementation of the same pattern)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 7662 introspection&lt;/td&gt;
&lt;td&gt;Opaque-token revocation at modest scale&lt;/td&gt;
&lt;td&gt;Parallel path&lt;/td&gt;
&lt;td&gt;At hyperscale only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DPoP (RFC 9449)&lt;/td&gt;
&lt;td&gt;Sender-constrained tokens&lt;/td&gt;
&lt;td&gt;Yes (compose for full coverage)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mTLS-bound tokens (RFC 8705)&lt;/td&gt;
&lt;td&gt;Sender-constrained tokens&lt;/td&gt;
&lt;td&gt;Yes (compose for full coverage)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity-aware proxy&lt;/td&gt;
&lt;td&gt;Per-request policy at the proxy edge&lt;/td&gt;
&lt;td&gt;Composes for proxy-fronted workloads&lt;/td&gt;
&lt;td&gt;Different layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Short access-token lifetime&lt;/td&gt;
&lt;td&gt;Reduces revocation window mechanically&lt;/td&gt;
&lt;td&gt;Falls back when CAE not available&lt;/td&gt;
&lt;td&gt;Yes, and loses on the trade&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The reader who came to this article expecting a binary contest -- &quot;which one wins?&quot; -- has the wrong frame. The actual answer is that CAE is one move in a layered defense, and most production deployments will end up composing it with DPoP or mTLS for token binding, falling back to short lifetimes for non-CAE clients, and continuing to use introspection for opaque-token internal APIs.&lt;/p&gt;
&lt;p&gt;That handles deployment. But every architecture has limits. The reader has spent six sections climbing; the next section is the &lt;em&gt;humility&lt;/em&gt; beat where the descent begins.&lt;/p&gt;
&lt;h2&gt;8. Theoretical Limits: What CAE Cannot Do&lt;/h2&gt;
&lt;p&gt;Every architecture has a floor. The reader has spent six sections climbing; this is where the limits show up -- not as vendor laziness, but as physics, scale, and trust topology.&lt;/p&gt;
&lt;h3&gt;Limit 1: cannot revoke a token already in flight&lt;/h3&gt;
&lt;p&gt;Once a request has been accepted and is being served by the resource provider, CAE cannot reach into the RP&apos;s execution thread and abort it. The revocation applies to the &lt;em&gt;next&lt;/em&gt; request. A long-running operation -- a bulk Outlook export, a large SharePoint upload -- that began at 10:23:00 may complete normally even if the user is disabled at 10:23:01. The revocation takes effect the next time the client presents the token [@ms-cae-concept]. For most use cases the in-flight window is sub-second and the consequence is negligible; for long-running data egress, it matters.&lt;/p&gt;
&lt;h3&gt;Limit 2: cannot beat the 15-minute critical-event SLA for most events&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s published SLA is &quot;up to 15 minutes&quot; for critical-event propagation [@ms-cae-concept]. Only IP-location enforcement is instant. The 15-minute number is not a fundamental limit; it is engineering economics at hyperscale. Fanning out an event to every CAE-aware RP for every potentially affected subject across Microsoft 365&apos;s global infrastructure is what produces the budget. Smaller-scale deployments demonstrate much better numbers: TigerIdentity&apos;s commercial deployment self-reports sub-second end-to-end revocation in a tuned CAEP receiver configuration [@tigeridentity-caep-explained]. The architecture allows sub-second; Microsoft&apos;s particular deployment chooses 15 minutes because the alternative at its fan-out scale is prohibitively expensive.&lt;/p&gt;
&lt;p&gt;The strict physical floor sits below even the tuned implementations. An RP cannot enforce a revocation it has not yet learned about. The one-way network latency $L$ between IdP and RP sets the absolute minimum: with a transcontinental $L \approx 70,\text{ms}$, no push protocol can revoke faster than that, and pull protocols are necessarily worse. In practice, queuing, scheduling, and event-fanout dominate $L$ at scale -- but the floor remains.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The 15-minute SLA is not a fundamental limit; it is engineering economics at hyperscale. Sub-second is feasible at smaller fan-outs, and is the direction of travel as receiver implementations improve and as Microsoft&apos;s own event-distribution infrastructure ages well. But the strict physical floor is the network latency between IdP and RP; no cooperative protocol can do better than that.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Limit 3: cannot cover non-CAE-aware clients or resource providers&lt;/h3&gt;
&lt;p&gt;CAE is a cooperative protocol. Both the client (via the &lt;code&gt;xms_cc=cp1&lt;/code&gt; capability declaration) and the resource provider (via implementing the participation contract) must be CAE-aware [@ms-cae-app-resilience]. A non-CAE client receives a default 1-hour token and never sees a claims challenge; it relies on standard expiry. A non-CAE RP silently falls back to standard token expiry as well; the IdP&apos;s events have no consumer. The CAE-aware portion of the estate enjoys the new contract; the rest carries the old security debt unchanged.&lt;/p&gt;
&lt;p&gt;This is why audit posture matters. A tenant administrator who wants to argue that revocation latency for their workforce is &quot;under 15 minutes&quot; must be able to demonstrate that the client and RP combinations the workforce actually uses are CAE-aware. Microsoft&apos;s compatibility tables [@ms-cae-concept] document several Office-web-app and OneDrive-Win32-versus-SharePoint combinations as &lt;em&gt;Not Supported&lt;/em&gt; or &lt;em&gt;Partially supported&lt;/em&gt;; those gaps are part of the tenant&apos;s effective revocation profile, not someone else&apos;s problem.&lt;/p&gt;
&lt;h3&gt;Limit 4: cannot help if the resource provider itself is compromised&lt;/h3&gt;
&lt;p&gt;Revocation state lives at the RP. A compromised RP can simply ignore revocation events: keep serving requests against tokens Entra has signaled are invalid; misreport its own subscription state; drop events on the floor. CAE is a &lt;em&gt;cooperative&lt;/em&gt; protocol between trustworthy parties. It is not a defense against an RP that has been pwned. The OpenID SSF specification addresses this implicitly by defining receiver requirements (verification events, stream-control endpoints, signature verification on SETs), but no receiver requirement can compel a compromised receiver to obey the protocol.&lt;/p&gt;
&lt;p&gt;The threat model implication: an attacker who has compromised an RP does not need to bypass CAE. They simply do not implement it from the inside, and the protocol&apos;s design has no remedy. RP integrity is a prerequisite, not a guarantee.&lt;/p&gt;
&lt;h3&gt;Limit 5: cannot revoke a stolen PRT before it mints a new access token&lt;/h3&gt;
&lt;p&gt;As noted in Section 7, the Primary Refresh Token sits outside CAE&apos;s scope. A stolen PRT mints new CAE-aware access tokens that Entra treats as legitimately issued, because from Entra&apos;s perspective they &lt;em&gt;are&lt;/em&gt; legitimately issued -- the attacker is presenting a credential the IdP recognizes. CAE catches PRT theft only when one of the five critical events fires after the theft. If the attacker exfiltrates a PRT, refreshes a token, and immediately uses it, the access token is valid and the revocation channel has nothing to revoke.&lt;/p&gt;
&lt;p&gt;The SharePoint Online user-risk-event caveat is a useful concrete example of the per-feature limit pattern. Even within the four CAE-consuming RPs, feature support is not uniform; you cannot reason about CAE as a single boolean property at the workload level. Every event you care about must be checked against the specific RP that will enforce it [@ms-cae-concept].&lt;/p&gt;
&lt;h3&gt;The bounded design space&lt;/h3&gt;
&lt;p&gt;Put together, the five limits draw the perimeter of what CAE can do. It cannot stop in-flight requests. It cannot beat network latency at the strict floor or 15 minutes at Microsoft&apos;s chosen operating point. It cannot help non-participating clients or RPs. It cannot fix a compromised RP. It cannot revoke PRT-layer credentials before they mint new tokens. The honest summary is that the design space is &lt;em&gt;bounded&lt;/em&gt; -- the reader who internalizes the five limits has a calibrated sense of what is fundamentally possible, and can stop expecting CAE to be a single fix for revocation in all situations.&lt;/p&gt;
&lt;p&gt;The limits also map the open frontier. If those are the structural constraints, what are the OpenID Foundation and the SaaS long tail working on in 2026?&lt;/p&gt;
&lt;h2&gt;9. Open Problems (2026)&lt;/h2&gt;
&lt;p&gt;Final Specifications are necessary but not sufficient. CAEP 1.0, SSF 1.0, and RISC 1.0 were approved on September 2, 2025 [@openid-three-final-specs]. The question for 2026 is what &lt;em&gt;adoption&lt;/em&gt; and &lt;em&gt;extension&lt;/em&gt; look like. Five live problems.&lt;/p&gt;
&lt;h3&gt;1. Third-party SaaS receiver-adoption depth&lt;/h3&gt;
&lt;p&gt;The Final Specifications give every SaaS vendor a clean target to build against. The question is whether they will. Google Workspace shipped its SSF receiver in Closed Beta, supporting only the &lt;code&gt;session-revoked&lt;/code&gt; CAEP event at launch [@google-workspace-ssf-api]. That is one event out of CAEP 1.0&apos;s eight. The SaaS long tail -- Workday, ServiceNow, GitHub Enterprise, Atlassian, Salesforce -- has not, as of the Final Specification&apos;s first anniversary, shipped public receivers.&lt;/p&gt;
&lt;p&gt;For the &quot;fired employee with N SaaS apps&quot; scenario to be fully solved, every SaaS app in the user&apos;s bundle has to be a CAEP receiver subscribed to events from the enterprise IdP. The architecture is in place; the integration work is per-vendor and per-customer. This is the largest single determinant of CAE&apos;s real-world value over the next several years.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The Microsoft 365 estate enjoys near-complete CAE coverage because Microsoft built both the IdP and the resource providers. The cross-vendor story is fundamentally a coordination problem: every receiver has to be built, deployed, and configured to subscribe to events from every transmitter the enterprise uses. SSF 1.0 makes the integration tractable; it does not make the work disappear. Watch receiver coverage in 2026-2028 as the leading indicator of CAE&apos;s industry-wide impact.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2. CAE for non-human and agent identities&lt;/h3&gt;
&lt;p&gt;CAEP subject identifiers assume user-shaped or device-shaped subjects [@openid-caep-1_0]. Workload identities, service principals, and emerging &lt;a href=&quot;https://paragmali.com/blog/agentic-identity-on-windows-when-the-process-acting-on-your-/&quot; rel=&quot;noopener&quot;&gt;AI-agent identities&lt;/a&gt; sit outside the model as currently profiled. An agent acting on behalf of a user, with its own identity and its own session, is not yet covered by a Final-Specification profile. The Microsoft Entra &lt;em&gt;Conditional Access for Agent Identities&lt;/em&gt; workstream is a documented Microsoft Learn surface as of 2026 [@ms-conditional-access-agent-id] and is one of the workstreams that will eventually produce a CAEP profile for non-human subjects, but as of mid-2026 the cross-vendor standardization gap is open.&lt;/p&gt;
&lt;h3&gt;3. Cross-IdP federation of SSF streams&lt;/h3&gt;
&lt;p&gt;When tenant A federates to tenant B, the event-flow path crosses a trust boundary the current Final Specifications do not explicitly profile. If a user is disabled in tenant A&apos;s IdP, how does the revocation event reach the resource providers downstream in tenant B? The pieces -- transmitter, receiver, SET envelope, signed events -- are all in place; what is missing is the canonical profile for cross-IdP federation of SSF streams. This is a 2026-2027 OpenID Foundation workstream rather than a Final-Specification gap.&lt;/p&gt;
&lt;h3&gt;4. Bidirectional signal sharing&lt;/h3&gt;
&lt;p&gt;Today&apos;s CAE and CAEP deployments are largely IdP-as-transmitter, RP-as-receiver. The full vision is bidirectional: an RP that detects anomalous behavior (unusual access patterns, suspected automation, post-authentication risk signals) should be able to transmit those signals back to the IdP, which can then incorporate them into the next authorization decision. SGNL and similar vendors are building toward this model. The Final Specifications support bidirectional flow at the protocol level; the policy and operational pieces -- who trusts whom, what events flow which way, how an IdP weighs signals from an RP -- are still being worked out.&lt;/p&gt;
&lt;h3&gt;5. Reason-code convergence between CAEP and RISC&lt;/h3&gt;
&lt;p&gt;CAEP 1.0 and RISC 1.0 cover overlapping ground around credential mutation. CAEP defines a &lt;code&gt;credential-change&lt;/code&gt; event; RISC defines &lt;code&gt;account-credential-change-required&lt;/code&gt; [@openid-caep-1_0; @openid-sharedsignals-wg]. Implementers must choose, and vendor extensions proliferate where the spec leaves room. Reason-code convergence between the two profiles is incomplete; some receivers will subscribe to both streams to be safe, others will pick one and hope upstream transmitters agree. Over time the WG will likely consolidate; for 2026, the practical guidance is to support both event vocabularies in receiver code.&lt;/p&gt;

The first interoperability event whose tested text was the Final-Specification version of SSF took place at Authenticate 2025 in San Diego, October 13-15, 2025, hosted by the FIDO Alliance and coordinated by the OpenID Foundation Shared Signals Working Group [@openid-authenticate-2025-interop]. The event required that all participants with an SSF Transmitter pass the OpenID Foundation&apos;s free, open-source conformance tests. This was the fourth in a series of Gartner-IAM and Authenticate interops since March 2024, and the first conducted after SSF 1.0 was approved Final on September 2, 2025. The list of vendor participants has grown at each event; cross-vendor receiver coverage is the metric to watch.
&lt;p&gt;Given all this -- the architecture, the limits, the open frontier -- what should you actually do this week in your tenant and your code?&lt;/p&gt;
&lt;h2&gt;10. Turning CAE On in Your Tenant and Your Code&lt;/h2&gt;
&lt;p&gt;Three audiences, three checklists. Each section is what an engineer in that role needs to confirm or change to make CAE work in their environment.&lt;/p&gt;
&lt;h3&gt;For the tenant administrator&lt;/h3&gt;
&lt;p&gt;CAE has been auto-enabled by default for new Microsoft Entra tenants since the January 2022 GA [@simons-2022-01-ga-rss]. Tenants created before then may need to verify enablement in &lt;strong&gt;Conditional Access -&amp;gt; Session controls -&amp;gt; Customize continuous access evaluation&lt;/strong&gt;. The relevant signals to check:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CAE enablement state.&lt;/strong&gt; Confirm that the tenant-wide CAE policy is set to &lt;em&gt;Enabled&lt;/em&gt; rather than &lt;em&gt;Disabled&lt;/em&gt; or &lt;em&gt;Strict location&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-policy disable flags.&lt;/strong&gt; Some legacy CA policies carry per-policy CAE overrides. Audit any that explicitly disable CAE; the right default is to honor it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strict location enforcement migration.&lt;/strong&gt; Tenants with pre-GA &quot;strict location enforcement&quot; preview settings should verify that the policy has migrated to the current GA configuration model documented in Microsoft Learn [@ms-strict-location-enforcement].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit log baselines.&lt;/strong&gt; Sign-in logs surface &lt;code&gt;signInEventTypes&lt;/code&gt; with CAE-related entries; refresh-token issuance events and revocation events appear in the Entra ID audit log. Build a baseline before changing policies so you can detect drift.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;For the MSAL client developer&lt;/h3&gt;
&lt;p&gt;The client side has three things to confirm and one thing to test:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;MSAL version.&lt;/strong&gt; Use a current MSAL release on your client platform: 4.x for MSAL.NET and MSAL.js; the appropriate current line for MSAL Python, MSAL Java, MSAL Android, and MSAL for iOS/macOS, per each SDK&apos;s own release stream. Microsoft Learn&apos;s &lt;em&gt;Use Continuous Access Evaluation enabled APIs&lt;/em&gt; page enumerates the per-SDK guidance [@ms-cae-app-resilience]. Earlier major-version lines do not handle the claims challenge transparently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capability declaration.&lt;/strong&gt; The app registration must declare &lt;code&gt;xms_cc&lt;/code&gt; with value &lt;code&gt;[&quot;cp1&quot;]&lt;/code&gt; (lowercase is the canonical token-claim form; uppercase &lt;code&gt;&quot;CP1&quot;&lt;/code&gt; also works because negotiation is case-insensitive). This is the wire-level signal to Entra that the client can handle a CAE-aware token and the claims challenge that comes with it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claims-challenge handling.&lt;/strong&gt; MSAL helpers do this transparently in current SDK versions, but custom HTTP pipelines that bypass MSAL must implement the &lt;code&gt;WWW-Authenticate: Bearer error=&quot;insufficient_claims&quot;&lt;/code&gt; response handler manually. Decode the &lt;code&gt;claims&lt;/code&gt; parameter (base64url), pass it to &lt;code&gt;AcquireTokenInteractive&lt;/code&gt; or the equivalent, retry the original request with the new token.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;End-to-end test.&lt;/strong&gt; Trigger an admin password reset against a test user in a non-production tenant and verify that the next API call from a signed-in MSAL session surfaces the claims challenge and recovers cleanly. This is the single most useful confidence test; it exercises every layer of the protocol in one round trip.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;{`
// Illustrative: inspect an MSAL JS token-cache entry for the xms_cc capability
// marker. In real apps, MSAL handles capability negotiation; this is for
// educational inspection only.&lt;/p&gt;
&lt;p&gt;// A real-shape AccessTokenEntity from MSAL JS cache
const tokenEntity = {
  homeAccountId: &apos;abc.def-tenant&apos;,
  environment: &apos;login.microsoftonline.com&apos;,
  credentialType: &apos;AccessToken&apos;,
  clientId: &apos;11111111-2222-3333-4444-555555555555&apos;,
  tenantId: &apos;tenant-id&apos;,
  target: &apos;User.Read Mail.Read&apos;,
  // expiresOn is up to ~28 hours after cachedAt for CAE-aware sessions
  cachedAt: &apos;1748534400&apos;,
  expiresOn: &apos;1748635200&apos;,  // 28h later
  extendedExpiresOn: &apos;1748635200&apos;,
  // Capability declaration the app advertised at acquisition time
  requestedClaims: { xms_cc: [&apos;cp1&apos;] }
};&lt;/p&gt;
&lt;p&gt;const ttlSeconds = parseInt(tokenEntity.expiresOn) - parseInt(tokenEntity.cachedAt);
const ttlHours = ttlSeconds / 3600;
const isCaeAware = tokenEntity.requestedClaims &amp;amp;&amp;amp;
                   tokenEntity.requestedClaims.xms_cc &amp;amp;&amp;amp;
                   tokenEntity.requestedClaims.xms_cc
                     .some(c =&amp;gt; c.toLowerCase() === &apos;cp1&apos;);&lt;/p&gt;
&lt;p&gt;console.log(&apos;TTL hours:&apos;, ttlHours.toFixed(1));
console.log(&apos;CAE-aware:&apos;, isCaeAware);
// TTL hours: 28.0
// CAE-aware: true
// A TTL above ~1 hour with xms_cc cp1 is a strong indicator the session is
// CAE-aware and Entra issued an extended-lifetime token.
`}&lt;/p&gt;
&lt;h3&gt;For the custom-API author&lt;/h3&gt;
&lt;p&gt;This is the hardest path. To make a custom protected API a CAE-aware resource provider today, the first-party Microsoft pathway is not publicly available -- the CAE participation contract for the M365 productivity workloads is internal to Microsoft. The community-canonical implementation pattern is Damien Bowden&apos;s &lt;code&gt;damienbod/AspNetCoreMeIDCAE&lt;/code&gt; reference repository on GitHub [@damienbod-aspnetcoremeidcae], with an accompanying blog post walkthrough [@damienbod-blog-2022-04]. The repository (initial version April 3, 2022; updated through .NET 10 in late 2025) demonstrates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;xms_cc=cp1&lt;/code&gt; capability declaration on both the client and the API app registrations.&lt;/li&gt;
&lt;li&gt;The Microsoft.Identity.Web claims-challenge handling on the API side.&lt;/li&gt;
&lt;li&gt;The Razor Page client flow that catches a &lt;code&gt;401&lt;/code&gt; with the challenge header and re-acquires the token.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a fully standards-track pathway, the same custom API can be built as an OpenID SSF receiver consuming CAEP events from any SSF-compliant transmitter, using the RFC 8417 SET envelope over the RFC 8935 push transport [@rfc-8417; @rfc-8935]. Production-grade SSF receiver code is now available in commercial CAEP Hub products (SGNL, TigerIdentity) and a growing set of open-source libraries.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; CAE itself does not require add-on licensing for the basic critical-event evaluation across Microsoft 365 -- it is part of the Entra ID baseline for new tenants. The Microsoft Entra ID Protection feed that drives &lt;em&gt;high user risk detected&lt;/em&gt; events, however, requires Microsoft Entra ID P2 (or an equivalent SKU that includes Identity Protection). Confirm current licensing terms in the Microsoft licensing documentation before making procurement decisions; the lower SKUs cover four of the five critical events but not the risk-based one [@ms-cae-concept].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Observability&lt;/h3&gt;
&lt;p&gt;Sign-in logs and audit logs are where CAE behavior shows up. Look for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sign-in logs&lt;/strong&gt;: filter by &lt;code&gt;signInEventTypes&lt;/code&gt; containing CAE-related entries. CAE-aware sign-ins have a different telemetry shape than non-CAE sign-ins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token-issuance events&lt;/strong&gt;: refresh-token issuance against CAE-aware app registrations should show the extended lifetime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit log revocation entries&lt;/strong&gt;: administrator revocation actions and Identity-Protection-driven revocations appear here; cross-correlate with the resource-provider-side telemetry to validate end-to-end propagation.&lt;/li&gt;
&lt;/ul&gt;

Use Microsoft Graph PowerShell to enumerate the tenant&apos;s CAE configuration and then trigger a synthetic test: 1) read `Get-MgIdentityConditionalAccessPolicy` to verify the relevant CA policies have CAE enabled in their `SessionControls.ContinuousAccessEvaluation` block; 2) create a test user, sign them in via Outlook on the Web; 3) reset their password via `Update-MgUser`; 4) observe in the audit log that the password reset propagates to a CAE event, and verify in Outlook on the Web that the next refresh surfaces a re-authentication prompt within the 15-minute SLA. This is the simplest end-to-end confidence test that does not require modifying any production resource.
&lt;h3&gt;Defaults are good&lt;/h3&gt;
&lt;p&gt;The most common engineering recommendation here is to leave the defaults alone. CAE on, default tenant settings, current MSAL clients, &lt;code&gt;xms_cc=cp1&lt;/code&gt; on every new app registration. The configuration surface area is small precisely because the design is right: there are not many knobs to turn. The work is in confirming that the client and RP combinations your users actually exercise are CAE-aware, and in monitoring the audit logs to catch drift.&lt;/p&gt;
&lt;p&gt;That is what to do. The last section is what to remember -- the misconceptions every team carries into a CAE conversation, and the answers that close them.&lt;/p&gt;
&lt;h2&gt;11. FAQ and Coda&lt;/h2&gt;

No. The published SLA is up to 15 minutes for the five critical events; only IP-location enforcement is instant. See Section 6 for the mechanical reason for the asymmetry and Section 8 Limit 2 for why 15 minutes is engineering economics rather than a fundamental limit [@ms-cae-concept].

No. CAE addresses *stale authorization* (the original authorization decision is no longer correct), not *stolen tokens* (an attacker is presenting a token that was legitimately issued to someone else). For token theft, use a sender-constrained-token construction: DPoP per RFC 9449 [@rfc-9449-dpop] or mTLS-bound tokens per RFC 8705 [@rfc-8705-mtls]. Both compose cleanly with CAE; a DPoP-bound CAE-aware token is the strongest commonly-deployed combination today, closing both the replay attack surface and the stale-authorization gap.

No. SSF 1.0, CAEP 1.0, and RISC 1.0 were approved as OpenID Foundation Final Specifications on September 2, 2025 -- see Section 4 for the standards-stack treatment [@openid-three-final-specs].

No. MDE and Intune are signal sources into Conditional Access, not CAE-consuming resource providers; see the Section 6 Common-misconception callout for the full distinction and the CAE-aware RP set [@ms-cae-concept].

*Not when the resource provider is CAE-aware.* The token lifetime stops carrying the revocation weight; the channel does. A CAE-aware RP can revoke a 28-hour token within 15 minutes of a critical event, which is a strictly better revocation profile than a 1-hour token with no channel (revocable only at the 1-hour expiry boundary in the worst case) [@ms-cae-concept]. *Yes*, however, when the RP is *not* CAE-aware: the token then carries its full lifetime as the revocation window, and longer is worse. The architectural rule: only issue extended-lifetime tokens to clients whose RPs are CAE-aware -- which is exactly what the `xms_cc=cp1` capability negotiation enforces [@ms-cae-app-resilience].

No. CAE is specific to OAuth 2.0 and OpenID Connect access tokens. SAML assertions have their own lifetime and replay-protection model and are not in scope for the CAE participation contract or for the OpenID SSF/CAEP profiles [@ms-cae-concept; @openid-caep-1_0]. If you are still operating SAML-fronted workloads, the analogous design problem (revocation between sign-in and assertion expiry) is solved differently and is largely a per-product implementation question rather than a standards story.
&lt;h3&gt;Coda: the bargain&lt;/h3&gt;
&lt;p&gt;The OAuth 2.0 designers in 2012 took a deliberate trade: short-lived self-contained tokens were the price they paid to escape the WAM bottleneck. The trade was correct for the web they were designing for. It became wrong the moment enterprises ran compliance-bound SaaS at scale on top of those tokens. Three obvious patches were tried -- the &lt;code&gt;/revoke&lt;/code&gt; endpoint, the &lt;code&gt;/introspect&lt;/code&gt; endpoint, the short-lifetime experiment -- and each failed for a distinct reason: the wrong party initiates revocation; the AS becomes a per-request critical path; expiry as a blunt instrument creates load and reliability problems while still leaving a window.&lt;/p&gt;
&lt;p&gt;What replaced them was an architecture that took two facts seriously. First, revocation has to be push from the IdP to the RP -- not pull from RP to AS, not client-initiated POST to &lt;code&gt;/revoke&lt;/code&gt;. Second, expiry and revocation can be separated: once the channel handles revocation, expiry can be measured in days rather than minutes. The 15-minute critical-event SLA and the up-to-28-hour token lifetime are two halves of the same bargain. Microsoft Entra ships them together because they only work together; the OpenID Foundation has standardized the same pattern across vendors because the long tail of SaaS faces the same problem.&lt;/p&gt;
&lt;p&gt;The architecture is settled; the adoption is in progress. The CAEP, SSF, and RISC Final Specifications give every SaaS vendor a tractable target. The Microsoft 365 estate is already covered. Cross-vendor receiver coverage is the metric that will decide how much of the 2026 enterprise identity surface actually inherits the bargain -- and that, more than any further protocol work, is the story to watch over the next several years.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;continuous-access-evaluation&quot; keyTerms={[
  { term: &quot;Continuous Access Evaluation (CAE)&quot;, definition: &quot;Microsoft Entra&apos;s push-subscription channel that lets a resource provider revoke an already-issued access token in near-real-time without waiting for expiry.&quot; },
  { term: &quot;Web Access Management (WAM)&quot;, definition: &quot;Pre-2012 enterprise identity pattern with synchronous per-request PDP round-trips; instant revocation but no scale or federation.&quot; },
  { term: &quot;Security Event Token (SET)&quot;, definition: &quot;IETF RFC 8417 signed-JWT envelope for transmitting security events; the wire format under CAEP, SSF, and RISC.&quot; },
  { term: &quot;Claims Challenge&quot;, definition: &quot;HTTP 401 with WWW-Authenticate insufficient_claims header and a base64url-encoded claims parameter; the wire-level mechanism by which a CAE-aware RP tells a client to re-acquire a token.&quot; },
  { term: &quot;xms_cc capability&quot;, definition: &quot;App-registration claim with canonical value cp1 (case-insensitive) by which a client advertises CAE-handling support to Entra.&quot; },
  { term: &quot;Resource Provider (RP) in CAE&quot;, definition: &quot;Exchange Online, SharePoint Online, Teams, or Microsoft Graph; a workload that consumes Entra&apos;s critical-event notifications.&quot; },
  { term: &quot;OpenID Shared Signals Framework (SSF)&quot;, definition: &quot;Vendor-neutral OpenID Foundation Final Specification (September 2, 2025) for stream-based SET delivery between transmitters and receivers.&quot; },
  { term: &quot;Continuous Access Evaluation Profile (CAEP)&quot;, definition: &quot;OpenID Foundation Final Specification (September 2, 2025) defining session-level event types atop SSF.&quot; }
]} questions={[
  { q: &quot;Why does the standard OAuth 2.0 /revoke endpoint not solve the fired-employee problem?&quot;, a: &quot;Because it is client-initiated: an uncooperative token holder will not POST their token to /revoke, and the administrator on the other side does not possess the bearer token to POST.&quot; },
  { q: &quot;Why is RFC 7662 introspection unworkable as the next generation of revocation at hyperscale?&quot;, a: &quot;Because it reintroduces a synchronous per-request dependency on the authorization server, returning the architecture to the WAM bottleneck that OAuth was designed to escape.&quot; },
  { q: &quot;What three innovations interlock to make CAE work, and why are they only meaningful in combination?&quot;, a: &quot;Subscription (push channel from IdP to RP), claims challenge (the 401 plus WWW-Authenticate insufficient_claims handshake), and extended lifetime (up to 28 hours). Subscription without the claims challenge gives push events with no in-band way to act on them; claims challenge without subscription has no information to decide when to fire; extended lifetime without either reverts to Generation 1&apos;s fired-employee window.&quot; },
  { q: &quot;Why is the 15-minute critical-event SLA not a fundamental limit?&quot;, a: &quot;Because it is engineering economics at Microsoft 365&apos;s event-fanout scale, not architecture. Smaller-scale CAEP receivers (TigerIdentity, SGNL) demonstrate sub-second propagation. The strict physical floor is the one-way network latency between IdP and RP.&quot; },
  { q: &quot;Which Microsoft workloads are CAE-aware resource providers, and which are signal sources rather than RPs?&quot;, a: &quot;RPs: Exchange Online, SharePoint Online, Teams, and Microsoft Graph (for Conditional Access policy evaluation). Signal sources, not RPs: Microsoft Defender for Endpoint and Microsoft Intune.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>continuous-access-evaluation</category><category>microsoft-entra</category><category>oauth2</category><category>zero-trust</category><category>openid-shared-signals</category><category>identity-security</category><category>conditional-access</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>AD Is a Graph: How BloodHound Made Defenders Think Like Attackers</title><link>https://paragmali.com/blog/ad-is-a-graph-how-bloodhound-made-defenders-think-like-attac/</link><guid isPermaLink="true">https://paragmali.com/blog/ad-is-a-graph-how-bloodhound-made-defenders-think-like-attac/</guid><description>From Lambert&apos;s 2015 essay to Microsoft Security Exposure Management in 2024 -- how the attack-path graph became the default model for Active Directory security.</description><pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate><content:encoded>
**AD is a graph.** In April 2015 John Lambert named the missing model in two sentences. In August 2016 Andy Robbins, Rohan Vazarkar, and Will Schroeder shipped the tool that made it operational: BloodHound treats Active Directory as a directed graph of privilege relationships, queries it with Neo4j&apos;s Cypher, and turns weeks of red-team whiteboard work into a 200 ms shortest-path lookup. By November 2024 Microsoft itself shipped the same mental model as Microsoft Security Exposure Management. This article is half tool history (1998 academic attack graphs through OpenGraph 2025) and half graph-theory exposition: property graphs, BFS shortest paths, the edge taxonomy, and the open problem of *weighting* what is currently treated as one BFS hop per privilege.
&lt;h2&gt;1. How do I get from this user to Domain Admin?&lt;/h2&gt;
&lt;p&gt;In 2014, a red-team analyst with a help-desk account inside a 40,000-user Active Directory forest asks the question every red-team analyst asks: &lt;em&gt;is there a path from here to Domain Admins?&lt;/em&gt; The answer takes two analysts five days of PowerView scripts, hand-drawn whiteboard diagrams, and per-host RDP probing. The same question in 2024 is a ninety-character Cypher query that returns in 200 milliseconds.&lt;/p&gt;
&lt;p&gt;What happened in those ten years is the story of one sentence -- &lt;em&gt;defenders think in lists, attackers think in graphs&lt;/em&gt; -- becoming, in turn, a tool, a discipline, and a Microsoft product.&lt;/p&gt;
&lt;p&gt;The 2014 reality was a stack of CSV files. PowerView, the PowerShell enumeration toolkit Will Schroeder first published in August 2014 [@powersploit-repo], could dump every group membership, every &lt;a href=&quot;https://paragmali.com/blog/windows-access-control-25-years-of-attacks/&quot; rel=&quot;noopener&quot;&gt;Access Control Entry&lt;/a&gt;, and every active session from a low-privilege account [@powertools-powerview]. The outputs were rows. Hundreds of thousands of rows. Composing them into a coherent attack path was a job for a marker and a whiteboard, and the join keys were Distinguished Names that wrapped twice across an analyst&apos;s notebook page. Five days to map a single 40,000-user forest was not unusual. It was the price of doing business.&lt;/p&gt;
&lt;p&gt;The 2024 reality is a query. The analyst loads SharpHound&apos;s JSON dump into a Neo4j graph, opens the BloodHound web interface in a browser, types&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cypher&quot;&gt;MATCH p = shortestPath(
  (u:User {name:&apos;HELPDESK@CORP.LOCAL&apos;})-[*1..]-&amp;gt;(g:Group {name:&apos;DOMAIN ADMINS@CORP.LOCAL&apos;})
) RETURN p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and clicks Run. The graph renders. The shortest path is highlighted. The pivot points are circled. Time elapsed: 200 milliseconds on the database, plus a second for the browser to draw the SVG [@bloodhound-ce-repo].&lt;/p&gt;
&lt;p&gt;Whatever happened in those ten years has to be more than a software release. It has to be a change in how the entire community models the problem. The change has a date and a sentence. Both arrived in April 2015. Let us start with the sentence.&lt;/p&gt;
&lt;h2&gt;2. From ACL lists to graphs -- why the model matters&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;An access-control list is not the same as a graph -- and the difference is everything.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Consider five accounts in a hypothetical &lt;code&gt;CORP.LOCAL&lt;/code&gt; forest. Bob, a help-desk operator, has been granted &lt;code&gt;ForceChangePassword&lt;/code&gt; on Carol&apos;s account by a long-departed administrator who once needed to delegate password resets. Carol is a member of &lt;code&gt;Server Operators&lt;/code&gt;. &lt;code&gt;Server Operators&lt;/code&gt;, by default, can log on locally to Domain Controllers and back up the directory database. The Domain Controller hosts the &lt;a href=&quot;https://paragmali.com/blog/krbtgt-the-account-that-owns-active-directory/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;krbtgt&lt;/code&gt; account&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Three rows in three different audit reports. One attack path.&lt;/p&gt;
&lt;p&gt;A per-object Access Control List audit looks at Bob&apos;s row, sees a &lt;code&gt;ForceChangePassword&lt;/code&gt; ACE, and flags it as &quot;an over-broad delegation.&quot; It looks at Carol&apos;s row, sees that she belongs to &lt;code&gt;Server Operators&lt;/code&gt;, and flags it as &quot;a privileged group membership.&quot; It looks at the Domain Controller and sees that &lt;code&gt;Server Operators&lt;/code&gt; has logon rights, which is the default. Nothing in the audit composes the three facts. Reachability is not a property the report computes.&lt;/p&gt;

A set of nodes (vertices) and directed edges (arrows) between them. Each edge points from one node to another. A *path* is a sequence of edges that can be traversed in the direction of their arrows. A node B is *reachable* from a node A if some path leads from A to B.

A property of two nodes A and B in a directed graph: B is reachable from A if there exists a sequence of edges leading from A to B, regardless of length. Reachability is fundamentally a graph property and cannot be answered by inspecting any single edge in isolation.
&lt;p&gt;Now draw the same five accounts as a directed graph. Bob is a node. Carol is a node. &lt;code&gt;Server Operators&lt;/code&gt; is a node. The Domain Controller is a node. The &lt;code&gt;krbtgt&lt;/code&gt; account is a node. There is an edge from Bob to Carol labelled &lt;code&gt;ForceChangePassword&lt;/code&gt;. There is an edge from Carol to &lt;code&gt;Server Operators&lt;/code&gt; labelled &lt;code&gt;MemberOf&lt;/code&gt;. There is an edge from &lt;code&gt;Server Operators&lt;/code&gt; to the Domain Controller labelled &lt;code&gt;CanRDP&lt;/code&gt;. There is an edge from the Domain Controller to &lt;code&gt;krbtgt&lt;/code&gt; labelled &lt;a href=&quot;https://paragmali.com/blog/two-checkmarks-and-the-keys-to-the-kingdom-how-active-direct/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DCSync&lt;/code&gt;&lt;/a&gt;. Trace the arrows. Bob can reach &lt;code&gt;krbtgt&lt;/code&gt;.&lt;/p&gt;

flowchart LR
    Bob([Bob, help-desk]) --&amp;gt;|ForceChangePassword| Carol([Carol])
    Carol --&amp;gt;|MemberOf| SO([Server Operators])
    SO --&amp;gt;|CanRDP| DC([Domain Controller])
    DC --&amp;gt;|DCSync| Krb([krbtgt])
&lt;p&gt;The graph form makes reachability visually obvious. The list form does not. This is not a presentation difference. It is a data-model difference, and it is the difference that decides whether a tool can answer the question &lt;em&gt;&quot;is there a path from A to B?&quot;&lt;/em&gt; at all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Reachability is a property of the graph that the list does not, in general, express. This is not a UX difference. It is a data-model difference, and it is the entire reason BloodHound exists.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The sentence that named this gap appeared on April 26, 2015. John Lambert, then a Distinguished Engineer in the Microsoft Threat Intelligence Center, published a short essay to a personal GitHub repository [@lambert-2015-defenders-lists-attackers-graphs]. No peer review. No formal venue. Two declarative opening sentences that every defender attack-path product since has cited approvingly.&lt;/p&gt;

Defenders don&apos;t have a list of assets -- they have a graph. Assets are connected to each other by security relationships. As long as defenders use a list and attackers use a graph, attackers win. -- John Lambert, April 26, 2015.
&lt;p&gt;Lambert then enumerated five concrete classes of &quot;security dependencies&quot; that constitute edges in any real network: shared local-admin passwords; logon scripts on file servers; print-driver propagation from print servers; certificate authorities that mint smart-card logon certificates; and database administrators who run code as a privileged DB process. The essay closed with a defender prescription: &lt;em&gt;&quot;The first step is to visualize your network by turning your lists into graphs.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If the diagnosis is so obvious in retrospect, why was every mainstream AD audit tool from 2000 through 2015 list-shaped? The answer is that the academic literature had the right model but the wrong substrate, and the operator community had the right substrate but the wrong model. The two did not meet until April 2015.&lt;/p&gt;
&lt;h2&gt;3. Attack graphs before BloodHound, 1998 to 2015&lt;/h2&gt;
&lt;p&gt;The phrase &lt;em&gt;attack graph&lt;/em&gt; is older than Active Directory itself.&lt;/p&gt;
&lt;p&gt;In September 1998, two researchers at Sandia National Laboratories presented a paper titled &lt;em&gt;A Graph-Based System for Network-Vulnerability Analysis&lt;/em&gt; at the New Security Paradigms Workshop in Charlottesville, Virginia. The authors, Cynthia Phillips and Laura Painton Swiler, proposed that a network&apos;s worst-case attack was a &lt;em&gt;graph traversal&lt;/em&gt; problem [@phillips-swiler-1998-nspw]. Nodes encoded network states (the set of attacker privileges across the set of hosts). Edges encoded atomic attack steps (an exploit that, given prerequisite privileges, granted new ones). The shortest path from &quot;attacker outside the network&quot; to &quot;attacker has goal asset&quot; was the worst-case attack.&lt;/p&gt;
&lt;p&gt;Phillips and Swiler observed in a now-canonical sentence that &lt;em&gt;&quot;the security of a network is more than the sum of the security of its hosts.&quot;&lt;/em&gt; It is the conceptual ancestor of every attack-path tool that followed.&lt;/p&gt;
&lt;p&gt;Four years later, at IEEE Symposium on Security and Privacy 2002, Oleg Sheyner (then a CMU PhD student) along with Joshua Haines and Richard Lippmann (MIT Lincoln Lab), Somesh Jha (Wisconsin), and Jeannette Wing (CMU) made the construction automatic [@sheyner-et-al-2002-attack-graphs]. They encoded the network and attacker model as an NuSMV model-checker specification, treated the negation of the security goal as a temporal-logic property, and let the model checker generate every counterexample. The union of counterexamples was the attack graph.&lt;/p&gt;
&lt;p&gt;They also proved that the &lt;em&gt;minimum&lt;/em&gt; set of edges whose removal disconnects the attacker from the goal is NP-hard, but admits an O(log n) approximation -- the first asymptotic bound for the defender-hardening problem.&lt;/p&gt;
&lt;p&gt;Sheyner&apos;s companion 2004 thesis -- &lt;em&gt;Scenario Graphs and Attack Graphs&lt;/em&gt;, CMU-CS-04-122 -- remains the most readable book-length treatment of the academic-attack-graph generation [@sheyner-2004-thesis].&lt;/p&gt;
&lt;p&gt;By 2005 the academic line had a scale solution. Xinming Ou, Sudhakar Govindavajhala, and Andrew Appel at Princeton released &lt;strong&gt;MulVAL&lt;/strong&gt; at USENIX Security 2005 [@mulval-usenix-2005], encoding network state and attacker rules as Datalog facts and running them through XSB Prolog. From the paper&apos;s abstract: &lt;em&gt;&quot;Once the information is collected, the analysis can be performed in seconds for networks with thousands of machines.&quot;&lt;/em&gt; NetSPA at MIT Lincoln Lab (2006) and TVA / CAULDRON at George Mason (2003 to 2005) achieved similar scale through different mechanisms.&lt;/p&gt;
&lt;p&gt;In parallel, on the Windows operator side, Sean Metcalf was running adsecurity.org and documenting AD misconfiguration patterns one writeup at a time [@adsecurity-org]. Microsoft was rolling out its Enhanced Security Administrative Environment (&quot;Red Forest&quot;) tiered-administration model -- the predecessor that the Enterprise Access Model later replaced [@ms-enterprise-access-model] -- which was implicitly graph-aware (the entire ESAE prescription is a tier diagram) but never exposed the tier graph as a queryable structure. ESAE was a deployment blueprint, not a tool.&lt;/p&gt;

gantt
    title Three parallel tracks converging on the attack-path graph
    dateFormat YYYY
    axisFormat %Y&lt;pre&gt;&lt;code&gt;section Academic CVE line
Phillips and Swiler NSPW   :a1, 1998, 1999
Sheyner et al. IEEE SP     :a2, 2002, 2003
MulVAL USENIX              :a3, 2005, 2006
NetSPA TVA                 :a4, 2006, 2008

section Windows operator line
adsecurity.org writeups    :b1, 2014, 2016
PowerView                  :b2, 2014, 2016
Lambert essay              :milestone, b3, 2015-04-26, 1d
BloodHound DEF CON 24      :b4, 2016, 2018
AzureHound BloodHound 4.0  :b5, 2020, 2023
BloodHound CE 5.0          :b6, 2023, 2024
ADCS attack paths          :b7, 2024, 2025
Butterfly v6.3             :b8, 2024, 2026
OpenGraph v8.0             :b9, 2025, 2026

section Microsoft defender stack
ESAE Red Forest blueprint  :c1, 2015, 2018
Azure ATP LMPs preview     :c2, 2018, 2020
Defender CSPM attack paths :c3, 2022, 2024
MSEM GA                    :milestone, c4, 2024-11-19, 1d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The April 2015 essay sat between the two tracks. Lambert was inside Microsoft. He had read the academic literature. He worked next to the threat-intelligence teams who watched real intrusions unfold. The essay was the moment the two communities&apos; vocabularies met. The diagnosis was right. The cure was sixteen months away. It would not come from Microsoft, and it would not come from academia. It would come from a red-team consultancy and a free graph database, and it would debut on a Saturday in August at a hacker convention in Las Vegas.&lt;/p&gt;
&lt;h2&gt;4. Defenders evaluating Active Directory as a list of ACLs&lt;/h2&gt;
&lt;p&gt;If you were a Microsoft-aligned AD security engineer in 2013, your job was to read ACLs. One object at a time. Down a list.&lt;/p&gt;
&lt;p&gt;The mainstream defender toolchain of the era was, almost without exception, list-shaped. Microsoft shipped &lt;code&gt;dsacls.exe&lt;/code&gt; and PowerShell&apos;s &lt;code&gt;Get-Acl&lt;/code&gt;; both produced row-oriented output that an analyst read sequentially. The commercial AD-audit market -- NetWrix Auditor [@netwrix-auditor], ManageEngine ADAudit Plus [@manageengine-adaudit-plus], Quest ActiveRoles [@quest-activeroles], and others -- produced HTML reports with one section per directory object, one row per non-default Access Control Entry, and severity classifications based on a fixed checklist of &quot;dangerous rights&quot; (&lt;code&gt;GenericAll&lt;/code&gt;, &lt;code&gt;WriteDacl&lt;/code&gt;, &lt;code&gt;WriteOwner&lt;/code&gt;, and a handful of others).&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s own &lt;em&gt;Best Practices for Securing Active Directory&lt;/em&gt; document codified this approach. Its central recommendation was &lt;em&gt;per-object delegation review&lt;/em&gt;: walk the directory tree, evaluate each object&apos;s ACL against a hardening checklist, and remediate non-default ACEs that exceed the documented privilege model [@ms-best-practices-ad]. The document is excellent at what it sets out to do. What it does not do -- because the format does not permit it -- is compose multi-hop reachability.&lt;/p&gt;
&lt;p&gt;This was the failure mode Lambert described. A help-desk operator with &lt;code&gt;ForceChangePassword&lt;/code&gt; on a junior service account appears as one row in the audit. The junior service account&apos;s &lt;code&gt;MemberOf Server Operators&lt;/code&gt; membership appears in a different report section. The &lt;code&gt;Server Operators&lt;/code&gt; group&apos;s logon rights on the Domain Controller appear in the Domain Controller&apos;s own report. Three findings in three places, with no machinery to compose them. The data model cannot represent the question.&lt;/p&gt;

A reader trained on Phillips and Swiler, Sheyner et al., MulVAL, NetSPA, and TVA might object that *the field already had* graph-based attack-path analysis a decade before BloodHound. True -- in academia. The academic line solved the scale problem (MulVAL: *&quot;seconds for networks with thousands of machines&quot;*) but spoke the wrong vocabulary. Its atomic attack step was a CVE exploit -- a buffer overflow, a format-string bug, a daemon remote code execution. The dominant AD attack primitive is not a CVE. A user with `WriteDacl` on a group is not exploiting any vulnerability; they are using the system as designed. None of MulVAL, NetSPA, or TVA developed an AD-style privilege-graph input format, and the operator community never adopted them. The academic line was substrate-mismatched and delivered as research-PDF tarballs rather than as `git clone`-able tools.
&lt;p&gt;Two communities, both wrong in different ways. The academic one had the right algorithm with the wrong substrate. The operator one had the right substrate with no algorithm at all. The fix was to fuse them. That happened on August 6, 2016.&lt;/p&gt;
&lt;h2&gt;5. The breakthrough -- BloodHound and Six Degrees of Domain Admin&lt;/h2&gt;
&lt;p&gt;Saturday, August 6, 2016. 1:00 PM. DEF CON 24, Track 2, Paris and Bally&apos;s hotel-casinos in Las Vegas [@defcon-24-archive]. Three speakers from Veris Group&apos;s adaptive threat division step on stage: Andy Robbins, Rohan Vazarkar, Will Schroeder. The talk is titled &lt;em&gt;Six Degrees of Domain Admin: Using Graph Theory to Accelerate Red Team Operations&lt;/em&gt; [@defcon24-bloodhound-slides]. The Veris Group team would spin out as SpecterOps the following year, but the August 2016 attribution -- Robbins (&lt;code&gt;@_wald0&lt;/code&gt;), Vazarkar (&lt;code&gt;@CptJesus&lt;/code&gt;), and Schroeder (&lt;code&gt;@harmj0y&lt;/code&gt;) -- is the canonical one [@bloodhound-legacy-repo].&lt;/p&gt;
&lt;p&gt;The talk demonstrated three design decisions that in retrospect look obvious and at the time were not.&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;model Active Directory as a directed property graph&lt;/strong&gt;. Nodes are typed security principals (User, Computer, Group, Domain, OU, GPO). Edges are typed privilege relationships (MemberOf, AdminTo, HasSession, GenericAll, GenericWrite, WriteDacl, ForceChangePassword, and others). This was Lambert&apos;s framing made concrete: every ACE that grants a dangerous right becomes an edge from the trustee to the target.&lt;/p&gt;
&lt;p&gt;Second, &lt;strong&gt;reuse Neo4j as the engine&lt;/strong&gt;. Do not build a custom graph database; piggyback on Cypher&apos;s pattern-matching query language. The cost of building a path-finding engine from scratch was non-trivial; the cost of standing up Neo4j was a single Docker container.&lt;/p&gt;
&lt;p&gt;Third, &lt;strong&gt;ship a collector that emits typed JSON edges, not raw NTSecurityDescriptors&lt;/strong&gt;. The collector&apos;s value is the &lt;em&gt;interpretation&lt;/em&gt; of an ACE as a graph edge -- the mapping from a binary security descriptor to the typed edge that says &quot;trustee X has GenericWrite on object Y&quot; is the hard part, and a defender re-creating that mapping per query would lose. SharpHound (initially &lt;code&gt;SharpHound.ps1&lt;/code&gt;, then a C# binary) does the interpretation once at collection time and writes the edges to disk [@bloodhound-ce-repo].&lt;/p&gt;

SharpHound&apos;s enumeration calls are visibly descended from PowerView. The November 2020 SpecterOps blog announcing BloodHound 4.0 acknowledges the lineage explicitly, naming Schroeder&apos;s joint authorship of both projects and crediting PowerView as the data-collection precursor [@specterops-2020-bloodhound-4]. PowerView&apos;s August 2014 release was the substrate that made BloodHound&apos;s August 2016 synthesis possible. The chain is unbroken: enumeration in 2014, framing in April 2015, graph synthesis in August 2016.
&lt;p&gt;The five-day-of-whiteboard-work figure comes from the SpecterOps team&apos;s own internal benchmark from the original DEF CON 24 talk. The 200 ms query latency is the typical 2024 figure on a mid-size enterprise forest of roughly 10^5 nodes and 10^6 edges, retained as the company&apos;s marketing framing across subsequent blog posts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; BloodHound&apos;s breakthrough was neither an algorithm nor an architecture. It was the decision to &lt;em&gt;interpret&lt;/em&gt; Active Directory&apos;s access-control data as a typed graph and ship that interpretation as a tool the operator community could actually run.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Crucially, the choice to use Neo4j&apos;s free &lt;code&gt;shortestPath()&lt;/code&gt; function rather than building a custom path-finder was a &lt;em&gt;delivery&lt;/em&gt; decision as much as a &lt;em&gt;technical&lt;/em&gt; one. Neo4j already did breadth-first shortest paths. The team did not need to invent anything. The hard work was in the edge taxonomy and the collector, not in the graph database.&lt;/p&gt;
&lt;p&gt;The talk made a promise the rest of the article must now cash: that there is a real algorithm under the hood, that the algorithm has a name, and that the name is not Dijkstra.&lt;/p&gt;
&lt;h2&gt;6. The algorithmic core -- property graphs, Cypher, and shortest paths&lt;/h2&gt;
&lt;p&gt;If you have never written a Cypher query in your life, the next ninety seconds is the entirety of the syntax you need.&lt;/p&gt;

A graph in which both nodes and edges are typed and can carry key-value properties. A node might have type `User` and properties `name=&apos;BOB@CORP.LOCAL&apos;`, `enabled=true`, `pwdlastset=1719234234`. An edge might have type `GenericWrite` and properties `source=&apos;ACE&apos;`, `isacl=true`. This is the data model Neo4j implements and the model BloodHound uses.
&lt;p&gt;A Cypher pattern is a parenthesised node, a bracketed edge, and a parenthesised node, with arrows showing direction. &lt;code&gt;(u:User)-[:MemberOf]-&amp;gt;(g:Group)&lt;/code&gt; reads &quot;find a node &lt;code&gt;u&lt;/code&gt; of type User connected by a MemberOf edge to a node &lt;code&gt;g&lt;/code&gt; of type Group.&quot; A full query has four parts: &lt;code&gt;MATCH&lt;/code&gt; for the pattern, optional &lt;code&gt;WHERE&lt;/code&gt; for filters, &lt;code&gt;RETURN&lt;/code&gt; for the output. That&apos;s it.Cypher patterns visually mimic ASCII graph drawings: parentheses are nodes, square brackets are edges, and the arrow direction matches the edge direction. The syntax was deliberately designed to look like the diagram you would sketch on a whiteboard.&lt;/p&gt;

The pattern-matching query language originally created for the Neo4j graph database. Cypher&apos;s syntax is declarative: you describe the shape of the data you want, and the engine plans the traversal. Since April 2024 Cypher has been the basis of the ISO/IEC 39075:2024 GQL standard -- the first ISO-standardised graph query language [@iso-39075-2024-gql] [@opencypher-home].
&lt;p&gt;Here is the canonical BloodHound query, with annotations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cypher&quot;&gt;MATCH p = shortestPath(
  (u:User {name:&apos;BOB@CORP.LOCAL&apos;})       // start node: Bob
    -[*1..]-&amp;gt;                            // any number of typed edges
  (g:Group {name:&apos;DOMAIN ADMINS@CORP.LOCAL&apos;})  // end node: Domain Admins
)
RETURN p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;shortestPath()&lt;/code&gt; wrapper tells Neo4j to short-circuit at the first solution. The variable-length quantifier &lt;code&gt;[*1..]&lt;/code&gt; says &quot;one or more edges of any type.&quot; The &lt;code&gt;p =&lt;/code&gt; binds the entire matched path to the variable &lt;code&gt;p&lt;/code&gt;, which the &lt;code&gt;RETURN&lt;/code&gt; then emits as a sequence of node-edge-node triples that the BloodHound frontend renders as an SVG.&lt;/p&gt;
&lt;p&gt;What does &lt;code&gt;shortestPath()&lt;/code&gt; actually run? Here is where the misconception that BloodHound uses Dijkstra needs to die. The current Neo4j Cypher manual is explicit that &lt;code&gt;shortestPath()&lt;/code&gt; runs an unweighted &lt;strong&gt;bidirectional traversal&lt;/strong&gt; -- BFS in the classical sense -- between the source and target nodes [@neo4j-cypher-shortest-paths]. Not Dijkstra. Not A*. BFS.&lt;/p&gt;

A graph-traversal algorithm that explores all nodes at the current depth before proceeding to the next depth. Starting from the source, it visits all 1-hop neighbours, then all 2-hop neighbours, and so on. For unweighted graphs, BFS is guaranteed to find a shortest path (in number of edges) the first time it reaches the target. Worst-case time is O(V + E) where V is the node count and E is the edge count -- the trivial information-theoretic lower bound for any algorithm that must read the input.
&lt;p&gt;Cypher does support weighted shortest paths via the Neo4j Graph Data Science library, but the BloodHound CE distribution does not enable it. There is no natural cost metric on Active Directory privilege edges in 2026; every edge is treated as one hop.&lt;/p&gt;
&lt;p&gt;Why BFS rather than Dijkstra? Dijkstra is BFS&apos;s generalisation to weighted graphs. If your edges have natural costs -- road distances, link latencies, dollar prices -- Dijkstra (with a Fibonacci heap, $O(E + V\log V)$) gives you shortest paths under that cost metric. Active Directory privilege edges do not have a natural cost metric. &lt;code&gt;MemberOf&lt;/code&gt;, &lt;code&gt;GenericAll&lt;/code&gt;, and &lt;code&gt;CanRDP&lt;/code&gt; are all &quot;the attacker can take this step.&quot; Some are easier than others, but quantifying &lt;em&gt;how much&lt;/em&gt; easier is itself an unsolved problem (see Section 11). Treating every edge as one hop is the load-bearing simplification that makes the model tractable.&lt;/p&gt;

Of a binary relation R: the relation R+ that contains the pair (a, c) whenever there is a chain a R b R ... R c. For a graph, the transitive closure tells you, for every pair of nodes, whether one is reachable from the other. BloodHound&apos;s `shortestPath()` queries can be thought of as on-demand evaluation of the transitive closure restricted to one source-target pair.
&lt;p&gt;The per-query complexity is $O(V + E)$, the standard BFS bound from any algorithms textbook. On a mid-size enterprise forest -- roughly 10^5 nodes and 10^6 edges -- a single user-to-group shortest path returns in sub-second wall-clock time.&lt;/p&gt;
&lt;p&gt;The variable-length quantifier &lt;code&gt;[*1..N]&lt;/code&gt; for general path enumeration is a different matter. Cyclic graphs admit exponentially many paths in N, and Neo4j&apos;s documentation explicitly warns that quantified path patterns can return exponentially many results in the worst case [@neo4j-cypher-variable-length]. The &lt;code&gt;shortestPath()&lt;/code&gt; short-circuit avoids this by returning on the first hit; &lt;code&gt;allShortestPaths()&lt;/code&gt; enumerates only paths tied for shortest; unbounded enumeration is intractable on any non-trivial graph.&lt;/p&gt;
&lt;p&gt;A concrete demonstration is in order. The runnable snippet below is a 35-line implementation of unweighted BFS over a six-node toy graph. It returns the shortest path from a &lt;code&gt;helpdesk&lt;/code&gt; user to the &lt;code&gt;domain-admin&lt;/code&gt; group. This is, structurally, the same algorithm Neo4j&apos;s &lt;code&gt;shortestPath()&lt;/code&gt; runs. The numerical answer (&quot;path of length 4&quot;) is the same number BloodHound would report on the same graph.&lt;/p&gt;
&lt;p&gt;{`
// Edges modelled as a typed adjacency list.
const edges = {
  helpdesk:        [{ to: &apos;carol&apos;,          via: &apos;ForceChangePassword&apos; }],
  carol:           [{ to: &apos;serverops&apos;,      via: &apos;MemberOf&apos; }],
  serverops:       [{ to: &apos;dc01&apos;,           via: &apos;CanRDP&apos; }],
  dc01:            [{ to: &apos;krbtgt&apos;,         via: &apos;DCSync&apos; }],
  krbtgt:          [{ to: &apos;domain-admin&apos;,   via: &apos;GoldenTicket&apos; }],
  domain_admin:    []
};&lt;/p&gt;
&lt;p&gt;function shortestPath(start, goal) {
  const queue = [[start]];           // queue holds candidate paths
  const seen  = new Set([start]);    // each node enqueued at most once&lt;/p&gt;
&lt;p&gt;  while (queue.length) {
    const path = queue.shift();      // BFS = FIFO
    const node = path[path.length - 1];
    if (node === goal) return path;
    for (const e of (edges[node] || [])) {
      if (!seen.has(e.to)) {
        seen.add(e.to);
        queue.push([...path, e.to]);
      }
    }
  }
  return null;                       // unreachable
}&lt;/p&gt;
&lt;p&gt;const path = shortestPath(&apos;helpdesk&apos;, &apos;domain-admin&apos;);
console.log(&apos;Path:&apos;, path.join(&apos; -&amp;gt; &apos;));
console.log(&apos;Length:&apos;, path.length - 1, &apos;hops&apos;);
`}&lt;/p&gt;
&lt;p&gt;The algorithm fits on a single screen. The hard work of BloodHound is not in this loop. The hard work is in deciding &lt;em&gt;which edges to insert into the graph in the first place&lt;/em&gt;. That decision -- the edge taxonomy -- is what makes BloodHound a security tool rather than a graph-database demo.&lt;/p&gt;

sequenceDiagram
    participant Client as Cypher client
    participant Planner as Cypher planner
    participant Engine as BFS engine
    participant Graph as Property graph
    Client-&amp;gt;&amp;gt;Planner: MATCH shortestPath(u to g)
    Planner-&amp;gt;&amp;gt;Engine: plan(start=u, end=g, bidirectional=true)
    Engine-&amp;gt;&amp;gt;Graph: expand 1-hop frontier from u
    Engine-&amp;gt;&amp;gt;Graph: expand 1-hop frontier from g
    Graph--&amp;gt;&amp;gt;Engine: neighbours of u, neighbours of g
    Engine-&amp;gt;&amp;gt;Graph: expand 2-hop frontier (both sides)
    Engine-&amp;gt;&amp;gt;Graph: expand 3-hop frontier (both sides)
    Graph--&amp;gt;&amp;gt;Engine: frontiers intersect at node m
    Engine--&amp;gt;&amp;gt;Planner: reconstruct path u to m to g
    Planner--&amp;gt;&amp;gt;Client: return path
&lt;h2&gt;7. The edge taxonomy -- what Active Directory actually looks like as a graph&lt;/h2&gt;
&lt;p&gt;The BloodHound graph is not &lt;em&gt;every privilege relationship in Active Directory.&lt;/em&gt; It is the set of relationships that the SpecterOps team has decided -- by iterative discovery, often in response to specific community-reported abuse primitives -- to model. The taxonomy has grown roughly monotonically since 2016; the rate has accelerated since the BloodHound CE 5.0 reboot in August 2023 [@specterops-2023-bloodhound-ce].&lt;/p&gt;
&lt;p&gt;Eight families dominate the 2026 graph. They map, family by family, onto the substrates of a modern hybrid enterprise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Group membership.&lt;/strong&gt; &lt;code&gt;MemberOf&lt;/code&gt; is the simplest edge. Cypher&apos;s variable-length quantifier (&lt;code&gt;[:MemberOf*1..]&lt;/code&gt;) walks transitive memberships in one expression, which is why nested-group reachability is a one-liner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. ACL write-equivalents.&lt;/strong&gt; &lt;code&gt;GenericAll&lt;/code&gt;, &lt;code&gt;GenericWrite&lt;/code&gt;, &lt;code&gt;WriteDacl&lt;/code&gt;, &lt;code&gt;WriteOwner&lt;/code&gt;, &lt;code&gt;Owns&lt;/code&gt;, &lt;code&gt;AllExtendedRights&lt;/code&gt;, &lt;code&gt;ForceChangePassword&lt;/code&gt;, &lt;code&gt;AddSelf&lt;/code&gt;, &lt;code&gt;AddMember&lt;/code&gt;, and &lt;code&gt;AddKeyCredentialLink&lt;/code&gt; (the shadow-credentials primitive). Each names a specific dangerous-right pattern on a directory object&apos;s security descriptor. SharpHound&apos;s interpreter scans the &lt;code&gt;nTSecurityDescriptor&lt;/code&gt; attribute and emits one typed edge per matching ACE.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Sessions.&lt;/strong&gt; &lt;code&gt;HasSession&lt;/code&gt; is the dynamic edge that goes stale fastest. SharpHound enumerates active sessions via &lt;code&gt;NetSessionEnum&lt;/code&gt; and &lt;code&gt;SAMR&lt;/code&gt;; the resulting edges describe &quot;user U is currently logged into computer C.&quot; The graph is whatever the most recent collection captured.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Remote execution rights.&lt;/strong&gt; &lt;code&gt;AdminTo&lt;/code&gt;, &lt;code&gt;CanRDP&lt;/code&gt;, &lt;code&gt;ExecuteDCOM&lt;/code&gt;, &lt;code&gt;CanPSRemote&lt;/code&gt;, &lt;code&gt;SQLAdmin&lt;/code&gt;. Each describes a code-execution primitive granted to a principal on a computer object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. &lt;a href=&quot;https://paragmali.com/blog/kerberos-in-windows-the-other-half-of-ntlmless/&quot; rel=&quot;noopener&quot;&gt;Kerberos delegation&lt;/a&gt;.&lt;/strong&gt; &lt;code&gt;AllowedToDelegate&lt;/code&gt; (constrained delegation), &lt;code&gt;AllowedToAct&lt;/code&gt; (resource-based constrained delegation, via &lt;code&gt;msDS-AllowedToActOnBehalfOfOtherIdentity&lt;/code&gt;), unconstrained delegation surfaced as node properties on Computer objects, and -- from BloodHound CE v6.3 in December 2024 [@bloodhound-v6-3-release] -- a &lt;code&gt;CoerceToTGT&lt;/code&gt; edge that replaces the older &lt;code&gt;UnconstrainedDelegation&lt;/code&gt; finding for BHE customers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. &lt;a href=&quot;https://paragmali.com/blog/certified-pre-owned-ad-cs-and-active-directorys-second-trust/&quot; rel=&quot;noopener&quot;&gt;ADCS&lt;/a&gt; edges (early-access January 2024).&lt;/strong&gt; &lt;code&gt;ADCSESC1&lt;/code&gt; through &lt;code&gt;ADCSESC10&lt;/code&gt;, plus the &lt;strong&gt;&lt;code&gt;CoerceAndRelayNTLMToADCS&lt;/code&gt; edge for ESC8&lt;/strong&gt; [@specterops-2024-adcs-bloodhound]. These edges land in BloodHound roughly thirty months after Will Schroeder and Lee Christensen first published the ESC1 to ESC8 catalog in &lt;em&gt;Certified Pre-Owned: Abusing Active Directory Certificate Services&lt;/em&gt; [@specterops-2021-certified-preowned]. Each ADCS edge in BloodHound is the most complex in the taxonomy because each is composed from multiple raw facts (see the traversable / non-traversable discussion below).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. Azure / Entra ID edges&lt;/strong&gt; (via AzureHound, November 20, 2020) [@specterops-2020-bloodhound-4]. &lt;code&gt;AZGlobalAdmin&lt;/code&gt;, &lt;code&gt;AZRoleAssignment&lt;/code&gt;, &lt;code&gt;AZContains&lt;/code&gt;, &lt;code&gt;AZOwns&lt;/code&gt;, &lt;code&gt;AZUserAccessAdministrator&lt;/code&gt;, &lt;code&gt;AZAddSecret&lt;/code&gt;, &lt;code&gt;AZMGAddOwner&lt;/code&gt;, plus AzureRM-side resource roles. Microsoft Entra &lt;a href=&quot;https://paragmali.com/blog/privileged-identity-management-how-a-two-state-role-assignme/&quot; rel=&quot;noopener&quot;&gt;Privileged Identity Management (PIM)&lt;/a&gt; role coverage was added in BloodHound v8.0 in July 2025 [@specterops-2025-opengraph].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8. OpenGraph custom edges (v8.0, July 29, 2025).&lt;/strong&gt; User-defined edges for arbitrary substrates: GitHub, Snowflake, Microsoft SQL Server, ServiceNow, Tailscale, Duo. The schema is intentionally generic so that a community contributor can ship edges for any system whose privilege model can be drawn as a graph [@bloodhound-opengraph-library].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Family&lt;/th&gt;
&lt;th&gt;Representative edges&lt;/th&gt;
&lt;th&gt;Underlying AD mechanism&lt;/th&gt;
&lt;th&gt;What it gives the attacker&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Group membership&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MemberOf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;member&lt;/code&gt; attribute on group object&lt;/td&gt;
&lt;td&gt;Inherits all permissions held by the group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACL write-equivalents&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GenericAll&lt;/code&gt;, &lt;code&gt;GenericWrite&lt;/code&gt;, &lt;code&gt;WriteDacl&lt;/code&gt;, &lt;code&gt;WriteOwner&lt;/code&gt;, &lt;code&gt;ForceChangePassword&lt;/code&gt;, &lt;code&gt;AddKeyCredentialLink&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specific dangerous-right ACE patterns in &lt;code&gt;nTSecurityDescriptor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Take control of the target principal (reset password, modify object, plant shadow credentials)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sessions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HasSession&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NetSessionEnum&lt;/code&gt; and &lt;code&gt;SAMR&lt;/code&gt; enumeration on member computers&lt;/td&gt;
&lt;td&gt;Pivot via credential theft from the logged-in user&apos;s memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote execution&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AdminTo&lt;/code&gt;, &lt;code&gt;CanRDP&lt;/code&gt;, &lt;code&gt;ExecuteDCOM&lt;/code&gt;, &lt;code&gt;CanPSRemote&lt;/code&gt;, &lt;code&gt;SQLAdmin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local-admin membership, RDP / DCOM / WinRM / SQL group rights&lt;/td&gt;
&lt;td&gt;Run arbitrary code as the target principal on the target host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kerberos delegation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AllowedToDelegate&lt;/code&gt;, &lt;code&gt;AllowedToAct&lt;/code&gt;, &lt;code&gt;CoerceToTGT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Constrained and resource-based delegation attributes&lt;/td&gt;
&lt;td&gt;Forge service tickets and impersonate other accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ADCS composite&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ADCSESC1&lt;/code&gt; through &lt;code&gt;ADCSESC10&lt;/code&gt;, &lt;code&gt;CoerceAndRelayNTLMToADCS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Certificate template misconfigurations plus CA trust plus enrollment ACEs&lt;/td&gt;
&lt;td&gt;Obtain a certificate usable for authentication as a privileged account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure / Entra&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AZGlobalAdmin&lt;/code&gt;, &lt;code&gt;AZRoleAssignment&lt;/code&gt;, &lt;code&gt;AZAddSecret&lt;/code&gt;, &lt;code&gt;AZOwns&lt;/code&gt;, &lt;code&gt;AZMGAddOwner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Entra role assignments, AzureRM RBAC&lt;/td&gt;
&lt;td&gt;Cross the on-prem to cloud boundary; pivot via tenant or subscription privileges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenGraph&lt;/td&gt;
&lt;td&gt;User-defined&lt;/td&gt;
&lt;td&gt;Any substrate the contributor models&lt;/td&gt;
&lt;td&gt;Anything the contributed schema encodes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The ADCS family deserves a closer look because it introduced an important new modelling vocabulary.&lt;/p&gt;

A *traversable* edge is one the shortest-path query can step through directly: `MemberOf`, `ForceChangePassword`, `CanRDP`. A *non-traversable* edge is a precondition relationship that is only exploitable when several others appear together. A certificate template&apos;s `Enroll` ACE is non-traversable on its own; combined with eight other facts about the template, the issuing CA, and the domain&apos;s trust posture, it composes into `ADCSESC1`. The post-processor scans for the full pattern and synthesises a single traversable edge that the BFS can then treat as one hop [@specterops-2024-adcs-bloodhound].
&lt;p&gt;For ESC1 the pattern has nine numbered prerequisites: six template and CA requirements, two enterprise-CA trust facts, and one implicit constraint. None of the nine raw facts is exploitable in isolation. All nine together are. The post-processor&apos;s job is to walk the candidate sub-graphs, check every requirement, and write the composed &lt;code&gt;ADCSESC1&lt;/code&gt; edge when the pattern holds.&lt;/p&gt;
&lt;p&gt;This is a non-trivial graph-modelling contribution because it gives the field a vocabulary for &quot;an edge that is real only as a join over several facts.&quot; It also generalises beyond ADCS: any future attack primitive composed from a fixed pattern of raw facts can be modelled the same way.&lt;/p&gt;
&lt;p&gt;ESC8 -- the &lt;a href=&quot;https://paragmali.com/blog/ntlmless-the-death-of-ntlm-in-windows/&quot; rel=&quot;noopener&quot;&gt;NTLM relay&lt;/a&gt; primitive against an HTTP-enrollment certificate authority -- is the most delicate case, and the one most commonly mis-modelled in early secondary writeups.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;CoerceAndRelayNTLMToADCS&lt;/code&gt; edge is a &lt;strong&gt;Group(&lt;code&gt;Authenticated Users&lt;/code&gt;) to Computer(coerced target)&lt;/strong&gt; edge, not a Computer to Computer edge as some early secondary writeups described. The relay-target CA and the certificate template are carried as &lt;em&gt;edge metadata&lt;/em&gt;, not as additional graph nodes. The canonical edge documentation is explicit: &lt;em&gt;Source: Authenticated Users [Group] / Destination: Computer / Traversable: Yes&lt;/em&gt; [@bloodhound-coerce-relay-edge].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The schema correction matters because the source-principal choice affects every shortest-path query that crosses ESC8. If the edge is mis-modelled as Computer to Computer, queries that begin from a low-privilege user account miss the path entirely. The Group-to-Computer schema correctly captures that &lt;em&gt;any authenticated principal&lt;/em&gt; can coerce.&lt;/p&gt;
&lt;p&gt;What the graph does not yet model is also worth naming. The &lt;code&gt;SpecterOps/TierZeroTable&lt;/code&gt; README states it verbatim: &lt;em&gt;&quot;DISCLAIMER: The table does not include all Tier Zero assets yet.&quot;&lt;/em&gt; [@specterops-tier-zero-table] Several edge classes remain partially or fully out of scope; the full enumeration appears in Section 11 (open problems). Coverage expansion is iterative and community-fed; OpenGraph (Section 9) is the structural answer to &quot;where does the graph end?&quot;&lt;/p&gt;

flowchart TD
    subgraph OnPrem[&quot;On-prem Active Directory&quot;]
        Members[&quot;MemberOf, Owns&quot;]
        ACL[&quot;ACL write-equivalents&lt;br /&gt;GenericAll, WriteDacl, ForceChangePassword,&lt;br /&gt;AddKeyCredentialLink&quot;]
        Sessions[&quot;HasSession&quot;]
        Exec[&quot;AdminTo, CanRDP, ExecuteDCOM,&lt;br /&gt;CanPSRemote, SQLAdmin&quot;]
        Krb[&quot;Kerberos delegation&lt;br /&gt;AllowedToDelegate, AllowedToAct, CoerceToTGT&quot;]
    end
    subgraph Entra[&quot;Entra ID and AzureRM&quot;]
        EntraRoles[&quot;AZGlobalAdmin, AZRoleAssignment,&lt;br /&gt;AZAddSecret, AZUserAccessAdministrator,&lt;br /&gt;PIM roles&quot;]
        AzureRM[&quot;AZContains, AZOwns,&lt;br /&gt;AzureRM resource roles&quot;]
    end
    subgraph ADCS[&quot;ADCS composite edges&quot;]
        ESC[&quot;ADCSESC1 to ADCSESC10,&lt;br /&gt;CoerceAndRelayNTLMToADCS&quot;]
    end
    subgraph Open[&quot;OpenGraph user-defined&quot;]
        Custom[&quot;GitHub, Snowflake, SQL Server,&lt;br /&gt;ServiceNow, Tailscale, Duo, custom&quot;]
    end
    OnPrem --&amp;gt; Cypher((Cypher query layer))
    Entra --&amp;gt; Cypher
    ADCS --&amp;gt; Cypher
    Open --&amp;gt; Cypher
&lt;p&gt;If this is what one community modelled, the natural question is: what did Microsoft model? And when?&lt;/p&gt;
&lt;h2&gt;8. The defender adoption -- Microsoft catches up, 2018 to 2024&lt;/h2&gt;
&lt;p&gt;The defender vendor whose product BloodHound was mapping is also a defender vendor with a graph product of its own. Three of them, in fact, shipped in three different years for three different substrates. They are easy to confuse; press releases sometimes do.&lt;/p&gt;
&lt;p&gt;The first arrived on November 27, 2018, when Tali Ash (then a Program Manager on the Azure Advanced Threat Protection team) announced a preview feature called &lt;em&gt;Lateral Movement Paths&lt;/em&gt; (LMPs) in a Microsoft tech-community post [@ms-azure-atp-lmp-2018]. LMPs were a graph-shaped visualisation, but a constrained one: restricted to &quot;sensitive accounts&quot; (a configurable set defaulting to Domain Admins and similar) plus non-sensitive accounts that had shared a session on the same host as a sensitive account.&lt;/p&gt;
&lt;p&gt;The portal rendered one- and two-hop credential-theft pivots as a static SVG. There was no Cypher equivalent, no LMP-export API, and no way to write a custom query. Azure ATP was rebranded &lt;strong&gt;Microsoft Defender for Identity&lt;/strong&gt; in 2020, and the LMP feature came along under the new name [@ms-mdi-lmp-docs].&lt;/p&gt;
&lt;p&gt;Several secondary sources date Lateral Movement Paths to &quot;June 2019,&quot; which corresponds to the general-availability and rebrand window rather than the original preview announcement. The primary Microsoft tech-community post is November 27, 2018; treat the year-not-month for any third-party claim and prefer the November 2018 preview date as the canonical first ship.&lt;/p&gt;
&lt;p&gt;The second arrived in October 2022, when Microsoft Defender for Cloud&apos;s Defender CSPM plan added a &lt;em&gt;cloud security graph&lt;/em&gt; with attack-path analysis (public preview at Ignite October 2022; generally available March 28, 2023) [@ms-defender-cloud-attack-path]. This product is a &lt;em&gt;cloud&lt;/em&gt; attack-path graph: Azure plus AWS plus GCP asset inventory, with inferred edges for permissions, network reachability, vulnerability presence, and internet exposure. It is explicitly &lt;em&gt;not&lt;/em&gt; the Active Directory identity graph; it covers the multi-cloud workload surface.&lt;/p&gt;
&lt;p&gt;A common mistake conflates this 2022-2023 product (Defender for &lt;em&gt;Cloud&lt;/em&gt;) with Microsoft Defender for &lt;em&gt;Identity&lt;/em&gt; (the LMP product from November 2018). The substrate, the team, and the year are all different. Worth flagging here because secondary writeups repeat the confusion often.&lt;/p&gt;

The Microsoft Defender XDR product family is a naming minefield. *Defender for Identity* (MDI) is the on-prem AD identity-threat product; LMPs are its graph view. *Defender for Cloud* (MDC) is the multi-cloud workload-protection product; its CSPM plan ships cloud-security-graph attack-path analysis. *Defender for Endpoint* (MDE) is the EDR product; it does not ship its own attack-path graph but feeds telemetry into MSEM. *Microsoft Security Exposure Management* (MSEM, GA November 19, 2024) is the unified exposure-graph layer that subsumes the others. Four products, four substrates, four ship dates. The naming overlap is unfortunate but the distinctions are real.
&lt;p&gt;The third arrived on November 19, 2024, at the Ignite 2024 keynote in Chicago. Satya Nadella, in the opening keynote, announced that &lt;strong&gt;Microsoft Security Exposure Management&lt;/strong&gt; (MSEM) had reached general availability [@ms-ignite-2024-msem]. MSEM is the product whose attack-path model is &lt;em&gt;structurally equivalent&lt;/em&gt; to BloodHound&apos;s: cross-substrate (identity plus endpoint plus multi-cloud), first-class attack-path objects with choke-point and blast-radius dashboards, and continuous data feed via the Defender XDR signal plane.&lt;/p&gt;

Microsoft&apos;s unified exposure-graph product, generally available November 19, 2024, at Ignite 2024 in Chicago. MSEM ingests telemetry from Defender for Endpoint, Defender for Identity, Defender for Cloud, Entra ID, and the Defender XDR plane into a single graph. Attack paths are first-class objects with three dashboard views: an attack-path list, choke-point analysis (small sets of nodes whose compromise enables disproportionately many downstream paths), and blast-radius (downstream reach of a selected node) [@ms-msem-attack-paths].
&lt;p&gt;The MSEM docs page introduces the model verbatim: &lt;em&gt;&quot;Attack paths in Microsoft Security Exposure Management help you to proactively identify and visualize potential routes that attackers can exploit using vulnerabilities, gaps, and misconfigurations across endpoints, cloud environments, and hybrid infrastructures.&quot;&lt;/em&gt; And, on choke points: &lt;em&gt;&quot;By focusing on these choke points, you can reduce risk by addressing high-impact assets.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The query interface is the Defender XDR portal plus KQL (Kusto Query Language), not Cypher. The graph engine is proprietary; Microsoft does not publish per-query latency numbers or the underlying algorithms. But the model -- nodes, typed edges, attack paths as the unit of analysis, choke-point and blast-radius views -- is the model BloodHound shipped at DEF CON 24 in August 2016.&lt;/p&gt;
&lt;p&gt;The arc takes eight years. From the August 6, 2016 BloodHound talk to the November 19, 2024 MSEM general-availability announcement is eight years and three months. The defender vendor whose product the original BloodHound was mapping ships a defender product whose attack-path model is structurally equivalent to the one a red-team consultancy shipped in a conference talk eight years earlier.&lt;/p&gt;
&lt;p&gt;Microsoft adopted the model. The community kept extending it. By 2026 the frontier is no longer &lt;em&gt;&quot;does the graph exist?&quot;&lt;/em&gt; It is &lt;em&gt;&quot;how do we make the graph weighted, complete, and substrate-independent?&quot;&lt;/em&gt; That is the state of the art.&lt;/p&gt;
&lt;h2&gt;9. State of the art -- Tier Zero, ADCS edges, and OpenGraph, 2023 to 2026&lt;/h2&gt;
&lt;p&gt;By the time MSEM shipped, SpecterOps had already moved past &lt;em&gt;&quot;is there a graph?&quot;&lt;/em&gt; and was asking three sharper questions. Where does the graph end? How do we model attack primitives that compose from raw facts? And does the AD-specific schema even matter?&lt;/p&gt;
&lt;p&gt;The first question is what &lt;em&gt;&quot;Tier Zero&quot;&lt;/em&gt; means. On June 22, 2023, Jonas Bülow Knudsen, Elad Shamir, and Justin Kohler at SpecterOps published &lt;em&gt;What is Tier Zero -- Part 1&lt;/em&gt;, which reframed Microsoft&apos;s tiered-administration concept -- introduced in the 2012-2014 Securing Privileged Access guidance and renamed the Enterprise Access Model with the &quot;Control Plane&quot; vocabulary in December 2020 [@ms-enterprise-access-model] -- as a property &lt;em&gt;of the graph&lt;/em&gt; [@specterops-2023-tier-zero].&lt;/p&gt;
&lt;p&gt;A Tier Zero asset (see Definition below) reframes Microsoft&apos;s tiered concept from &lt;em&gt;the set of things in the high-privilege tier&lt;/em&gt; to &lt;em&gt;the set of things from which the high-privilege tier is reachable&lt;/em&gt;. Microsoft&apos;s own Tier 0 definition -- &lt;em&gt;&quot;Direct Control of enterprise identities... and all the assets in it&quot;&lt;/em&gt; -- becomes a graph property. The two formulations are equivalent if and only if the graph is complete. If the graph is incomplete (which it is), the Tier Zero set computed from the graph is the floor, not the ceiling.&lt;/p&gt;

Any node in the attack-path graph whose compromise lets an attacker reach an administrative privilege in the forest. The companion `SpecterOps/TierZeroTable` GitHub project is the community-maintained inventory; the README discloses that the table is the floor, not the ceiling [@specterops-tier-zero-table].
&lt;p&gt;The Tier Zero definition is the answer to &lt;em&gt;&quot;shortest path to what?&quot;&lt;/em&gt; -- the target side of every BloodHound shortest-path query. Without a defined Tier Zero set, the question has no endpoint.&lt;/p&gt;
&lt;p&gt;The second question is how to model attack primitives that compose. The January 24, 2024 SpecterOps blog by Knudsen formalised this with the traversable / non-traversable edge distinction discussed in Section 7. The mechanism generalises: any attack primitive whose exploitability is a conjunction of raw facts can be encoded as a composed edge that the post-processor synthesises when the pattern is present.&lt;/p&gt;
&lt;p&gt;ESC1, walked through in Section 7, is the canonical example: nine numbered prerequisites that the post-processor checks before writing the composed &lt;code&gt;ADCSESC1&lt;/code&gt; edge [@specterops-2024-adcs-bloodhound]. Subsequent posts in the series extended the same machinery to ESC3, ESC4, ESC6, ESC7, ESC8 (the &lt;code&gt;CoerceAndRelayNTLMToADCS&lt;/code&gt; edge), ESC9, ESC10, and -- on February 14, 2024 -- ESC13 [@specterops-2024-esc13].&lt;/p&gt;
&lt;p&gt;In December 2024 BloodHound v6.3 introduced an early-access &quot;improved analysis algorithm&quot; internally referred to as &lt;strong&gt;Butterfly&lt;/strong&gt; [@bloodhound-v6-3-release]. Butterfly is the first production attempt at &lt;em&gt;bi-directional impact&lt;/em&gt; analysis. Pre-v6.3 BloodHound Enterprise quantified risk as &quot;&lt;em&gt;who can reach this node?&lt;/em&gt;&quot; (incoming attack-path count). v6.3 also quantifies &quot;&lt;em&gt;who can this node reach if compromised?&lt;/em&gt;&quot; (outgoing blast radius).&lt;/p&gt;
&lt;p&gt;The release notes describe the outcome but not the algorithm: &lt;em&gt;&quot;Improve risk scoring fidelity for all finding types... Measure risk at each individual finding... Support the inclusion of hybrid paths in risk scoring (Azure assets will now contribute to measured risk in AD and vice versa).&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The same release also announced that BloodHound Enterprise had begun migrating off Neo4j onto PostgreSQL as the &lt;em&gt;graph&lt;/em&gt; database, with the release notes reporting &lt;em&gt;&quot;&amp;gt;50% improvement in the time it takes to perform post-processing during the Analysis process.&quot;&lt;/em&gt; Cypher continues to be the query language; the engine underneath changed.&lt;/p&gt;
&lt;p&gt;The third question -- whether the AD-specific schema matters -- got its answer on July 29, 2025, when SpecterOps released BloodHound v8.0 with &lt;strong&gt;OpenGraph&lt;/strong&gt; [@specterops-2025-opengraph]. OpenGraph decouples the graph engine from the AD-specific schema. Users (and SpecterOps partners) define their own node and edge kinds and ingest attack-path data from arbitrary substrates. The initial release included GitHub organisations, Snowflake role hierarchies, Microsoft SQL Server logins, ServiceNow groups, and Tailscale ACLs. Subsequent community contributions extended the library.&lt;/p&gt;

BloodHound OpenGraph is a foundational shift toward... identity risk management across the entire enterprise. -- Justin Kohler, SpecterOps Chief Product Officer, July 29, 2025.
&lt;p&gt;OpenGraph is the closing observation of an arc that began with Phillips and Swiler in 1998: &lt;em&gt;the model is the abstraction; the substrate is whatever your enterprise runs.&lt;/em&gt; The same &lt;code&gt;shortestPath()&lt;/code&gt; that finds Active Directory attack paths now finds attack paths over a GitHub organisation, a Snowflake role hierarchy, or a Microsoft SQL Server login graph, with no engine change. The 2026 BloodHound release (v9.1.0, May 6, 2026, per the public release-notes index [@bloodhound-release-notes-index]) extends OpenGraph and adds incremental edge updates -- the first step toward a streaming graph rather than a snapshot.&lt;/p&gt;
&lt;h2&gt;10. Competing approaches -- BloodHound versus Microsoft versus the alternatives&lt;/h2&gt;
&lt;p&gt;In 2026 no single product covers every substrate. The field is plural.&lt;/p&gt;
&lt;p&gt;A practitioner choosing among attack-path tools answers four questions, in order. What substrate do you need to cover? Self-host or SaaS? Snapshot or continuous? Open query language or vendor portal? The table below assembles the answers on the dimensions a 2026 practitioner actually uses.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Substrate&lt;/th&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Query language&lt;/th&gt;
&lt;th&gt;Deployment&lt;/th&gt;
&lt;th&gt;Licensing&lt;/th&gt;
&lt;th&gt;Edge weighting&lt;/th&gt;
&lt;th&gt;ADCS coverage&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;BloodHound CE 9.x&lt;/td&gt;
&lt;td&gt;AD + Entra + AzureRM + OpenGraph&lt;/td&gt;
&lt;td&gt;Neo4j + Postgres app DB&lt;/td&gt;
&lt;td&gt;Cypher&lt;/td&gt;
&lt;td&gt;Self-hosted Docker Compose&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;No (unweighted BFS)&lt;/td&gt;
&lt;td&gt;Yes -- ESC1 through ESC10 + ESC8 composite&lt;/td&gt;
&lt;td&gt;Authorised offensive testing + DIY blue team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BloodHound Enterprise&lt;/td&gt;
&lt;td&gt;Same as CE&lt;/td&gt;
&lt;td&gt;PostgreSQL-as-graph (in-progress migration off Neo4j)&lt;/td&gt;
&lt;td&gt;Cypher&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;Bi-directional (Butterfly v6.3+); weighting function not public&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Continuous AD/Entra attack-surface management at enterprise scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adalanche&lt;/td&gt;
&lt;td&gt;AD (on-prem; LDIF or live LDAP)&lt;/td&gt;
&lt;td&gt;In-memory Go&lt;/td&gt;
&lt;td&gt;AQL (GQL-like)&lt;/td&gt;
&lt;td&gt;Single Go binary&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (per README)&lt;/td&gt;
&lt;td&gt;Offline / air-gapped analysis from LDIF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft Security Exposure Management&lt;/td&gt;
&lt;td&gt;Defender XDR signal: identity + endpoint + multi-cloud + Entra&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;td&gt;KQL + portal&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;Microsoft licensing&lt;/td&gt;
&lt;td&gt;Implicit (filter against exploitability oracle)&lt;/td&gt;
&lt;td&gt;Indirect via MDI signals&lt;/td&gt;
&lt;td&gt;Hybrid Microsoft-substrate unified exposure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MDI Lateral Movement Paths&lt;/td&gt;
&lt;td&gt;On-prem AD (sensitive-account paths only)&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;td&gt;None -- portal only&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;Microsoft licensing&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Implicit via separate MDI alerts&lt;/td&gt;
&lt;td&gt;Default-on credential-hopping detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defender for Cloud CSPM attack-path analysis&lt;/td&gt;
&lt;td&gt;Multi-cloud (Azure + AWS + GCP)&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;td&gt;Cloud Security Explorer + KQL&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;Microsoft licensing&lt;/td&gt;
&lt;td&gt;Implicit&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Multi-cloud workload protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PingCastle / Semperis DSP / ADAudit Plus&lt;/td&gt;
&lt;td&gt;On-prem AD (+ limited Entra)&lt;/td&gt;
&lt;td&gt;None -- list-of-findings&lt;/td&gt;
&lt;td&gt;None -- HTML / portal&lt;/td&gt;
&lt;td&gt;Self-hosted or SaaS&lt;/td&gt;
&lt;td&gt;Commercial / mixed&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Single-finding hygiene only&lt;/td&gt;
&lt;td&gt;Compliance auditing and change tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;A few rows deserve commentary.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; BloodHound CE ships under &lt;strong&gt;Apache-2.0&lt;/strong&gt; per the current repository [@bloodhound-ce-repo]. The GPL-3.0 license you may see in older treatments applies only to the deprecated BloodHound Legacy v4 repository [@bloodhound-legacy-repo], which was last updated in 2023 and is no longer maintained. The licensing difference is material: GPL-3.0 is copyleft, Apache-2.0 is permissive. Downstream use cases that need permissive licensing should rely on the current CE.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Older blog posts and conference talks frequently call BloodHound CE GPL-3.0. The CE-Legacy LICENSE block does carry the GPL-3.0 copyright header, which is the source of the confusion. The &lt;em&gt;current&lt;/em&gt; CE codebase at github.com/SpecterOps/BloodHound is Apache-2.0; the GPL-3.0 LICENSE applies only to the deprecated Legacy v4 repository.&lt;/p&gt;
&lt;p&gt;Adalanche, by Lars Karlslund, is the load-bearing counter-example to the claim that &quot;the graph model requires Neo4j&quot; [@adalanche-repo]. Adalanche reads AD data from an LDIF dump or live LDAP, builds the graph entirely in process memory, and exposes a web GUI plus an Adalanche Query Language (AQL) -- described in the README as &lt;em&gt;&quot;a GQL-like language that allows for complex queries.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The README&apos;s headline claim is verbatim: &lt;em&gt;&quot;Adalanche gives instant results, showing you what permissions users and groups have in an Active Directory.&quot;&lt;/em&gt; The trade is no continuous monitoring, no multi-user web app, and a smaller community in exchange for zero deployment friction. The model is identical; the engine is replaceable.&lt;/p&gt;
&lt;p&gt;MSEM is the closest Microsoft analogue to BloodHound (see Section 8 for substrate and query interface). Reasonable defenders run &lt;em&gt;both&lt;/em&gt; MSEM and BloodHound (CE or Enterprise) on the same forest. The tools are complementary rather than substitutionary: MSEM brings the EDR plus workload-protection telemetry that BloodHound does not natively ingest, while BloodHound brings the precise AD edge semantics that SpecterOps&apos;s research community has validated. Running both is not double-counting.&lt;/p&gt;
&lt;p&gt;The hygiene scanners -- PingCastle [@pingcastle], Semperis DSP [@semperis-dsp], ManageEngine ADAudit Plus [@manageengine-adaudit-plus] -- are the surviving descendants of the per-object ACL-inspection generation, with risk-scoring layered on top. They are valuable for compliance auditing and change tracking. They do not expose a queryable attack-path graph. The compliance auditor and the attack-path analyst are different personas with different tools.&lt;/p&gt;
&lt;p&gt;If the field is plural and every tool has a gap, what is the shape of the problem that no tool yet solves? The next section is the honest answer.&lt;/p&gt;
&lt;h2&gt;11. Theoretical limits and open problems&lt;/h2&gt;
&lt;p&gt;Some of the gaps in attack-path analysis are engineering gaps. Others are not.&lt;/p&gt;
&lt;p&gt;The single most consequential open problem is &lt;strong&gt;edge weighting&lt;/strong&gt;. BloodHound&apos;s BFS treats every edge as one hop. In reality, &lt;code&gt;MemberOf&lt;/code&gt; is effectively free; &lt;code&gt;ForceChangePassword&lt;/code&gt; requires the attacker to log in as the changed principal afterwards; &lt;code&gt;AddKeyCredentialLink&lt;/code&gt; requires shadow-credential infrastructure; &lt;code&gt;CoerceAndRelayNTLMToADCS&lt;/code&gt; requires an active SMB-coercion primitive, NTLM relay tooling, and an ESC8-vulnerable certificate authority. A shortest-&lt;em&gt;hop&lt;/em&gt; path is not in general the shortest-&lt;em&gt;exploitation-cost&lt;/em&gt; path.&lt;/p&gt;
&lt;p&gt;BloodHound Enterprise v6.3 shipped the Butterfly analysis as the first production attempt to relax this assumption. As the v6.3 release notes acknowledge (see Section 9), Butterfly&apos;s weighting function is not publicly documented [@bloodhound-v6-3-release].&lt;/p&gt;
&lt;p&gt;Academic intuition suggests weighting edges by an exploitation-success probability and computing &lt;em&gt;most-likely-exploited&lt;/em&gt; paths via shortest paths under negative-log-probability weights. The complexity ceiling is well-known: Dijkstra (with non-negative weights) runs in $O(E + V\log V)$ time with a Fibonacci heap; Bellman-Ford handles negative weights at $O(VE)$. Either fits comfortably inside the per-query budget BloodHound already operates within.&lt;/p&gt;
&lt;p&gt;The unsolved part is the &lt;em&gt;empirical calibration&lt;/em&gt;: what numerical weight is the right weight on a &lt;code&gt;ForceChangePassword&lt;/code&gt; edge versus a &lt;code&gt;CoerceAndRelayNTLMToADCS&lt;/code&gt; edge? There is no published peer-reviewed answer.&lt;/p&gt;
&lt;p&gt;The second open problem is &lt;strong&gt;coverage&lt;/strong&gt;. The &lt;code&gt;TierZeroTable&lt;/code&gt; README is the authoritative self-disclosure: file-system ACLs on member servers, fine-grained GPO delegation, on-host service-account permissions, some Entra conditional-access logic, and cross-tenant Entra B2B trust paths remain partially or fully out of scope [@specterops-tier-zero-table]. This is an &lt;em&gt;engineering&lt;/em&gt; problem -- more collectors, more edge definitions -- rather than an algorithmic one. OpenGraph is the structural answer: shift coverage from &quot;what edges has SpecterOps modelled?&quot; to &quot;what edges has the community contributed to the shared library?&quot; [@bloodhound-opengraph-library].&lt;/p&gt;
&lt;p&gt;The third is &lt;strong&gt;graph privacy&lt;/strong&gt;. A continuously-collected, complete AD privilege graph shipped to a third-party SaaS backend is, in adversarial hands, a pre-computed attack plan for the customer&apos;s forest. Tenant isolation, encryption at rest, SOC 2 and FedRAMP attestation, and customer-managed key encryption do not eliminate the structural risk: a compromised SaaS backend yields the customer&apos;s graph regardless of compliance posture.&lt;/p&gt;
&lt;p&gt;Cryptographic approaches -- homomorphic graph queries, secure multi-party computation for path enumeration -- exist in the theoretical literature but are not in production attack-path products at time of writing. Adalanche and self-hosted BloodHound CE remain the privacy-preserving options at the cost of forgoing continuous monitoring.&lt;/p&gt;
&lt;p&gt;The fourth is the &lt;strong&gt;the graph is alive&lt;/strong&gt; problem. Session edges (&lt;code&gt;HasSession&lt;/code&gt;) go stale in hours. New ACEs, new group memberships, new sessions appear continuously. SharpHound&apos;s snapshot model is yesterday&apos;s view; continuous collectors (BloodHound Enterprise, MSEM agent streams) trade stealth for freshness. The May 6, 2026 release notes describe BloodHound CE&apos;s first move toward a streaming model: &lt;em&gt;&quot;incremental edge updates that reduce unnecessary writes during post-processing&quot;&lt;/em&gt; [@bloodhound-release-notes-index]. No production attack-path tool yet ships a fully streaming graph.&lt;/p&gt;
&lt;p&gt;The fifth is &lt;strong&gt;combinatorial intractability&lt;/strong&gt;. These are not engineering gaps; they are complexity-theoretic facts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Counting all attack paths is #P-complete.&lt;/strong&gt; Leslie Valiant&apos;s 1979 result on the complexity of counting solutions to combinatorial problems applies directly: counting the simple paths between two nodes in a general graph cannot be done in polynomial time unless P = #P [@valiant-1979-permanent]. BloodHound&apos;s path-count UI is necessarily an approximation or a length-truncation; this is the theoretical reason why.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The minimum-edge-cut &quot;defender hardening&quot; problem is NP-hard.&lt;/strong&gt; Choose the smallest set of edges whose removal disconnects the attacker from the goal. Sheyner et al. 2002 proved the result is NP-hard but admits an $O(\log n)$ approximation [@sheyner-et-al-2002-attack-graphs]. BHE choke-point ranking and MSEM choke-point analysis necessarily implement heuristic approximations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finding regular simple paths is NP-complete.&lt;/strong&gt; Mendelzon and Wood 1995 proved that finding a simple path matching a regular expression over edge labels in a graph database is NP-complete [@mendelzon-wood-1995-dblp]. Cypher&apos;s &lt;code&gt;shortestPath()&lt;/code&gt; does not enforce simple-path semantics, which is why it remains in P; quantified path patterns with &lt;code&gt;DIFFERENT RELATIONSHIPS&lt;/code&gt; semantics (available since Neo4j 5.x and documented in the current Cypher manual [@neo4j-cypher-variable-length]) do enforce simple paths and so cross into the NP-complete regime.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; BloodHound&apos;s per-query algorithm (BFS, $O(V + E)$) is optimal up to constants. The frontier of the field is no longer the algorithm. It is what &lt;em&gt;question&lt;/em&gt; we ask the algorithm: weighted? regular-path? simple-path? bi-directional? cross-substrate? Each open question is a different model, not a different implementation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Three further honest framings deserve a mention.&lt;/p&gt;
&lt;p&gt;First, the &lt;strong&gt;standardisation of OpenGraph edge taxonomies&lt;/strong&gt; is unsettled. Without community convergence on edge naming, different contributors may model the same substrate with incompatible schemas. Historical precedent (MITRE ATT&amp;amp;CK technique IDs, CVE identifiers) suggests that convergence happens when a single high-trust curator becomes the de-facto registry; whether SpecterOps will operate OpenGraph as a vendor-neutral standards body or as a SpecterOps-owned artefact is a governance question, not an algorithmic one.&lt;/p&gt;
&lt;p&gt;Second, the &lt;strong&gt;adversarial robustness of the collector&lt;/strong&gt; is an open question: SharpHound runs as an authenticated principal, and an attacker with prior compromise can poison the collection. There is no closed-form defence.&lt;/p&gt;
&lt;p&gt;Third, the &lt;strong&gt;absence of any public head-to-head benchmark&lt;/strong&gt; of BloodHound CE versus BHE versus Adalanche versus MSEM on the same forest under controlled conditions is structural: Microsoft does not publish per-query latency, SpecterOps publishes only relative improvement claims, and the academic line uses 2005-era hardware figures that are not comparable.&lt;/p&gt;
&lt;h2&gt;12. Practical guide -- running BloodHound today&lt;/h2&gt;
&lt;p&gt;If the previous sections sold you on the model, the next few paragraphs are the minimum you need to stand it up.&lt;/p&gt;
&lt;p&gt;Stand up BloodHound CE with the Docker Compose file in the repository [@bloodhound-ce-repo]. The stack is four containers: a PostgreSQL application database (users, roles, sessions, audit logs, saved queries); a Neo4j graph database holding the property graph; a Go REST API; and a React plus Sigma.js single-page frontend. Five minutes to first boot on a developer laptop. The repository README is the authoritative deployment reference.&lt;/p&gt;
&lt;p&gt;Run &lt;strong&gt;SharpHound&lt;/strong&gt; on a domain-joined Windows host as the collection identity. The default invocation -- &lt;code&gt;SharpHound.exe --CollectionMethods all,GPOLocalGroup&lt;/code&gt; -- enumerates every group membership, every recognised ACL pattern, every active session, every local-admin relationship, and every Kerberos delegation. Run &lt;strong&gt;AzureHound&lt;/strong&gt; with appropriate Entra ID credentials for Entra and AzureRM coverage. Both emit JSON dumps in the same envelope; the BloodHound CE upload tab in the web UI ingests both.&lt;/p&gt;
&lt;p&gt;Open the web UI. The stock pre-built queries are a reasonable starting palette: &lt;em&gt;Find Shortest Paths to Domain Admins&lt;/em&gt;, &lt;em&gt;Find Principals with DCSync Rights&lt;/em&gt;, &lt;em&gt;Find Computers with Unsupported Operating Systems&lt;/em&gt;, and &lt;em&gt;Shortest Paths from Owned Principals to High-Value Targets&lt;/em&gt; (after marking some accounts as owned). Custom Cypher goes in the Cypher tab at the top right; the Section 6 query is a good template.&lt;/p&gt;
&lt;p&gt;The most important interpretation discipline: treat the result as a &lt;em&gt;risk register&lt;/em&gt;, not a vulnerability list. A finding is &quot;Bob can reach Domain Admins via a four-hop path.&quot; The &lt;em&gt;edge&lt;/em&gt; is &quot;Bob has &lt;code&gt;GenericWrite&lt;/code&gt; on Carol.&quot; Closing the edge breaks the finding &lt;em&gt;and&lt;/em&gt; every other path that passed through it. Edges are the unit of remediation, not findings. SpecterOps&apos;s own 2021 customer-anonymous essay &lt;em&gt;Active Directory Attack Paths -- Is It Always This Bad?&lt;/em&gt; reports findings from hundreds of engagements, and the recurring observation across forests is that a small number of high-blast-radius edges explain most of the discovered paths [@specterops-2021-ad-attack-paths].&lt;/p&gt;
&lt;p&gt;A few pitfalls are worth naming. &lt;code&gt;HasSession&lt;/code&gt; collection generates measurable LDAP and SAMR traffic that Microsoft Defender for Identity alerts on; coordinate with the blue team or expect detections. The stealth collection mode trades coverage for traffic volume.&lt;/p&gt;
&lt;p&gt;Unconstrained variable-length Cypher queries (&lt;code&gt;MATCH p=(a)-[*]-&amp;gt;(b)&lt;/code&gt;) can pin Neo4j&apos;s heap; CE&apos;s &quot;protected Cypher&quot; cost limits help but do not eliminate the problem, so prefer &lt;code&gt;shortestPath()&lt;/code&gt; or bound the path length explicitly. The wildcard-principal post-processing for &lt;code&gt;Authenticated Users&lt;/code&gt; and &lt;code&gt;Everyone&lt;/code&gt; requires v6.0 or later to be correct; older versions miscount these edges [@bloodhound-v6-0-release]. And the &lt;code&gt;CoerceAndRelayNTLMToADCS&lt;/code&gt; edge is Group to Computer, not Computer to Computer, as discussed in Section 7.&lt;/p&gt;

```cypher
MATCH (n)
WHERE n.system_tags CONTAINS &apos;admin_tier_0&apos;
WITH collect(n) AS tier_zero
MATCH p = shortestPath((u:User {enabled:true})-[*1..6]-&amp;gt;(t))
WHERE t IN tier_zero AND NOT u.system_tags CONTAINS &apos;admin_tier_0&apos;
RETURN u.name AS source, t.name AS target, length(p) AS hops
ORDER BY hops ASC
LIMIT 25
```&lt;p&gt;This returns up to 25 enabled non-Tier-Zero users with the shortest paths into Tier Zero. The &lt;code&gt;[*1..6]&lt;/code&gt; bound prevents the pathological cyclic-graph cost explosion. Bound length aggressively until you have indexed your graph.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;

BloodHound is dual-use. Authorised defensive use on your own forest, or contracted penetration testing within written scope, is the standard legal posture. Running it against a directory you are not authorised to assess is unlawful in most jurisdictions: the Computer Fraud and Abuse Act in the United States; the Computer Misuse Act 1990 in the United Kingdom; equivalents in most EU jurisdictions. The dual-use posture is fundamental to the tool; the legal posture depends on you.
&lt;p&gt;The tool is the easy part. The hard part is what you do with the answer.&lt;/p&gt;
&lt;h2&gt;13. Frequently asked questions&lt;/h2&gt;
&lt;p&gt;The misconceptions worth disposing of, in order of how often they recur.&lt;/p&gt;

No. BloodHound is the SpecterOps-maintained, evolving set of edge families. File-system ACLs on member servers, fine-grained GPO delegation, on-host service-account permissions, and some Entra conditional-access logic remain partially or fully out of scope. The `SpecterOps/TierZeroTable` README is explicit about this limitation [@specterops-tier-zero-table]. Coverage expansion is iterative and community-fed; OpenGraph is the structural answer to scope generalisation.

The original BloodHound (2016) shipped Neo4j only. The modern BloodHound CE uses *both*: PostgreSQL as the application database (users, roles, sessions, audit logs) and Neo4j as the graph layer [@bloodhound-ce-repo]. BloodHound Enterprise has begun migrating entirely off Neo4j onto PostgreSQL-as-graph (announced in v6.3, December 2024) [@bloodhound-v6-3-release]; Cypher continues to be the query language on the new backend. The model is engine-independent; Adalanche proves the same point by doing it all in process memory in Go [@adalanche-repo].

Authorised defensive use on your own forest, yes. Contracted penetration testing within written scope, yes. Running it against a directory you are not authorised to assess is unlawful in most jurisdictions: the Computer Fraud and Abuse Act in the United States, the Computer Misuse Act 1990 in the United Kingdom, and equivalents in most EU jurisdictions. The dual-use posture is fundamental to the tool; legal compliance is the operator&apos;s responsibility.
&lt;h2&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;The 2014 analyst with the whiteboard and the 2024 analyst with the Cypher query are doing the same work. The unit of analysis has shifted, and once the unit shifts, the field does not go back.&lt;/p&gt;
&lt;p&gt;John Lambert diagnosed it in two sentences in April 2015 [@lambert-2015-defenders-lists-attackers-graphs]. Andy Robbins, Rohan Vazarkar, and Will Schroeder shipped it as BloodHound in August 2016 [@bloodhound-legacy-repo]. SpecterOps extended it through AzureHound in 2020, the CE 5.0 web architecture in 2023, the Tier Zero formalisation in 2023, ADCS composed edges in 2024, Butterfly bi-directional analysis in 2024, and OpenGraph in 2025 [@specterops-2025-opengraph].&lt;/p&gt;
&lt;p&gt;Microsoft validated the model with Lateral Movement Paths in 2018, cloud security graph attack-path analysis in 2022 to 2023, and Microsoft Security Exposure Management at Ignite in 2024 [@ms-msem-attack-paths]. The community that shipped the graph won; the community that kept shipping lists is selling compliance reports to the auditors.&lt;/p&gt;
&lt;p&gt;The frontier in 2026 is not whether to model attacks as a graph -- that argument is settled. The frontier is how to make the graph weighted (so the shortest path approximates the easiest), how to make it complete (so the unmodelled edges shrink toward zero), and how to make it substrate-independent (so the next enterprise primitive worth modelling -- whatever it turns out to be -- can be ingested without changing the engine). Each of these is a research direction with its own asymptotic ceiling, its own engineering practice, and its own community of contributors.&lt;/p&gt;
&lt;p&gt;What started as a sentence in a 1,100-word essay on a personal GitHub repository is now an ISO-standardised query language [@iso-39075-2024-gql], a shipped Microsoft product family, an open-source repository with hundreds of thousands of downloads, and a discipline taught at most major security conferences. The graph wins because the graph is the right model. The right model wins because, eventually, the right model always does.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;bloodhound-attack-path-graph&quot; keyTerms={[
  { term: &quot;Attack-path graph&quot;, definition: &quot;A directed graph whose nodes are security principals and resources and whose edges are privilege relationships an attacker can traverse. Reachability in the graph models multi-hop privilege escalation.&quot; },
  { term: &quot;Directed property graph&quot;, definition: &quot;A graph in which both nodes and edges have types and can carry key-value properties. The data model BloodHound uses.&quot; },
  { term: &quot;Cypher&quot;, definition: &quot;The pattern-matching query language for Neo4j and now the basis of the ISO/IEC 39075:2024 GQL standard.&quot; },
  { term: &quot;Bidirectional BFS&quot;, definition: &quot;Breadth-first search executed simultaneously from source and target, meeting in the middle. The algorithm Neo4j&apos;s shortestPath() runs and the algorithm BloodHound inherits.&quot; },
  { term: &quot;Tier Zero&quot;, definition: &quot;Any node in the attack-path graph whose compromise lets an attacker reach an administrative privilege in the forest. The endpoint of every BloodHound shortest-path query.&quot; },
  { term: &quot;Traversable / non-traversable edge&quot;, definition: &quot;A traversable edge can be stepped through directly by a path query; a non-traversable edge is a precondition fact that, with others, composes into a traversable edge during post-processing. ADCS edges are the canonical example.&quot; },
  { term: &quot;OpenGraph&quot;, definition: &quot;BloodHound&apos;s v8.0 (July 2025) generalisation that decouples the graph engine from the AD-specific schema, admitting user-defined node and edge kinds for arbitrary substrates.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>active-directory</category><category>bloodhound</category><category>graph-theory</category><category>attack-paths</category><category>specterops</category><category>identity-security</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>Privileged Identity Management: How a Two-State Role Assignment Retired Standing Admin</title><link>https://paragmali.com/blog/privileged-identity-management-how-a-two-state-role-assignme/</link><guid isPermaLink="true">https://paragmali.com/blog/privileged-identity-management-how-a-two-state-role-assignme/</guid><description>Microsoft Entra PIM did not add eight features. It added one field to the role-assignment object -- and everything else, from activation policies to GDAP, is downstream.</description><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Standing Global Administrator was never a design choice. It was the only posture a single-state role-assignment object could produce.** Microsoft Entra PIM added one field to that object -- `type: eligible | active` -- and everything downstream (activation policies, audit logs, access reviews, six PIM Alerts, PIM-for-Groups, PIM-for-Azure-Resources, GDAP, Lighthouse, PIM with Conditional Access) is a structural consequence of that single change. The pattern works for human users. The open boundary in 2026 is application identities -- service principals, managed identities, OAuth consent grants -- which route around PIM entirely via the Azure Instance Metadata Service endpoint at `169.254.169.254`, the bypass class Andy Robbins documented in June 2022 and MITRE ATT&amp;amp;CK now maps to T1078.004.
&lt;h2&gt;1. The Tenant with Zero Standing Global Administrators&lt;/h2&gt;
&lt;p&gt;At 14:03:01 on a Tuesday in 2026, &lt;a href=&quot;mailto:alice@contoso.com&quot; rel=&quot;noopener&quot;&gt;alice@contoso.com&lt;/a&gt; became Global Administrator of her company&apos;s Microsoft Entra tenant. At 15:03:01 the same day, she stopped being one. In between, she restored a deleted user, exported an audit log, and produced a single PIM record: &lt;code&gt;Justification&lt;/code&gt; reads &quot;incident MSRC-2026-PIM-12345, ticket SNOW-INC-987654&quot;; &lt;code&gt;Approver&lt;/code&gt; reads &quot;&lt;a href=&quot;mailto:bob@contoso.com&quot; rel=&quot;noopener&quot;&gt;bob@contoso.com&lt;/a&gt; (decided 14:02:17)&quot;; &lt;code&gt;ActivatedAt&lt;/code&gt; and &lt;code&gt;ExpiredAt&lt;/code&gt; differ by exactly &lt;code&gt;PT1H&lt;/code&gt;. The SOC 2 auditor signed it off without follow-up questions.&lt;/p&gt;
&lt;p&gt;The 2015-vintage version of the same tenant looked nothing like this. Twelve standing Global Administrators. No multifactor challenge at privilege use. No approval workflow. No justification field. No audit trail beyond ordinary sign-in logs. A single phish of any one of those twelve identities was tenant takeover. The math required no sophistication: the attack surface for &quot;Global Administrator of contoso.com&quot; equalled the union of twelve personal attack surfaces, indefinitely.&lt;/p&gt;
&lt;p&gt;What changed between the two tenants is not a habit, not a policy, not a culture shift. It is a single field on a single object inside Microsoft Entra ID.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Standing admin was never a deliberate design decision. It was the only deployment posture a single-state role-assignment object could produce. Once Microsoft made the role-assignment object two-state, JIT admin became expressible -- and standing admin became visibly the anti-pattern it had been since 1975.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To explain that field, and to explain why it took fifty-one years to ship, we start where the principle did: a 1975 paper by two MIT researchers who knew what privilege should look like but had no mechanism to enforce it.&lt;/p&gt;
&lt;h2&gt;2. The Default Wasn&apos;t a Decision&lt;/h2&gt;
&lt;p&gt;Who designed the standing Domain Admin pattern? No one. It was the only assignment category Active Directory shipped with.&lt;/p&gt;
&lt;p&gt;A forty-year deployment posture with no author. That is the first thing to internalize. Standing admin is what happens when a data model offers exactly one assignment category and operators still have real work to do. Every later &quot;best practice&quot; was an attempt to talk operators out of the one tool they had been given.&lt;/p&gt;
&lt;h3&gt;1975: The principle without a mechanism&lt;/h3&gt;
&lt;p&gt;In September 1975, Jerome Saltzer and Michael Schroeder published &lt;em&gt;The Protection of Information in Computer Systems&lt;/em&gt; in the &lt;em&gt;Proceedings of the IEEE&lt;/em&gt; [@saltzer-schroeder-1975]. The paper is a survey of secure-systems design, organized around eight named design principles that the authors crystallized from work on Multics and other early protected operating systems. Both authors were affiliated with MIT&apos;s Project MAC and the Department of Electrical Engineering and Computer Science [@saltzer-mit-meta].&lt;/p&gt;
&lt;p&gt;The sixth principle, named &lt;strong&gt;Least Privilege&lt;/strong&gt;, is the one every later JIT-admin product cites:&lt;/p&gt;

Every program and every user of the system should operate using the least set of privileges necessary to complete the job. -- Saltzer &amp;amp; Schroeder, *The Protection of Information in Computer Systems*, 1975, Design Principle (f), the sixth of eight [@saltzer-schroeder-1975]

Design Principle (f), the sixth of eight, in the 1975 Saltzer and Schroeder paper. Every program and every user of the system should operate using the least set of privileges necessary to complete the job. The principle is correct, parsimonious, and -- for four decades after publication -- mechanically unenforceable for the temporal case. Static enforcement (ACLs, capability lists, ring boundaries) was tractable in 1975; bounding the time interval during which a privilege is held was not.
&lt;p&gt;Read the principle carefully. It does not say &quot;every user should hold the least set of privileges.&quot; It says they should &lt;em&gt;operate using&lt;/em&gt; the least set of privileges. The two formulations look identical until you ask what a person does between bursts of administrative work. A user who holds the privilege &quot;permanently active&quot; is operating using it permanently, whether they touch the system or not. The 1975 paper points at the temporal dimension and walks past it. The worked examples cover static mechanisms -- protection rings, access control lists, capability tickets -- not time-bounded ones. The principle was correct. The mechanism did not yet exist.&lt;/p&gt;
&lt;p&gt;For the next forty years, every approximation tried to compensate. UNIX &lt;code&gt;sudo&lt;/code&gt; (1980) bound elevation to a single command. Kerberos delegation (1988) bound impersonation to a ticket. Windows DACLs and Active Directory groups (1993 and 2000) bound access to a static membership list. None made temporal least privilege a first-class data-model property. None let an operator say &quot;I am eligible to be Domain Admin, but I am not Domain Admin right now.&quot;&lt;/p&gt;

Microsoft&apos;s 2014 *Mitigating Pass-the-Hash v2* whitepaper introduced a three-tier administrative model. Tier 0 is identity-system-critical: domain controllers, ADFS, PKI, anything whose compromise gives forest-wide privilege. Tier 1 is enterprise servers and business-critical applications. Tier 2 is user workstations and end users. The enforcement rule is one sentence: an administrator credential for Tier N must never be exposed to a system at a higher (numerically larger) tier. Microsoft has progressively retired this framing in favour of the Enterprise Access Model, which we revisit in section 6.
&lt;h3&gt;2000-2013: Group membership as a boolean&lt;/h3&gt;
&lt;p&gt;When Active Directory shipped with Windows 2000 on February 17, 2000 [@ms-news-windows-2000-launch], privileged access was structurally a boolean property of the principal. A user was either a member of &lt;code&gt;BUILTIN\Administrators&lt;/code&gt;, &lt;code&gt;Domain Admins&lt;/code&gt;, &lt;code&gt;Enterprise Admins&lt;/code&gt;, or &lt;code&gt;Schema Admins&lt;/code&gt;, or they were not. The membership lived in the directory as the &lt;code&gt;member&lt;/code&gt; attribute on the group object (and the &lt;code&gt;memberOf&lt;/code&gt; back-link on the user). It was set when assignment was made, unset when an administrator manually revoked it. No third state. No attribute could hold one.&lt;/p&gt;

A privileged identity whose role assignment is active and permanent. The role&apos;s permissions are granted continuously, regardless of whether the principal is currently exercising the privilege. Standing admin is the default state of any pre-PIM tenant and the deployed-reality state of most AD-only environments through 2026.
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/kerberos-in-windows-the-other-half-of-ntlmless/&quot; rel=&quot;noopener&quot;&gt;Kerberos&apos;s Privilege Attribute Certificate&lt;/a&gt; -- the PAC -- carried the user&apos;s group SIDs forward into every Kerberos ticket the user obtained.The Privilege Attribute Certificate is the data structure inside a Kerberos ticket that lists the user&apos;s group SIDs. Pre-2016 Active Directory had no per-membership TTL metadata in the PAC. There was nowhere in the existing schema to put an expiry timestamp, which is why on-prem JIT membership later required a &lt;em&gt;separate forest&lt;/em&gt; rather than an in-directory mechanism. A ticket&apos;s lifetime was bounded; the SID set inside it was not. There was no per-membership TTL anywhere in the system. If you wanted &quot;Alice is Domain Admin between 14:00 and 15:00 today and not otherwise,&quot; the directory had no machinery to express it. Alice was Domain Admin permanently, or not at all.&lt;/p&gt;
&lt;p&gt;Twenty years of deployment matched the data model exactly. A typical 2010-vintage enterprise ran ten to thirty standing Domain Administrators across business units, because manually adding and removing membership for each task was untenable at human scale. The data model did not punish standing membership; the operator chose the only category the directory offered.&lt;/p&gt;
&lt;h3&gt;December 2012: Microsoft names the failure mode&lt;/h3&gt;
&lt;p&gt;In December 2012, Patrick Jungles, Mark Simos, Aaron Margosis, Roger Grimes, Laura Robinson and the Microsoft Trustworthy Computing team published &lt;em&gt;Mitigating Pass-the-Hash and Other Credential Theft, Version 1&lt;/em&gt; [@pth-download-center], [@berkouwer-pth-2013]. It is the first formal Microsoft acknowledgment that credential-theft propagation through Active Directory was not a software defect to be patched but a structural property of standing admin membership.&lt;/p&gt;
&lt;p&gt;The argument is direct. If twelve Domain Admins exist, the attack surface of &quot;Domain Admin of contoso.local&quot; is the union of those twelve people&apos;s personal attack surfaces. Any one gets phished, or gets hash-extracted from a Tier-1 server they accidentally signed into, and the attacker has Domain Admin permanently. The MIM PAM documentation later restated the failure in one sentence: &lt;em&gt;&quot;Today, it&apos;s too easy for attackers to obtain Domain Admins account credentials, and it&apos;s too hard to discover these attacks after the fact&quot;&lt;/em&gt; [@ms-learn-mim-pam-overview].&lt;/p&gt;
&lt;h3&gt;2014: The tier model arrives, the mechanism does not&lt;/h3&gt;
&lt;p&gt;The 2014 update -- &lt;em&gt;Mitigating Pass-the-Hash, Version 2&lt;/em&gt; [@pth-download-center] -- generalized the threat model and introduced the &lt;a href=&quot;https://paragmali.com/blog/who-is-allowed-to-log-in-where-the-kdc-side-answer-to-creden/&quot; rel=&quot;noopener&quot;&gt;Tier-0 / Tier-1 / Tier-2 framing&lt;/a&gt; as a structural mitigation. v2 said two things clearly that v1 had only implied. First, standing membership in Tier-0 groups was the root cause, not a downstream defect. Second, the mitigation pattern -- isolate tiers, reduce the standing count, use dedicated Privileged Access Workstations -- was &lt;em&gt;guidance&lt;/em&gt;, not a mechanism. Microsoft Trustworthy Computing did not yet have a product that could mechanically time-bound group membership in Active Directory.&lt;/p&gt;
&lt;p&gt;v2 named the problem, drew the threat model, and recommended the structural fix. What it could not do was ship a mechanism. The mechanism would come, but on the wrong side of the cloud boundary.&lt;/p&gt;
&lt;h2&gt;3. The On-Prem Detour: MIM 2016 PAM, Bastion Forests, and Shadow Principals&lt;/h2&gt;
&lt;p&gt;Microsoft&apos;s first mechanical JIT-admin product was not in the cloud. It was on-premises, and it required a separate Active Directory forest.&lt;/p&gt;
&lt;p&gt;Stop and re-read that. To bound the duration of a group membership in pre-2016 Active Directory, Microsoft had to build a &lt;em&gt;different&lt;/em&gt; directory and inject SIDs from one into the other across a trust. The reason was the data model. The production forest&apos;s &lt;code&gt;member&lt;/code&gt; attribute had no TTL field. Adding one meant changing the AD schema. Changing the schema meant a Windows Server release. So while the schema change was in flight, Microsoft shipped the on-prem JIT-admin product on a different architecture: ask the operator to stand up a second forest whose only job was to issue time-bounded SIDs into the first.&lt;/p&gt;
&lt;h3&gt;August 6, 2015: MIM 2016 ships PAM&lt;/h3&gt;
&lt;p&gt;On August 6, 2015, Microsoft Identity Manager 2016 reached general availability and shipped a new capability named &lt;strong&gt;Privileged Access Management&lt;/strong&gt; [@ms-learn-mim-pam-overview]. The architecture is the interesting part. MIM PAM uses three primitives that, together, give Active Directory a mechanically time-bounded group membership for the first time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;bastion forest&lt;/strong&gt; -- an entirely separate Active Directory forest, sometimes called the &quot;red&quot; forest or &quot;admin&quot; forest, where privileged accounts live.&lt;/li&gt;
&lt;li&gt;A one-way &lt;strong&gt;PAM trust&lt;/strong&gt; from the production forest to the bastion forest, configured for selective authentication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shadow principal&lt;/strong&gt; objects in the bastion forest, each carrying a SID that names a real privileged group in the production forest.&lt;/li&gt;
&lt;/ol&gt;

A separate Active Directory forest dedicated to housing privileged accounts. In MIM 2016 PAM the bastion forest holds shadow-principal objects whose SIDs point at production-forest privileged groups; a one-way PAM trust lets the production forest accept those SIDs in incoming Kerberos tickets for a bounded duration.

An Active Directory object (schema class `msDS-ShadowPrincipal`, introduced in Windows Server 2016) that represents a foreign user, group, or computer in the bastion forest and carries an `msDS-ShadowPrincipalSid` attribute populated with the SID of a production-forest privileged group. Membership in a shadow principal results in that production-forest SID being added to the requesting user&apos;s Kerberos PAC for the membership TTL.
&lt;p&gt;The activation flow is direct. A user in the bastion forest requests privilege through the MIM Portal. An approver decides. MIM writes a TTL-bounded membership in the appropriate shadow principal, with the TTL enforced by the Windows Server 2016 temporal-group-membership feature [@teal-esae3]. The bastion KDC injects the production-forest SID into the user&apos;s Kerberos PAC. The production forest accepts that SID across the PAM trust. After the TTL expires, subsequent ticket renewals exclude the privileged SID, and the user no longer holds the privilege.&lt;/p&gt;

flowchart LR
    subgraph BASTION[&quot;CORP-PRIV bastion forest&quot;]
        A[&quot;Privileged user account&quot;]
        SP[&quot;Shadow principal (msDS-ShadowPrincipal) carries production SID, TTL&quot;]
        BKDC[&quot;Bastion KDC&quot;]
        A --&amp;gt;|&quot;Time-bound membership&quot;| SP
        SP --&amp;gt; BKDC
    end
    subgraph PROD[&quot;CORP production forest&quot;]
        DA[&quot;Domain Admins&quot;]
        PKDC[&quot;Production KDC&quot;]
    end
    BKDC --&amp;gt;|&quot;Kerberos ticket carries injected SID via PAM trust&quot;| PKDC
    PKDC --&amp;gt;|&quot;SID in PAC grants membership for TTL only&quot;| DA
&lt;h3&gt;October 15, 2016: Windows Server 2016 makes the mechanism real&lt;/h3&gt;
&lt;p&gt;For the first fourteen months of MIM 2016&apos;s life, the full feature did not work. The temporal-group-membership and shadow-principal schema classes that MIM PAM depends on are AD primitives that arrived only with Windows Server 2016, which reached general availability on October 15, 2016 [@ms-learn-lifecycle-ws2016]. Microsoft Learn states the requirement directly: &lt;em&gt;&quot;With Windows Server 2016, PAM features of time-limited group memberships and shadow principal groups are built into Windows Server Active Directory&quot;&lt;/em&gt; [@ms-learn-raise-bastion], and &lt;em&gt;&quot;All domain controllers in the bastion environment for the PRIV forest must be Windows Server 2016 or later&quot;&lt;/em&gt; [@ms-learn-raise-bastion].The PAM trust is technically a forest trust with selective authentication enabled. The selective authentication flag is what prevents the bastion forest&apos;s privileged identities from being usable for anything other than the explicit shadow-principal SID injection -- without it, the bastion forest would itself become a sprawling privileged-access surface.&lt;/p&gt;
&lt;p&gt;This is the moment AD itself gains a temporal least-privilege primitive, forty-one years after Saltzer and Schroeder published the principle. The mechanism is real, but the operational profile is brutal.&lt;/p&gt;
&lt;h3&gt;Three reasons it did not generalize&lt;/h3&gt;
&lt;p&gt;MIM PAM solved exactly one problem and could not be extended to the next. Three structural constraints kept it confined to a niche.&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;it was on-premises only&lt;/strong&gt;. A bastion forest is an Active Directory artifact. Microsoft Entra ID, Office 365, and Azure RBAC role assignments live in a different identity system, with no concept of a forest, no PAM trust target, and no place to plug a shadow-principal object. MIM PAM had no cloud story, and by 2015 the cloud was already where most new Microsoft privileged-access surfaces were being deployed.&lt;/p&gt;
&lt;p&gt;Second, &lt;strong&gt;the operational complexity filtered out everyone except the most security-mature shops&lt;/strong&gt;. A bastion forest is a separate Active Directory forest, with its own domain controllers, replication, backup, disaster recovery, and PKI implications. The deployment also requires MIM Service, MIM Portal, MIM Web Service, and SQL Server. Auditing the PAM trust correctly is itself non-trivial work. Microsoft Learn now positions MIM PAM as appropriate only for isolated, non-Internet-connected deployments [@ms-learn-mim-pam-overview]; the verbatim positioning and the MIM 2016 lifecycle details are in the Callout below.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Microsoft Learn states MIM PAM is &quot;not recommended for new deployments in Internet-connected environments&quot; and positions it for &quot;isolated AD environments where Internet access is not available&quot; [@ms-learn-mim-pam-overview]. MIM 2016 itself remains in extended support through January 9, 2029 [@ms-learn-mim-2016], and Microsoft has shipped SP3 compatibility updates for SharePoint Subscription Edition, Exchange SE, and SQL Server 2022 -- but the cloud-first Entra PIM path is the canonical answer for new tenants.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Third, &lt;strong&gt;the forest-functional-level dependency delayed real deployment by more than a year&lt;/strong&gt;. Shadow principals were not usable until Windows Server 2016 reached GA in October 2016. MIM 2016 had been generally available since August 2015. For its first fourteen months in market, the headline JIT-admin feature could not be configured at full fidelity. By the time Windows Server 2016 shipped, Microsoft was already operating its cloud PIM in production.&lt;/p&gt;
&lt;h3&gt;What the on-prem detour reveals about the cloud&apos;s shape&lt;/h3&gt;
&lt;p&gt;MIM PAM mechanically bounds membership &lt;em&gt;in groups&lt;/em&gt; via &lt;em&gt;shadow principals&lt;/em&gt; in &lt;em&gt;a separate forest&lt;/em&gt;. The cloud has no concept of a forest. So the cloud-native mechanical bound must attach to the &lt;em&gt;assignment object&lt;/em&gt; directly, not to the &lt;em&gt;group object indirected through a separate forest&lt;/em&gt;. The cloud needed a new assignment-category type, not a new forest topology.&lt;/p&gt;
&lt;p&gt;The cloud does not have a forest. It has a role-assignment object. What if that object grew a second state?&lt;/p&gt;
&lt;h2&gt;4. The Breakthrough: A Two-State Role-Assignment Object&lt;/h2&gt;
&lt;p&gt;By August 2015, while MIM 2016 PAM was still in late preview for the on-premises case, the Microsoft Identity Division had already shipped something different for the cloud. They shipped a role-assignment object with one new field. That field changed everything that came after it.&lt;/p&gt;
&lt;h3&gt;The 2015 preview&lt;/h3&gt;
&lt;p&gt;Alex Simons&apos;s August 27, 2015 capability-update post on the CloudBlogs (now migrated to Microsoft Tech Community) is the first public articulation of what Azure AD PIM was building [@simons-2015-aug]. It introduced four surfaces: an &lt;strong&gt;eligible&lt;/strong&gt; assignment category distinct from active, multifactor authentication required at activation, security alerts that watched for privileged-role anomalies, and what the post called Security Reviews -- the precursor to access reviews. The architecture under those four surfaces is the load-bearing part: a single new field on the role-assignment object.&lt;/p&gt;
&lt;p&gt;On September 15, 2016, Azure AD Premium P2 reached general availability and carried the first generally-available cloud-native PIM, attributed to Joy Chik (then Corporate Vice President of the Identity Division) and the Identity engineering team [@techcommunity-p2-ga]. Eligible-versus-active was now a billable, supported, production-grade feature.&lt;/p&gt;
&lt;h3&gt;The one-function spine&lt;/h3&gt;
&lt;p&gt;Read this carefully. It is the article&apos;s central claim.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Standing admin was the default not because anyone thought it was secure, but because the role-assignment object had only one state. PIM&apos;s contribution is to add a second state -- &lt;code&gt;eligible&lt;/code&gt; -- and to make the transition from eligible to active a gated, audited, time-bounded operation that is by definition mediated by PIM.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The principle was Saltzer and Schroeder, 1975. The recognition that standing admin was the failure mode was &lt;em&gt;Mitigating Pass-the-Hash&lt;/em&gt;, 2012 and 2014. The on-premises mechanism was MIM 2016 PAM. The cloud answer is a different shape entirely: not a new directory and a SID-injection trust, but a single field on the assignment object itself.&lt;/p&gt;
&lt;p&gt;Microsoft Learn documents the resulting terminology in the PIM overview. A principal -- user, group, service principal, or managed identity -- can be &lt;code&gt;eligible&lt;/code&gt; or &lt;code&gt;active&lt;/code&gt; for a role, and either assignment can be &lt;code&gt;permanent&lt;/code&gt; or &lt;code&gt;time-bound&lt;/code&gt; [@ms-learn-pim-configure]. The same page elevates a forty-year-old phrase into a product term: &lt;em&gt;&quot;principle of least privilege access -- A recommended security practice in which every user is provided with only the minimum privileges needed to accomplish the tasks they&apos;re authorized to perform&quot;&lt;/em&gt; [@ms-learn-pim-configure]. The 1975 sentence is now a glossary entry inside a 2026 product, and the product has a mechanism that makes the sentence enforceable.&lt;/p&gt;
&lt;h3&gt;The formal tuple&lt;/h3&gt;
&lt;p&gt;Concretely, a PIM-managed role assignment is a 5-tuple. Let $A = (p, r, s, t, d)$ where $p$ is the principal, $r$ is the role, $s$ is the scope, $t \in {\text{eligible}, \text{active}}$, and $d \in {\text{permanent}, \text{time-bound}[s_0, e_0]}$. The activation transition is&lt;/p&gt;
&lt;p&gt;$$\text{activate}: A_{t=\text{eligible}} \longrightarrow A_{t=\text{active},\ d=\text{time-bound}[\text{now},\ \text{now}+\Delta]}$$&lt;/p&gt;
&lt;p&gt;subject to the per-role activation policy. The interesting part is what the tuple makes expressible:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RoleAssignment = {
    principal:  user | group | service principal | managed identity,
    role:       Entra directory role | Azure RBAC role | group membership | group ownership,
    scope:      directory | management-group | subscription | resource-group | resource | group,
    type:       eligible | active,
    duration:   permanent | time-bound[start, end]
}

activate: eligible_assignment -&amp;gt; active_assignment   // PIM-mediated, gated, audited
&lt;/code&gt;&lt;/pre&gt;

A PIM-managed role assignment that grants no privilege until the principal invokes `activate()`. The eligible assignment is the standing relationship between principal and role; the active assignment is the time-bounded materialization that follows when the activation policy is satisfied [@ms-learn-pim-configure].

A PIM-managed role assignment that grants the role&apos;s permissions for the duration of the assignment. Active assignments are either permanent (the legacy pre-PIM posture, or an explicit permanent-active PIM assignment) or time-bound (the result of an `activate()` call on an eligible assignment) [@ms-learn-pim-configure].

flowchart TD
    subgraph Permanent[&quot;Permanent duration&quot;]
        PE[&quot;Permanent eligible -- standing eligibility, no privilege held&quot;]
        PA[&quot;Permanent active -- legacy standing admin&quot;]
    end
    subgraph TimeBound[&quot;Time-bound duration&quot;]
        TE[&quot;Time-bound eligible -- standing eligibility with end date&quot;]
        TA[&quot;Time-bound active -- JIT admin after activate()&quot;]
    end
    PE --&amp;gt;|&quot;activate()&quot;| TA
    TE --&amp;gt;|&quot;activate()&quot;| TA
    TA --&amp;gt;|&quot;expire or deactivate()&quot;| PE
    PA --&amp;gt;|&quot;legacy posture being retired&quot;| PE
&lt;p&gt;The grid has only four cells. Permanent active is the pre-PIM world, the standing-admin posture every later best practice has been trying to retire. Time-bound active is the JIT-admin state, materialized only at the moment of work and expired shortly after. The two eligible states -- permanent or time-bound -- are the standing relationships between a principal and a role that grant no privilege at rest. The expressive change is small. The deployment consequences are total.&lt;/p&gt;

PIM did not add eight features. It added one field, and everything else is downstream.
&lt;p&gt;This is Aha #1. The reader who came in believing standing admin persisted for forty years because operators lacked discipline now sees it differently. Operator discipline was a fragile workaround for a missing data-model field. The 1975 principle was correct. The 2012-2014 PtH whitepapers were correct. The operators were not the problem. The role-assignment object had one state to be in, and the deployment matched the data model exactly. The fix was a structural change to the data model.&lt;/p&gt;
&lt;p&gt;The next nine years of PIM history are about extending that two-state primitive: to Azure RBAC, to security groups, to partner tenants, to the conditional-access plane, and to a detection layer that flags people who try to skip activation entirely. We walk each extension in turn. First, the mechanism itself.&lt;/p&gt;
&lt;h2&gt;5. Anatomy of an Activation&lt;/h2&gt;
&lt;p&gt;We have seen what changed. Walk through what happens, end to end, when &lt;a href=&quot;mailto:alice@contoso.com&quot; rel=&quot;noopener&quot;&gt;alice@contoso.com&lt;/a&gt; clicks &quot;Activate&quot; on her eligible Global Administrator assignment at 14:00:00 on a Tuesday.&lt;/p&gt;
&lt;h3&gt;The activation flow, step by step&lt;/h3&gt;
&lt;p&gt;Six things happen, in order, and each writes audit-log evidence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The eligible assignment already exists.&lt;/strong&gt; Alice has been a permanent-eligible Global Administrator since she was hired. The PIM directory object records principal &lt;code&gt;alice@contoso.com&lt;/code&gt;, role &lt;code&gt;Global Administrator&lt;/code&gt;, scope &lt;code&gt;directory&lt;/code&gt;, &lt;code&gt;type=eligible&lt;/code&gt;, &lt;code&gt;duration=permanent&lt;/code&gt;. Today she holds zero of the role&apos;s permissions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The activation request lands on PIM.&lt;/strong&gt; Alice clicks Activate in the Entra admin centre, or fires the equivalent Microsoft Graph call. PIM pulls the activation policy for &lt;code&gt;(role=Global Administrator, scope=directory)&lt;/code&gt; and prepares to evaluate the gates [@ms-learn-pim-change-default-settings].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The policy gates evaluate.&lt;/strong&gt; This is the load-bearing part, and the place readers most often misread the docs. The gates are per-role configurable, not universal. Microsoft Learn documents five gates the tenant can independently switch on or off [@ms-learn-pim-change-default-settings]:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multifactor authentication at activation&lt;/strong&gt; if &lt;code&gt;requires_mfa&lt;/code&gt; is set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Approval routing&lt;/strong&gt; to named approvers or an approver group if &lt;code&gt;requires_approval&lt;/code&gt; is set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Justification text capture&lt;/strong&gt; if &lt;code&gt;requires_justification&lt;/code&gt; is set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ticket number capture&lt;/strong&gt;, optionally tagged with a ticketing-system identifier, if &lt;code&gt;requires_ticket&lt;/code&gt; is set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Activation duration validation&lt;/strong&gt; against the per-role configurable maximum -- one to twenty-four hours, with one hour the default for the highest-privileged Entra roles such as Global Administrator and Privileged Role Administrator [@ms-learn-pim-change-default-settings].&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PIM materializes the active assignment.&lt;/strong&gt; Microsoft Learn states the latency directly: &lt;em&gt;&quot;Microsoft Entra PIM creates active assignment (assigns user to a role) within seconds&quot;&lt;/em&gt; [@ms-learn-pim-activate]. A new token Alice obtains after this moment will carry the activated role&apos;s claims.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The PIM audit log records the entire transaction.&lt;/strong&gt; A new entry captures the request, the approver&apos;s decision and decision time, the justification text, the ticket reference, the activation start, and the planned expiry. The audit log is retained for thirty days by default and can be routed to Azure Monitor for longer retention [@ms-learn-pim-audit-log].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-deactivation fires at the duration boundary.&lt;/strong&gt; At 15:00:00 -- one hour after activation -- PIM deactivates the assignment within seconds [@ms-learn-pim-activate]. Alice can also call &lt;code&gt;deactivate()&lt;/code&gt; explicitly to return early.&lt;/li&gt;
&lt;/ol&gt;

sequenceDiagram
    autonumber
    participant User as alice
    participant PIM
    participant MFA
    participant Approver as bob
    participant Graph as Microsoft Graph
    participant Audit as PIM audit log
    User-&amp;gt;&amp;gt;PIM: Activate Global Administrator
    PIM-&amp;gt;&amp;gt;MFA: Require MFA challenge
    MFA--&amp;gt;&amp;gt;PIM: MFA passed
    PIM-&amp;gt;&amp;gt;Approver: Route approval request
    Approver--&amp;gt;&amp;gt;PIM: Approve with justification context
    PIM-&amp;gt;&amp;gt;Graph: Materialize active assignment within seconds
    PIM-&amp;gt;&amp;gt;Audit: Write request, decision, materialization records
    Note over PIM,Audit: Token issued with activated role claims
    Note over PIM,Graph: One-hour TTL begins
    PIM-&amp;gt;&amp;gt;Graph: Auto-deactivate at expiry within seconds
    PIM-&amp;gt;&amp;gt;Audit: Write deactivation record
&lt;h3&gt;Activation policies are configured, not assumed&lt;/h3&gt;
&lt;p&gt;Two of the most common misunderstandings the documentation receives are about this configurability. First, MFA at activation is not universally required by PIM. The role&apos;s activation policy must be set to require it. Second, the activation maximum is configurable per role per scope inside a one-to-twenty-four-hour range, with the default for Global Administrator and Privileged Role Administrator at one hour [@ms-learn-pim-change-default-settings]. A &quot;PIM tenant&quot; where one role requires MFA and approval and another role requires only justification text is a perfectly valid configuration; both roles are PIM-gated, but their gate sets differ.&lt;/p&gt;

A per-role-per-scope configuration of which gates an activation must satisfy: MFA at activation, approval, justification, ticket number, and the activation maximum duration. PIM evaluates the policy at activation time. The gates are independent flags; any combination can be required [@ms-learn-pim-change-default-settings].
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; PIM&apos;s activation maximum duration is configurable per role per scope in the one-to-twenty-four-hour range. The default value for the highest-privileged Entra directory roles -- Global Administrator and Privileged Role Administrator -- is one hour [@ms-learn-pim-change-default-settings]. Other roles default to higher values. Tighten the duration where you can; the activation cost is small, the standing-active surface saving is large.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Authentication context: gating activation, not sign-in&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/inside-the-primary-refresh-token-the-cryptographic-seam-betw/&quot; rel=&quot;noopener&quot;&gt;Conditional Access&lt;/a&gt; has gated sign-in since 2014. Until 2023, it had no way to gate the activation event itself. The integration between PIM and Conditional Access changes that by attaching an &lt;strong&gt;authentication context&lt;/strong&gt; label to the activation, which Conditional Access can target the same way it targets any other authentication. Microsoft Learn includes the activation policy option &lt;em&gt;&quot;On activation, require Microsoft Entra Conditional Access authentication context&quot;&lt;/em&gt; [@ms-learn-pim-change-default-settings].&lt;/p&gt;

A label that PIM attaches to the activation event so that Conditional Access policies can target the activation itself, not just the sign-in. Policies such as &quot;activation of Global Administrator requires a compliant device and an MFA challenge issued within the last five minutes&quot; become expressible without bolting on a third-party stack [@ms-learn-pim-change-default-settings].
&lt;h3&gt;The activation gate, as code&lt;/h3&gt;
&lt;p&gt;To make the gate-composition idea concrete, here is the activation policy as a small JavaScript function. Edit the policy or the request and re-run it.&lt;/p&gt;
&lt;p&gt;{`
function activate(request, policy) {
  // policy gates are independent; any combination can be required
  if (policy.requires_mfa &amp;amp;&amp;amp; !request.mfa_passed) {
    return { ok: false, reason: &apos;MFA challenge failed or absent&apos; };
  }
  if (policy.requires_approval &amp;amp;&amp;amp; !request.approval_decision) {
    return { ok: false, reason: &apos;Approval pending&apos; };
  }
  if (policy.requires_justification &amp;amp;&amp;amp; !request.justification) {
    return { ok: false, reason: &apos;Justification text missing&apos; };
  }
  if (policy.requires_ticket &amp;amp;&amp;amp; !request.ticket_number) {
    return { ok: false, reason: &apos;Ticket number missing&apos; };
  }
  if (request.duration_hours &amp;gt; policy.max_duration_hours) {
    return { ok: false, reason: &apos;Requested duration exceeds policy maximum&apos; };
  }
  // activation succeeds: materialize a time-bound active assignment
  const expires_at = new Date(Date.now() + request.duration_hours * 3600 * 1000);
  return {
    ok: true,
    active_assignment: {
      principal: request.principal,
      role: request.role,
      scope: request.scope,
      type: &apos;active&apos;,
      duration: { kind: &apos;time-bound&apos;, start: new Date(), end: expires_at }
    }
  };
}&lt;/p&gt;
&lt;p&gt;const policy = {
  requires_mfa: true, requires_approval: true,
  requires_justification: true, requires_ticket: true,
  max_duration_hours: 1
};
const request = {
  principal: &apos;&lt;a href=&quot;mailto:alice@contoso.com&quot; rel=&quot;noopener&quot;&gt;alice@contoso.com&lt;/a&gt;&apos;, role: &apos;Global Administrator&apos;, scope: &apos;directory&apos;,
  mfa_passed: true, approval_decision: &apos;approve&apos;,
  justification: &apos;MSRC-2026-PIM-12345&apos;, ticket_number: &apos;SNOW-INC-987654&apos;,
  duration_hours: 1
};
console.log(activate(request, policy));
`}&lt;/p&gt;
&lt;p&gt;The function is mechanical and short for a reason. Every PIM gate is independently expressible, the policy is a record, the request is a record, and the active-assignment output is itself a record the system can audit. The complexity of PIM, such as it is, lives in the surrounding infrastructure -- the directory, the audit log, Conditional Access, the alert engine -- not in the gate itself.&lt;/p&gt;
&lt;h3&gt;The Azure-resource five-minute floor&lt;/h3&gt;
&lt;p&gt;One operational detail belongs here.Azure resource role assignments under PIM-for-Azure-Resources carry an additional latency floor: an Azure resource role assignment cannot be made for a duration of less than five minutes and cannot be removed within five minutes of being created [@ms-learn-pim-resource-roles]. This is the rare place where the cloud control plane exposes a hard minimum-time bound in its assignment-state machine, and it shapes the lower limit of any tightening strategy on Azure RBAC scopes.&lt;/p&gt;
&lt;p&gt;Activation is the per-event control. But what about the standing posture across the tenant -- the eligibility surface, the drift you did not notice, the assignment configuration in places PIM does not reach by default? For that, you need access reviews, and you need to push the eligible/active primitive beyond the original twenty-eight built-in directory roles.&lt;/p&gt;
&lt;h2&gt;6. Beyond Directory Roles: Extending Eligible and Active Across Four Boundaries&lt;/h2&gt;
&lt;p&gt;PIM at GA in September 2016 covered roughly twenty-eight built-in Entra directory roles. Everything else -- Azure RBAC, security groups, partner-tenant delegation, the Conditional Access activation event -- was still single-state and permanent-active. The next nine years of PIM history are the story of closing those four boundaries, one at a time.&lt;/p&gt;

flowchart TD
    Core[&quot;Two-state assignment object, 2016&quot;]
    Core --&amp;gt; Azure[&quot;PIM for Azure Resources, 2017-2019, RBAC at four scopes&quot;]
    Core --&amp;gt; Groups[&quot;PIM for Groups, GA October 2023, membership and ownership&quot;]
    Core --&amp;gt; Partner[&quot;GDAP May 2022 plus Azure Lighthouse eligible authorizations&quot;]
    Core --&amp;gt; CA[&quot;PIM with Conditional Access authentication context, GA October 2023&quot;]
&lt;h3&gt;Boundary 1: PIM for Azure Resources&lt;/h3&gt;
&lt;p&gt;Between 2017 and 2019, Microsoft extended the eligible-versus-active model from Entra directory roles to Azure RBAC. The extension covers four scopes -- management group, subscription, resource group, and individual resource -- and supports both built-in roles (Owner, Contributor, User Access Administrator, and the security roles) and custom roles [@ms-learn-pim-resource-roles].&lt;/p&gt;
&lt;p&gt;The non-obvious operational property of PIM-for-Azure-Resources is that &lt;strong&gt;role settings do not inherit down the RBAC hierarchy&lt;/strong&gt;. A policy you tighten on Owner at the management-group scope does not automatically flow down to Owner on subscriptions, resource groups, or resources beneath it. Each (role, scope) pair is its own policy slot, and each must be configured.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Configure activation policies per role per scope explicitly across the management-group, subscription, resource-group, and resource hierarchy. A tightening at the management-group scope does not flow to subscriptions beneath it. The most common operational defect in mature PIM tenants is the unconfigured policy at a downstream scope, leaving a wide-open activation surface under what looked like a hardened parent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Boundary 2: PIM for Groups&lt;/h3&gt;
&lt;p&gt;The PIM-for-Groups timeline is three distinct events. In August 2020, Microsoft previewed the feature under its original name, &quot;Privileged Access Groups,&quot; and limited the preview scope to role-assignable security groups [@simons-2020-aug]. In January 2023, Microsoft renamed the feature to &quot;Privileged Identity Management for Groups&quot; in the Entra admin centre; the underlying eligible/active model was unchanged [@ms-learn-pim-for-groups]. In October 2023, more than three years after the preview, PIM for Groups reached general availability with a broader scope -- role-assignable security groups (carried forward), non-role-assignable security groups (newly supported), and Microsoft 365 groups (newly supported), with JIT for both membership and ownership [@ms-techcommunity-pim-groups-ca-ga-2023], [@ms-learn-pim-for-groups], [@ms-learn-pim-groups-role-settings].The three events span more than three years and should not be conflated. August 2020: preview of &quot;Privileged Access Groups,&quot; role-assignable security groups only [@simons-2020-aug]. January 2023: rename to &quot;PIM for Groups&quot;; same scope and model [@ms-learn-pim-for-groups]. October 2023: general availability with the broader scope (non-role-assignable security groups plus M365 groups), and JIT for both membership and ownership [@ms-techcommunity-pim-groups-ca-ga-2023]. Two structural exclusions persist throughout: dynamic-membership groups and groups synchronized from on-premises Active Directory [@ms-learn-pim-for-groups]. The scope is broad: any Entra security group and any Microsoft 365 group, except dynamic-membership groups and on-premises-synced groups, can be PIM-enabled [@ms-learn-pim-for-groups].&lt;/p&gt;
&lt;p&gt;The interesting design choice is that PIM-for-Groups gates &lt;strong&gt;two distinct surfaces per group&lt;/strong&gt;: membership and ownership. The two surfaces each get their own activation policy [@ms-learn-pim-groups-role-settings].&lt;/p&gt;

The extension of PIM eligible/active assignment to Entra security groups and Microsoft 365 groups. Originally previewed in August 2020 as &quot;Privileged Access Groups&quot; (role-assignable security groups only) [@simons-2020-aug]; renamed to &quot;PIM for Groups&quot; in January 2023 [@ms-learn-pim-for-groups]; reached general availability in October 2023 with the broader scope (role-assignable security groups, non-role-assignable security groups, and M365 groups), with JIT for both membership and ownership [@ms-techcommunity-pim-groups-ca-ga-2023]. Excludes dynamic-membership groups and groups synchronized from on-premises environments [@ms-learn-pim-for-groups], [@ms-learn-pim-groups-role-settings].

A group owner can add members. A privileged access group whose membership is PIM-gated but whose ownership is permanent-active offers an unmediated elevation path: a compromised owner adds themselves as a member, bypassing the membership gate they would have had to activate. PIM-for-Groups gates both surfaces because gating membership without gating ownership is a one-bypass-step elevation. The two policies are independent; both must be set.
&lt;h3&gt;Boundary 3: Partner tenants -- GDAP and Azure Lighthouse&lt;/h3&gt;
&lt;p&gt;Until 2022, the Microsoft partner channel -- Cloud Solution Providers and Managed Service Providers -- worked through a model called &lt;strong&gt;Delegated Admin Privileges (DAP)&lt;/strong&gt;, in which the partner held standing Global Administrator on every customer tenant they touched. The Nobelium supply-chain attack tradition of 2020-2021 made the structural risk of that posture unignorable [@cisa-aa20-352a]: one compromise of one partner credential meant Global Administrator across hundreds or thousands of customer tenants simultaneously.&lt;/p&gt;
&lt;p&gt;In May 2022, Microsoft introduced &lt;strong&gt;Granular Delegated Admin Privileges (GDAP)&lt;/strong&gt; [@ms-learn-gdap], [@crayon-gdap]. GDAP replaces the standing-GA pattern with time-bound (one to seven-hundred-thirty days) and role-scoped delegation between partner and customer tenants. Microsoft Learn&apos;s framing makes the design explicit: &lt;em&gt;&quot;GDAP is a security feature that provides partners with least-privileged access following the Zero Trust cybersecurity protocol. It lets partners configure granular and time-bound access to their customers&apos; workloads in production and sandbox environments. Customers must explicitly grant the least-privileged access to their partners&quot;&lt;/em&gt; [@ms-learn-gdap].&lt;/p&gt;

The May 2022 Microsoft Partner Center capability that replaces legacy DAP&apos;s standing-Global-Administrator-on-every-customer-tenant pattern with time-bound (one to seven-hundred-thirty days) and role-scoped delegation between partner and customer tenants. GDAP is the partner-tenant analogue of PIM eligible assignment [@ms-learn-gdap].
&lt;p&gt;The Azure plane has a parallel construct. &lt;strong&gt;Azure Lighthouse eligible authorizations&lt;/strong&gt;, introduced alongside GDAP, extend PIM-for-Azure-Resources eligibility across the tenant boundary [@ms-learn-lighthouse-eligible]. The customer (not the partner) controls the PIM policy on the delegated authorization. One important exception: service principals cannot use eligible authorizations, because there is currently no way for a service principal to elevate its access [@ms-learn-lighthouse-eligible]. The application-identity gap we reach in section 9 reaches into Lighthouse too.&lt;/p&gt;
&lt;h3&gt;Boundary 4: PIM and Conditional Access authentication context&lt;/h3&gt;
&lt;p&gt;The October 2023 GA wave closed the activation-gate-versus-sign-in-gate gap. Before October 2023, Conditional Access could gate sign-in into the tenant, but it could not gate the activation event itself. After October 2023, an authentication-context-tagged Conditional Access policy can target activation specifically [@ms-techcommunity-pim-groups-ca-ga-2023]. A policy of the form &lt;em&gt;&quot;activation of any control-plane role requires a compliant device and a fresh MFA challenge&quot;&lt;/em&gt; becomes expressible without third-party tooling [@ms-learn-pim-change-default-settings].&lt;/p&gt;
&lt;h3&gt;The retirement of Tier-0, Tier-1, Tier-2&lt;/h3&gt;
&lt;p&gt;The umbrella framing has also shifted. Microsoft&apos;s 2014 Tier-0 / Tier-1 / Tier-2 model is being progressively retired in favour of the &lt;strong&gt;Enterprise Access Model (EAM)&lt;/strong&gt;, which uses control plane, management plane, and data/workload plane as the structural divisions [@ms-learn-eam]. EAM is cloud-native where Tier-0/1/2 was on-premises-centric. Microsoft Learn states the mapping: &lt;em&gt;&quot;Tier 0 expands to become the control plane and addresses all aspects of access control&quot;&lt;/em&gt;, and &lt;em&gt;&quot;what was tier 1 is now split into the following areas: Management plane ... Data/Workload plane&quot;&lt;/em&gt; [@ms-learn-eam].&lt;/p&gt;

The post-2021 Microsoft reference architecture that replaces the Tier-0/Tier-1/Tier-2 administrative model with a plane-based division: control plane, management plane, and data/workload plane. EAM is cloud-native and zero-trust-friendly where Tier-0/1/2 was on-premises-centric [@ms-learn-eam]. Microsoft&apos;s RaMP -- the Rapid Modernization Plan -- is the post-2018 deployment roadmap that operationalizes EAM [@ms-docs-github-ramp].
&lt;p&gt;The retirement is partial. The practitioner audience still uses Tier-0/1/2 more often than EAM in day-to-day language. The Microsoft Learn page for Securing Privileged Access explicitly cross-references both [@ms-learn-spa-overview].&lt;/p&gt;
&lt;p&gt;Coverage is one half of the story. The other half is detection. What does PIM do when someone in the Privileged Role Administrator role simply assigns Global Administrator to a user directly through Microsoft Graph, bypassing the activation workflow entirely?&lt;/p&gt;
&lt;h2&gt;7. The Detection Layer: Six PIM Alerts and the Assignment-Bypass Class&lt;/h2&gt;
&lt;p&gt;PIM gates activation. The first question every adversary thinks of, and every architect should think of next, is: what about the assignment itself? What happens when someone in the Privileged Role Administrator role just creates a permanent-active Global Administrator assignment directly, skipping the eligible-to-active workflow entirely?&lt;/p&gt;
&lt;p&gt;The answer is the article&apos;s second aha moment, and it is deliberately surprising.&lt;/p&gt;
&lt;h3&gt;The six PIM Alerts&lt;/h3&gt;
&lt;p&gt;Microsoft Learn documents seven named alerts in the PIM Alerts surface for Microsoft Entra roles [@ms-learn-pim-alerts]. Six of them are behavioural detections; the seventh is a licensing-precondition alert that fires when the tenant lacks the appropriate license.The seventh alert, named &quot;The organization doesn&apos;t have Microsoft Entra ID P2 or Microsoft Entra ID Governance,&quot; is a low-severity licensing-precondition alert. The &quot;six PIM Alerts&quot; framing in this article refers to the six behavioural alerts; the licensing alert is structurally distinct. The six behavioural alerts, with the canonical names verbatim from the documentation, are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Alert (verbatim)&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;What it detects&lt;/th&gt;
&lt;th&gt;Configurable threshold&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;There are too many Global Administrators&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Tenant exceeds a tunable count and percentage of standing GAs&lt;/td&gt;
&lt;td&gt;Minimum count 2-100 and percentage 0-100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Roles are being assigned outside of Privileged Identity Management&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A privileged role assignment was created via Microsoft Graph or the classic admin centre without going through PIM&lt;/td&gt;
&lt;td&gt;None (binary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Roles are being activated too frequently&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Post-hoc activation-frequency anomaly&lt;/td&gt;
&lt;td&gt;Activation count and time window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Administrators aren&apos;t using their privileged roles&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Staleness on activation; eligible assignment unused&lt;/td&gt;
&lt;td&gt;0-100 day threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Roles don&apos;t require multifactor authentication for activation&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Configuration drift on the per-role activation policy&lt;/td&gt;
&lt;td&gt;None (binary on role policy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Potential stale accounts in a privileged role&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Sign-in staleness on a privileged principal&lt;/td&gt;
&lt;td&gt;1-365 day threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The third row -- &quot;Roles are being assigned outside of Privileged Identity Management&quot; -- is the load-bearing one. Microsoft Learn rates it &lt;strong&gt;High&lt;/strong&gt; severity because it is the alert that fires when somebody routed around PIM entirely [@ms-learn-pim-alerts]. The verbatim documentation reads: &lt;em&gt;&quot;Privileged role assignments made outside of Privileged Identity Management aren&apos;t properly monitored and might indicate an active attack&quot;&lt;/em&gt; [@ms-learn-pim-alerts].&lt;/p&gt;

The High-severity PIM Alert &quot;Roles are being assigned outside of Privileged Identity Management.&quot; It fires when a privileged role is assigned via a path other than PIM -- typically via Microsoft Graph, the classic admin centre assignment surface, or PowerShell. The alert is detective. It fires after the assignment is created [@ms-learn-pim-alerts].
&lt;h3&gt;Detective, not preventive -- and why&lt;/h3&gt;
&lt;p&gt;Read the definition again. The alert fires &lt;em&gt;after&lt;/em&gt; the assignment is created. PIM does not block direct assignments outside its workflow.&lt;/p&gt;
&lt;p&gt;For most architects this lands hard. The reasonable next thought is &quot;if PIM does not block the bypass, what is the point?&quot; Sit with that thought, then read the design rationale.&lt;/p&gt;
&lt;p&gt;The Microsoft Graph endpoints that allow direct role assignment are the integration surface every legitimate administrative tool uses. Identity Governance products use them. CI/CD identity provisioning scripts use them. Break-glass automations use them. Microsoft&apos;s own admin centres use them in some configurations. The customer-side tools that scan, audit, remediate, and provision against the tenant use them. A preventive block on direct assignment would break every one of those integrations. It would also break PIM itself; the eligible-to-active materialization step is a write to the same assignment surface.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; PIM does not block direct role assignments outside its workflow because blocking would break the Microsoft Graph integration surface every legitimate administrative tool uses. The High-severity assignment-bypass alert is detective: it fires after the assignment is created. Customers who need preventive blocking layer a separate Conditional Access policy on the Graph endpoint, an Azure Policy at the management-group scope, or an entitlement-management workflow on top of PIM.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is Aha #2. The reader who walked in expecting PIM to be a &quot;deny direct assignments&quot; product walks out understanding why the design says &quot;alert loudly via High severity, then let the customer layer preventive controls based on their tooling estate.&quot; The trade-off is named, not hidden.&lt;/p&gt;
&lt;h3&gt;The 1000-notification ceiling and the SIEM-side correlation&lt;/h3&gt;
&lt;p&gt;One operational footnote and one wider observation. The notification fan-out has a hard cap: &lt;em&gt;&quot;The maximum number of notifications sent per one event is 1000. If the number of recipients exceeds 1000, only the first 1000 recipients will receive an email notification&quot;&lt;/em&gt; [@ms-learn-pim-alerts]. Very large tenants whose privileged groups exceed the cap should not rely on email-notification fan-out alone.The detection layer beyond PIM Alerts is Microsoft Sentinel UEBA, which builds dynamic behavioural profiles for users, hosts, IP addresses, applications, and other entities and emits anomaly scores against &lt;code&gt;AuditLogs&lt;/code&gt; operations including role-eligibility additions and activations [@ms-learn-sentinel-ueba]. Sentinel UEBA is the closest 2026 Microsoft-shipped activation-anomaly-scoring surface; it is detective SIEM correlation, not synchronous gating.&lt;/p&gt;
&lt;p&gt;The wider observation is that the PIM detection layer is one piece of a larger pipeline. PIM Alerts give you the High-severity assignment-bypass detection. Microsoft Sentinel UEBA gives you per-user behavioural-anomaly scoring against the audit-log events [@ms-learn-sentinel-ueba]. Entra ID Protection gives you sign-in-risk and user-risk classifications for the principal whose token was used. The mature 2026 deployment correlates all three; the assignment-bypass alert is the floor of that pipeline, not the ceiling.&lt;/p&gt;
&lt;p&gt;Microsoft solved the JIT-admin problem with a two-state assignment object, four extension surfaces, and a six-alert detection layer. Did the rest of the industry agree? Look at what AWS and Google bet on, and at the third-party vault market that predates both.&lt;/p&gt;
&lt;h2&gt;8. Competing Architectures: AWS Sessions, GCP Bindings, and the Vault Model&lt;/h2&gt;
&lt;p&gt;Microsoft bet on a two-state assignment object. The rest of the industry placed different bets.&lt;/p&gt;
&lt;p&gt;AWS bet on the session credential. Google bet on the conditional binding. The third-party PAM market bet on the vault. HashiCorp bet on the ephemeral credential. Each architecture is a different answer to one question: &lt;em&gt;what should be the bounded unit of privilege?&lt;/em&gt; PIM bounds the assignment state; AWS bounds the session; GCP bounds the binding; CyberArk and Vault bound the credential. The methods are architecturally distinct, and they coexist in real estates more often than they compete.&lt;/p&gt;
&lt;h3&gt;AWS: bound the session&lt;/h3&gt;
&lt;p&gt;AWS IAM Identity Center plus the Security Token Service &lt;code&gt;AssumeRole&lt;/code&gt; API bound the &lt;em&gt;session&lt;/em&gt;, not the &lt;em&gt;assignment&lt;/em&gt;. Permanent role-bindings -- permission sets attached to identities -- are themselves standing. The temporary part is the session that materializes when the identity calls &lt;code&gt;AssumeRole&lt;/code&gt;. AWS documents this directly: &lt;em&gt;&quot;Temporary security credentials are short-term, as the name implies. They can be configured to last for anywhere from a few minutes to several hours. After the credentials expire, AWS no longer recognizes them or allows any kind of access from API requests made with them&quot;&lt;/em&gt; [@aws-temp-creds].&lt;/p&gt;
&lt;p&gt;The session lifecycle is concrete. &lt;code&gt;AssumeRole&lt;/code&gt; returns an access key, a secret key, and a session token, with a minimum fifteen-minute and a maximum twelve-hour session duration; the API operation default is one hour [@aws-roles-use]. IAM Identity Center permission sets ship with a one-hour default and a one-to-twelve-hour configurable range [@aws-sessionduration].&lt;/p&gt;

The AWS Security Token Service API by which a principal materializes a time-bounded session credential -- access key, secret key, session token -- from a permanent role-binding. The session is the ephemeral artifact; the binding is permanent [@aws-temp-creds], [@aws-roles-use].
&lt;p&gt;The AWS approach has clear strengths in multi-account AWS Organizations and in programmatic access. It is also the natural fit for any workload that needs short-lived credentials. The gaps relative to PIM: no built-in approval workflow, no equivalent of the PIM Alerts surface, and no eligible-versus-active distinction on the role-binding itself. A standing AssumeRole grant is, structurally, standing privilege; what is bounded is the session that consumes it.&lt;/p&gt;
&lt;h3&gt;Google Cloud: bound the binding&lt;/h3&gt;
&lt;p&gt;Google Cloud IAM took a different route. &lt;strong&gt;IAM Conditional Bindings&lt;/strong&gt; let an allow policy include a Common Expression Language predicate that is evaluated at request time. The canonical temporal pattern is &lt;code&gt;request.time &amp;lt; timestamp(...)&lt;/code&gt;, which expires the binding at a wall-clock instant [@gcp-conditions]. There is a practical ceiling of one hundred conditional bindings per allow policy.&lt;/p&gt;
&lt;p&gt;On top of conditional bindings, Google launched &lt;strong&gt;Privileged Access Manager (PAM)&lt;/strong&gt; in public preview in May 2024 [@gcp-iam-release-notes], [@gcp-pam]. PAM adds the entitlement-and-grant workflow that PIM ships natively: eligible principals, eligible roles, max duration, justification, approvers, and notifications, with grant duration enforced by the underlying conditional binding revocation. Audit-event correlation is documented in a separate page [@gcp-pam-audit].&lt;/p&gt;

A Google Cloud IAM role binding that includes a Common Expression Language predicate evaluated at request time. The most common temporal pattern, `request.time &amp;lt; timestamp(...)`, expires the binding at a wall-clock instant; Google Cloud Privileged Access Manager layers an entitlement-and-grant workflow on top [@gcp-conditions], [@gcp-pam].
&lt;p&gt;The GCP approach is the closest hyperscaler analogue to PIM&apos;s eligible/active model in architecture, but the PAM productization shipped in preview in May 2024 [@gcp-iam-release-notes] -- nearly a decade after Azure AD PIM&apos;s 2016 GA -- and the alert and detection surfaces are correspondingly less mature.&lt;/p&gt;
&lt;h3&gt;The third-party vault: CyberArk, BeyondTrust, Delinea&lt;/h3&gt;
&lt;p&gt;The longest-standing answer is the one the third-party PAM market built. CyberArk, BeyondTrust, and Delinea -- all three 2024 Gartner Magic Quadrant Leaders for Privileged Access Management [@cyberark-press-2024], [@beyondtrust-press-2024], [@delinea-press-2024] -- bound the &lt;em&gt;credential&lt;/em&gt;, not the assignment or the session. The credential exists permanently in the vault; access to the credential is bounded by session brokering, periodic password rotation, and full session recording.&lt;/p&gt;
&lt;p&gt;The vault model has structural strengths PIM&apos;s role-assignment-state model cannot match. The vault covers heterogeneous estates that include Windows, Linux, network devices, databases, mainframes, and OT/SCADA appliances -- every system whose credentials cannot be re-architected to a cloud-IAM eligible-active object. Vault-and-broker products provide session recording for SOX and PCI-DSS evidence collection, and they integrate with credential-rotation workflows for legacy vendor appliances whose hard-coded credentials cannot be eliminated.&lt;/p&gt;
&lt;p&gt;Most large enterprises run &lt;em&gt;both&lt;/em&gt; Entra PIM (for Entra and Azure role assignments) and a third-party PAM product (for SSH, on-premises service accounts, database passwords, network devices). The two markets are complements more than substitutes.&lt;/p&gt;
&lt;h3&gt;HashiCorp Vault and OpenBao: bound the credential&apos;s lifetime&lt;/h3&gt;
&lt;p&gt;HashiCorp Vault took the credential-bounded idea and made it ephemeral through &lt;strong&gt;dynamic secrets&lt;/strong&gt;: a credential materialized on demand by Vault for a configured backend (a database, a cloud IAM, a PKI), returned with a lease and TTL, and revoked at the backend when the lease expires [@vault-databases]. The OpenBao fork, governed under the Linux Foundation, preserves the same dynamic-credential semantics [@openbao].OpenBao was created in late 2023 after HashiCorp moved Vault from the open-source MPL to the Business Source License. The Linux Foundation announced on April 30, 2024 that OpenBao would join &lt;strong&gt;LF Edge&lt;/strong&gt; as one of four new projects (alongside EdgeLake, InfiniEdgeAI, and InstantX) at the Open Networking and Edge (ONE) Summit [@lfedge-openbao-2024]. The dynamic-secret primitive -- &quot;create a credential, hand it out, revoke it at lease expiry&quot; -- is preserved on both code lines.&lt;/p&gt;

A credential materialized by Vault on demand for a configured backend -- database, cloud IAM, or PKI -- returned with a lease ID and TTL; at lease expiry Vault revokes the credential at the backend. The canonical 2026 open-source primitive for replacing hard-coded application credentials [@vault-databases].
&lt;p&gt;The Vault story matters for our purposes because it is the strongest 2026 coverage of the &lt;em&gt;application-identity surface&lt;/em&gt; -- dynamic database credentials, Kubernetes service-account tokens, cloud-IAM short-lived credentials. PIM does not cover that surface today; Vault does. This previews the open boundary in section 9.&lt;/p&gt;
&lt;h3&gt;What is bound, in one comparison table&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;What is bound&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Default duration&lt;/th&gt;
&lt;th&gt;Approval workflow&lt;/th&gt;
&lt;th&gt;Detection layer&lt;/th&gt;
&lt;th&gt;Partner tenant&lt;/th&gt;
&lt;th&gt;Application identities&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Entra PIM&lt;/td&gt;
&lt;td&gt;Assignment state&lt;/td&gt;
&lt;td&gt;eligible -&amp;gt; active transition with policy gates&lt;/td&gt;
&lt;td&gt;1h (Global Admin)&lt;/td&gt;
&lt;td&gt;Built-in approver routing&lt;/td&gt;
&lt;td&gt;Six behavioural PIM Alerts plus Sentinel UEBA&lt;/td&gt;
&lt;td&gt;GDAP + Lighthouse&lt;/td&gt;
&lt;td&gt;Not yet (open boundary)&lt;/td&gt;
&lt;td&gt;Entra ID P2 or Entra ID Governance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS IAM Identity Center + STS&lt;/td&gt;
&lt;td&gt;Session credential&lt;/td&gt;
&lt;td&gt;AssumeRole returns access/secret/session token&lt;/td&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;td&gt;Not built-in&lt;/td&gt;
&lt;td&gt;Not equivalent to PIM Alerts&lt;/td&gt;
&lt;td&gt;Not directly comparable&lt;/td&gt;
&lt;td&gt;Strong (short-lived creds native)&lt;/td&gt;
&lt;td&gt;Included in AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GCP IAM + PAM&lt;/td&gt;
&lt;td&gt;Policy binding&lt;/td&gt;
&lt;td&gt;CEL predicate plus entitlement-and-grant&lt;/td&gt;
&lt;td&gt;Per entitlement&lt;/td&gt;
&lt;td&gt;Built-in via PAM&lt;/td&gt;
&lt;td&gt;Audit events plus Cloud Audit Logs&lt;/td&gt;
&lt;td&gt;Cross-org via folders&lt;/td&gt;
&lt;td&gt;Service-account impersonation&lt;/td&gt;
&lt;td&gt;Included in GCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CyberArk/BeyondTrust/Delinea&lt;/td&gt;
&lt;td&gt;Credential knowledge&lt;/td&gt;
&lt;td&gt;Vault stores, broker hands out, rotates&lt;/td&gt;
&lt;td&gt;Per session policy&lt;/td&gt;
&lt;td&gt;Built-in approver routing&lt;/td&gt;
&lt;td&gt;Session recording, full SIEM integration&lt;/td&gt;
&lt;td&gt;Per-tenant deployment&lt;/td&gt;
&lt;td&gt;Coverage via shared accounts&lt;/td&gt;
&lt;td&gt;Per-seat commercial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HashiCorp Vault / OpenBao&lt;/td&gt;
&lt;td&gt;Credential lifetime&lt;/td&gt;
&lt;td&gt;Lease-based revocation, dynamic secrets&lt;/td&gt;
&lt;td&gt;Per backend, per lease&lt;/td&gt;
&lt;td&gt;Optional plugins&lt;/td&gt;
&lt;td&gt;Audit log; lease events&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Strong (dynamic secrets)&lt;/td&gt;
&lt;td&gt;Open source / commercial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The five methods occupy four positions on the &quot;what is bound&quot; axis: &lt;strong&gt;assignment-state&lt;/strong&gt; (PIM), &lt;strong&gt;session-credential&lt;/strong&gt; (AWS), &lt;strong&gt;policy-binding&lt;/strong&gt; (GCP), and &lt;strong&gt;knowledge-of-credential&lt;/strong&gt; (CyberArk and Vault). The methods are architecturally distinct, and the right enterprise answer in heterogeneous estates is some composition of more than one.&lt;/p&gt;
&lt;p&gt;PIM is the most mature JIT-admin product in the cloud, and it has the most complete coverage of the user-principal surface. The remaining gaps are not about catching up to the competitors; they are about a class of identity the eligible/active model was never designed to gate.&lt;/p&gt;
&lt;h2&gt;9. What the JIT-Admin Pattern Does NOT Close&lt;/h2&gt;
&lt;p&gt;For all the architectural elegance of the two-state assignment object, PIM does not close the JIT-admin problem. It closes a sub-problem, very well, and leaves five structural limits an honest treatment must name.&lt;/p&gt;
&lt;h3&gt;9.1 Standing eligibility is itself standing privilege&lt;/h3&gt;
&lt;p&gt;PIM bounds the &lt;em&gt;active&lt;/em&gt; duration. It does not bound the &lt;em&gt;eligibility&lt;/em&gt; duration. A user with a permanent-eligible Global Administrator assignment is one &lt;code&gt;activate()&lt;/code&gt; call away from the role&apos;s permissions for the next hour. If that user has been phished -- credential plus MFA bypass via a session-cookie capture, say -- the attacker can satisfy the gates. The MFA challenge passes. The justification text is whatever the attacker types. The approval, if required, routes to the legitimate approver, who may approve a legitimate-looking request that actually came from the attacker.&lt;/p&gt;
&lt;p&gt;PIM produces an audit-log record of every step. It does not produce a structural impossibility. Eligibility is itself a security-critical property of the identity, and standing eligibility is the modern analogue of standing membership: a long-lived relationship between principal and role that a successful credential compromise can exercise.&lt;/p&gt;
&lt;h3&gt;9.2 Approver collusion&lt;/h3&gt;
&lt;p&gt;The approval gate is two-phishee resistant only when the requester and approver are independently compromisable. Two-phishee collusion -- the requester and the approver are the same adversary, or two adversaries cooperating -- defeats the workflow at the mechanism layer. The usual mitigations raise the bar: named approvers rather than approver groups (which can be compromised at the group level), CA-gated approval actions, and four-eyes alternatives. None close the class.&lt;/p&gt;
&lt;h3&gt;9.3 The application-identity gap&lt;/h3&gt;
&lt;p&gt;This is the article&apos;s heaviest limit, and it deserves the most space.&lt;/p&gt;
&lt;p&gt;PIM&apos;s eligible-active state machine is currently defined over &lt;code&gt;principal in (user | group)&lt;/code&gt;. Service principals, managed identities, and OAuth consent grants do not flow through PIM activation. Their role assignments are permanent and active by default, and there is no eligible category that applies to them. Microsoft Learn&apos;s documentation for Workload ID Premium and Conditional Access for workload identities makes this explicit: ID Protection workload-identity risk detections cover service principals in single-tenant, non-Microsoft SaaS, and multitenant apps, but &lt;em&gt;&quot;Managed Identities aren&apos;t currently in scope&quot;&lt;/em&gt; [@ms-learn-workload-identity-risk]. Conditional Access for workload identities applies similarly only to service principals owned by the organization, and CA policies &lt;em&gt;&quot;assigned to a group that contains a service principal are not enforced for that service principal&quot;&lt;/em&gt; [@ms-learn-ca-workload-identity].&lt;/p&gt;
&lt;p&gt;Andy Robbins&apos;s three-part &lt;em&gt;Managed Identity Attack Paths&lt;/em&gt; series, published June 6-8, 2022 on the SpecterOps blog, is the canonical demonstration of how this gap is exploited [@robbins-mip-part1], [@robbins-mip-part2], [@robbins-mip-part3]. The mechanism is direct. An Azure compute resource -- an Automation Account [@robbins-mip-part1], a Logic App [@robbins-mip-part2], or a Function App [@robbins-mip-part3] -- carries an attached managed identity. The managed identity holds standing role assignments at whatever scope the operator granted, often Owner or Contributor on a subscription.&lt;/p&gt;
&lt;p&gt;From inside the resource, any code can fetch an OAuth access token for the managed identity by calling the Azure Instance Metadata Service endpoint at &lt;code&gt;http://169.254.169.254/metadata/identity/oauth2/token&lt;/code&gt;. No human in the loop. No MFA challenge. No PIM activation. The audit log records a service-principal token issuance, not an alice-clicked-Activate event.&lt;/p&gt;

Managed Identity assignments are an extremely effective security control... But Managed Identities introduce a new problem: they can quickly create identity-based attack paths in Azure that may lead to escalation of privilege opportunities. -- Andy Robbins, *Managed Identity Attack Paths, Part 1: Automation Accounts*, June 6, 2022 [@robbins-mip-part1]

An Azure-managed service principal whose credentials are issued and rotated by Azure itself. The underlying Azure resource (a VM, App Service, Function App, Logic App, AKS cluster) retrieves the OAuth access token via the Instance Metadata Service endpoint. Managed identities are not currently in scope for PIM activation; their role assignments are permanent and active [@ms-learn-managed-identities-overview].

The Azure Instance Metadata Service endpoint at `http://169.254.169.254/metadata/identity/oauth2/token`, a link-local non-routable address reachable only from inside the Azure resource itself, that returns an OAuth 2.0 access token for the attached managed identity. The address is the credential: any process running on the resource can fetch the token without storing or presenting any secret.

sequenceDiagram
    autonumber
    participant Attacker
    participant FunctionApp as Compromised Function App
    participant IMDS as IMDS endpoint 169.254.169.254
    participant ARM as Azure Resource Manager
    participant PIMUnused as PIM activation (unused)
    Attacker-&amp;gt;&amp;gt;FunctionApp: Code execution via supply-chain or vuln
    FunctionApp-&amp;gt;&amp;gt;IMDS: GET /metadata/identity/oauth2/token
    IMDS--&amp;gt;&amp;gt;FunctionApp: OAuth access token for managed identity
    FunctionApp-&amp;gt;&amp;gt;ARM: Action as Owner on subscription
    ARM--&amp;gt;&amp;gt;FunctionApp: Action succeeds
    Note over PIMUnused,Attacker: No human, no MFA, no activation, no PIM audit
&lt;p&gt;MITRE ATT&amp;amp;CK maps the class explicitly. &lt;strong&gt;T1078.004 -- Valid Accounts: Cloud Accounts&lt;/strong&gt; cites Robbins&apos;s Part 1 as primary reference for the managed-identity case [@mitre-t1078-004]. The page reads: &lt;em&gt;&quot;In Azure environments, adversaries may target Azure Managed Identities, which allow associated Azure resources to request access tokens. By compromising a resource with an attached Managed Identity, such as an Azure VM, adversaries may be able to Steal Application Access Tokens to move laterally across the cloud environment&quot;&lt;/em&gt; [@mitre-t1078-004].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;T1548.005 -- Temporary Elevated Cloud Access&lt;/strong&gt; explicitly names PIM as an instance of the JIT-access pattern adversaries abuse: &lt;em&gt;&quot;Many cloud environments allow administrators to grant user or service accounts permission to request just-in-time access to roles... Just-in-time access is a mechanism for granting additional roles to cloud accounts in a granular, temporary manner&quot;&lt;/em&gt; [@mitre-t1548-005].&lt;/p&gt;

T1548.005 (Temporary Elevated Cloud Access) lists Microsoft&apos;s *Approve just-in-time access requests* documentation as citation [1] of the technique, recognizing PIM as a canonical implementation of the JIT-access pattern adversaries abuse [@mitre-t1548-005]. Being named in the ATT&amp;amp;CK framework is, in the security domain, the most explicit acknowledgement an adversary model can give a defensive product.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Three anchors to walk away with: Andy Robbins&apos;s June 2022 &lt;em&gt;Managed Identity Attack Paths&lt;/em&gt; series [@robbins-mip-part1], [@robbins-mip-part2], [@robbins-mip-part3]; MITRE ATT&amp;amp;CK T1078.004 citing Robbins as primary [@mitre-t1078-004]; the IMDS endpoint at &lt;code&gt;169.254.169.254&lt;/code&gt; as the technical mechanism [@ms-learn-managed-identities-overview]. If your tenant has any managed identity with Owner or User Access Administrator at a subscription scope, you have an unmediated bypass path around PIM until that role assignment is tightened.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;9.4 The assignment-bypass is detective, not preventive&lt;/h3&gt;
&lt;p&gt;The High-severity assignment-bypass alert documented in §7 is detective by design (see Aha #2). The structural limit it leaves open is that preventive blocking is not the PIM product&apos;s default: customers who want it layer a Conditional Access policy on the Microsoft Graph endpoint or an Azure Policy at the management-group scope [@ms-learn-azure-policy], accepting that some legitimate Graph integration may need an exception.&lt;/p&gt;
&lt;h3&gt;9.5 Customer-owned PIM policy in CSP and Lighthouse scenarios&lt;/h3&gt;
&lt;p&gt;In the partner-managed case, the customer (not the partner) controls the PIM policy on a delegated authorization [@ms-learn-lighthouse-eligible]. This is the right place to put control, but it is also the place misconfiguration is most common. A customer whose Lighthouse eligible authorization is set with permissive activation policies (no MFA, no approval, large maximum duration) has an unmediated partner activation surface, and the partner cannot tighten the customer-side policy. The MSP-managed case is the operational gotcha most frequently raised at PIM-deployment review boards.&lt;/p&gt;
&lt;h3&gt;Aha #3: The gap is a data-model problem, not a patchable defect&lt;/h3&gt;
&lt;p&gt;This is the third aha moment, and it lands differently from the first two.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The application-identity gap is not a backlog item. Extending the eligible-active state machine from &lt;code&gt;principal in (user | group)&lt;/code&gt; to &lt;code&gt;principal in (user | group | service principal | managed identity | OAuth consent grant)&lt;/code&gt; is a data-model extension that would require changes to the role-assignment object schema, the Microsoft Graph role-management endpoints, the PIM evaluation pipeline, the audit-log schema, the Sentinel detection schema, and every downstream IGA tool. The 2024+ Microsoft responses extend some controls to application identities. They do not yet introduce an eligible/active assignment-category type for application principals.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Microsoft has shipped partial responses. &lt;strong&gt;Entra Workload ID Premium&lt;/strong&gt; [@ms-entra-workload-id-product] is a separate three-dollar-per-workload-identity-per-month SKU [@ms-entra-workload-id-product] that unlocks &lt;strong&gt;Conditional Access for workload identities&lt;/strong&gt; [@ms-learn-ca-workload-identity] (with the explicit managed-identity exclusion clause) and &lt;strong&gt;ID Protection workload-identity risk detections&lt;/strong&gt; [@ms-learn-workload-identity-risk]. The PIM page on access reviews documents that &lt;em&gt;&quot;Using Access Reviews for Service Principals requires a Microsoft Entra Workload ID Premium plan in addition to a Microsoft Entra ID P2 or Microsoft Entra ID Governance license&quot;&lt;/em&gt; [@ms-learn-pim-access-reviews]. Microsoft&apos;s flagship Ignite 2025 announcement was &lt;strong&gt;Microsoft Entra Agent ID&lt;/strong&gt; for AI agents [@ms-entra-ignite-2025]; the announcement is identity for AI workloads, not an eligible-active type extension for service-principal role assignments.&lt;/p&gt;
&lt;p&gt;Robbins&apos;s class is closed-form within the 2026 PIM architecture. Closing it requires a new architecture, not a patch.&lt;/p&gt;
&lt;p&gt;None of these limits is a defect. Each is a deliberate design boundary, and naming them is the academic honesty the topic deserves. The interesting question: where is active research happening, and what would closing the gap actually look like?&lt;/p&gt;
&lt;h2&gt;10. Open Problems: Where Active Research Is Happening&lt;/h2&gt;
&lt;p&gt;The five limits in section 9 are settled architectural boundaries. The open problems are different. Each is something nobody has shipped a complete solution to as of 2026, but each has named partial results and named anchors.&lt;/p&gt;
&lt;h3&gt;10.1 JIT-gating application identities&lt;/h3&gt;
&lt;p&gt;The data-model extension previewed in section 9&apos;s Aha #3 is the largest open problem in this space, and the one Microsoft is responding to most publicly.&lt;/p&gt;
&lt;p&gt;What has been tried. &lt;strong&gt;Entra Workload ID Premium&lt;/strong&gt; at three dollars per workload identity per month [@ms-entra-workload-id-product]. &lt;strong&gt;Conditional Access for workload identities&lt;/strong&gt;, which lets the tenant block service-principal sign-ins based on IP range, ID-Protection risk score, or authentication context [@ms-learn-ca-workload-identity]. &lt;strong&gt;ID Protection workload-identity risk detections&lt;/strong&gt; that flag suspicious sign-ins, leaked credentials, and admin-confirmed compromise for service principals [@ms-learn-workload-identity-risk]. &lt;strong&gt;Service-principal access reviews&lt;/strong&gt;, gated behind Workload ID Premium plus Entra ID P2 or Governance [@ms-learn-pim-access-reviews]. &lt;strong&gt;Microsoft Entra Agent ID&lt;/strong&gt;, the flagship Ignite 2025 announcement, brings first-class identity to AI agents [@ms-entra-ignite-2025] -- parallel to, but not the same as, an eligible-active type extension on application role assignments.&lt;/p&gt;

An identity used by a software workload to authenticate to other services. In Microsoft Entra ID the term encompasses application objects, service principals, and managed identities [@ms-learn-workload-identities-overview]. As of 2026, workload identities are not in scope of the eligible/active assignment-category model. The 2024+ Workload ID Premium SKU extends sign-in-time controls and risk detection to service principals, but does not yet introduce an eligible category for service-principal role assignments.
&lt;p&gt;What is the conjecture? Closing this gap requires extending the role-assignment object&apos;s &lt;code&gt;principal&lt;/code&gt; axis to include service principals, managed identities, and OAuth consent grants as first-class subjects of the eligible-active state machine. That extension would require a defined &lt;code&gt;activate()&lt;/code&gt; semantics for non-human principals -- itself the hard problem, because the canonical user activation flow assumes an interactive MFA challenge.&lt;/p&gt;
&lt;p&gt;Microsoft Learn states the difficulty bluntly: workload identities &lt;em&gt;&quot;can&apos;t perform multifactor authentication. Often have no formal lifecycle process. Need to store their credentials or secrets somewhere&quot;&lt;/em&gt; [@ms-learn-workload-identities-overview]. The non-interactive case requires either programmatic policy gates (request from this caller, from this IP range, against this entitlement) or a delegation model where a human approver supplies the gate-passing event on the workload&apos;s behalf.&lt;/p&gt;
&lt;h3&gt;10.2 Real-time activation-anomaly blocking&lt;/h3&gt;
&lt;p&gt;The PIM Alert &quot;Roles are being activated too frequently&quot; is post-hoc. It fires after the activation has already occurred and after the count crosses a threshold. The phished-but-still-authentic activation -- the attacker who supplies a valid MFA, a plausible justification, and a real ticket number -- is observationally indistinguishable from a legitimate emergency activation at the mechanism layer. The only signal that distinguishes them must come from behavioural telemetry.&lt;/p&gt;
&lt;p&gt;What has been tried. &lt;strong&gt;Microsoft Defender for Cloud Apps&lt;/strong&gt; ships an out-of-the-box user-and-entity behavioural analytics (UEBA) and machine-learning anomaly-detection layer; the documented policy weighs more than thirty risk indicators across eight risk-factor groups (risky IP, login failures, admin activity, inactive accounts, location, impossible travel, device and user agent, activity rate), with a seven-day initial learning period and a June 2025 transition to a dynamic threat-detection model [@ms-learn-dfca-anomaly]. &lt;strong&gt;Microsoft Sentinel UEBA&lt;/strong&gt; scores anomalies post-event against &lt;code&gt;AuditLogs&lt;/code&gt; operations including role-eligibility additions and activations [@ms-learn-sentinel-ueba]. &lt;strong&gt;Microsoft Defender for Identity&lt;/strong&gt; correlates on-premises and cloud sign-in patterns for behavioural-anomaly detection. Neither Sentinel UEBA nor Defender for Cloud Apps is a synchronous gate. Both are detective layers that fire after the activation event has already created consequences.&lt;/p&gt;
&lt;p&gt;The academic upper bound for what character-level and LSTM detectors achieve on adjacent tasks comes from Hendler, Kels, and Rubin&apos;s 2019 work on AMSI-based detection of malicious PowerShell code, which reports a true-positive rate of nearly 90% at a false-positive rate of less than 0.1% on the PowerShell-misuse classification problem [@arxiv-hendler-1905]. That is the ceiling a probabilistic activation-anomaly classifier could approach. It is not enough to gate synchronously without false-positive operational pain, which is why the deployed surface is post-hoc UEBA scoring rather than pre-commit blocking.&lt;/p&gt;
&lt;p&gt;The conjecture. Synchronous gating on behavioural signal at activation time would require Conditional Access (or its successor) to subscribe to an activation-event hook and consume a risk score from ID Protection, Defender for Cloud Apps, or Sentinel UEBA in the few hundred milliseconds before PIM materializes the active assignment. The architectural primitives exist; the synchronous risk-evaluation hook does not yet ship.&lt;/p&gt;
&lt;h3&gt;10.3 Hybrid-bridge JIT&lt;/h3&gt;
&lt;p&gt;A single approval workflow spanning the on-premises (MIM PAM / shadow principals) and cloud (Entra PIM) boundaries is not a shipping product. Microsoft has &lt;strong&gt;Entra Cloud Sync&lt;/strong&gt; and &lt;strong&gt;Entra Connect&lt;/strong&gt; for directory synchronization; neither bridges the &lt;em&gt;activation workflow&lt;/em&gt;. MIM 2016 is on extended support through January 9, 2029 [@ms-learn-mim-2016]; Microsoft Learn states the path forward is cloud-first PIM with on-prem AD progressively scoped down to the few resources that cannot move [@ms-learn-mim-pam-overview].&lt;/p&gt;

MIM 2016 PAM is in extended support, not active development, and Microsoft Learn explicitly states it is &quot;not recommended for new deployments in Internet-connected environments&quot; [@ms-learn-mim-pam-overview]. SP3 ships compatibility updates for SharePoint SE, Exchange SE, and SQL Server 2022 [@ms-learn-mim-2016], but the product line is in maintenance posture. The on-premises half of a hybrid-bridge JIT story requires a different architectural choice than re-investing in MIM.
&lt;h3&gt;10.4 Coverage-as-code&lt;/h3&gt;
&lt;p&gt;How do you evaluate PIM policy coverage in CI/CD for a tenant with two hundred custom Azure roles and fifty directory roles, and gate every PR that touches the role-management policies?&lt;/p&gt;
&lt;p&gt;Best partial results. &lt;strong&gt;Microsoft Cloud Security Benchmark v3 Privileged Access controls&lt;/strong&gt; (PA-1, PA-2, ...) give Boolean per-recommendation pass/fail evaluation [@ms-learn-mcsb-v3-pa] -- close, but per-recommendation Boolean rather than composable policy. The PowerShell cmdlets &lt;code&gt;Get-MgPolicyRoleManagementPolicy&lt;/code&gt; and &lt;code&gt;Get-MgPolicyRoleManagementPolicyAssignment&lt;/code&gt; read role-management policies via Microsoft Graph; the cmdlets ship in the &lt;code&gt;Microsoft.Graph.Identity.SignIns&lt;/code&gt; module, despite the Identity Governance branding [@ms-learn-graph-pim-policy-cmdlet].The PIM role-management-policy cmdlets are commonly mis-attributed to the &lt;code&gt;Microsoft.Graph.Identity.Governance&lt;/code&gt; PowerShell module because of the Identity Governance branding. They are actually in &lt;code&gt;Microsoft.Graph.Identity.SignIns&lt;/code&gt;. The &lt;code&gt;Import-Module&lt;/code&gt; line that gets the cmdlets into scope is &lt;code&gt;Import-Module Microsoft.Graph.Identity.SignIns&lt;/code&gt; [@ms-learn-graph-pim-policy-cmdlet]. The &lt;strong&gt;EntraOps Privileged EAM&lt;/strong&gt; community project on GitHub, maintained by Thomas Naunheim, demonstrates the &quot;track changes and history of privileged principals and their assignments as code&quot; idiom against the Enterprise Access Model classification [@entraops-github]. Azure Policy itself operates on Azure resource configurations and does not directly evaluate PIM role-management policy state [@ms-learn-azure-policy], which is the data-model gap that drives the GitOps-flavoured drift-detection community pattern.&lt;/p&gt;
&lt;p&gt;{`
// Take an array of role-management policy assignments
// (the kind Get-MgPolicyRoleManagementPolicyAssignment returns)
// and assert tenant-wide PIM coverage invariants.&lt;/p&gt;
&lt;p&gt;function assertPrivilegedRoleCoverage(assignments, privilegedRoles, expected) {
  const findings = [];
  for (const role of privilegedRoles) {
    const a = assignments.find(x =&amp;gt; x.roleDefinitionId === role);
    if (!a) {
      findings.push({ role, severity: &apos;High&apos;, issue: &apos;No PIM policy assignment&apos; });
      continue;
    }
    const p = a.policy;
    if (p.requires_mfa !== expected.requires_mfa)
      findings.push({ role, severity: &apos;High&apos;, issue: &apos;MFA at activation not required&apos; });
    if (p.requires_approval !== expected.requires_approval)
      findings.push({ role, severity: &apos;High&apos;, issue: &apos;Approval not required&apos; });
    if (p.requires_justification !== expected.requires_justification)
      findings.push({ role, severity: &apos;Medium&apos;, issue: &apos;Justification not required&apos; });
    if (p.max_duration_hours &amp;gt; expected.max_duration_hours)
      findings.push({ role, severity: &apos;Medium&apos;,
        issue: &apos;Maximum activation duration exceeds expected value&apos;,
        actual: p.max_duration_hours, expected: expected.max_duration_hours });
  }
  return findings;
}&lt;/p&gt;
&lt;p&gt;const privileged = [&apos;Global Administrator&apos;, &apos;Privileged Role Administrator&apos;,
                    &apos;Security Administrator&apos;, &apos;User Access Administrator&apos;];
const expected = { requires_mfa: true, requires_approval: true,
                   requires_justification: true, max_duration_hours: 1 };
const sample = [{ roleDefinitionId: &apos;Global Administrator&apos;,
  policy: { requires_mfa: true, requires_approval: true,
            requires_justification: true, max_duration_hours: 4 } }];
console.log(assertPrivilegedRoleCoverage(sample, privileged, expected));
`}&lt;/p&gt;
&lt;p&gt;The conjecture. A full coverage-as-code primitive needs Azure Policy (or its successor) to evaluate PIM role-management policy state with the same first-class semantics it applies to Azure resource configuration. That extension would let a tenant declare an invariant -- &quot;every role in the control plane has &lt;code&gt;requires_mfa=true&lt;/code&gt; and &lt;code&gt;max_duration_hours &amp;lt;= 1&lt;/code&gt;&quot; -- and have the platform enforce it continuously across drift, the way Azure Policy already enforces resource invariants.&lt;/p&gt;
&lt;h3&gt;10.5 Adaptive-cadence eligibility reviews&lt;/h3&gt;
&lt;p&gt;Should eligible membership be access-reviewed at higher cadence than active assignments? Eligible membership is standing privilege; active membership is bounded. The argument for adaptive cadence -- reviewing eligibility more frequently when behavioural signals or organizational events suggest the principal may no longer need the role -- is intuitive but mechanically unshipped.&lt;/p&gt;
&lt;p&gt;Best partial result. The 2024+ ML-based access-review recommendations [@ms-learn-review-recommendations] -- inactive-user 30-day Deny, user-to-group-affiliation Deny -- are &lt;em&gt;within-cycle&lt;/em&gt; reviewer-assist features. They help reviewers decide during a configured access review. They are not &lt;em&gt;cross-cycle&lt;/em&gt; adaptive-cadence triggers that fire a new review off-schedule when conditions warrant.&lt;/p&gt;
&lt;p&gt;These are research problems. The practitioner does not have the luxury of waiting for them to be solved. What does Monday morning look like for the architect who has read this far and now has to deploy?&lt;/p&gt;
&lt;h2&gt;11. Practical Guide: Monday Morning for the 2026 Tenant Architect&lt;/h2&gt;
&lt;p&gt;You have read ten thousand words. You are responsible for a Microsoft 365 tenant that audits against SOX, SOC 2, and ISO 27001. You have a budget for Entra ID P2 (or Entra ID Governance) per privileged user. What do you do on Monday?&lt;/p&gt;
&lt;p&gt;Work in this order. The list is ordered by cost-to-impact, with the cheapest, highest-impact items first.&lt;/p&gt;
&lt;h3&gt;Step 1: Baseline the Tier-0 surface&lt;/h3&gt;
&lt;p&gt;Every directory role at &quot;Privileged&quot; classification or above should be PIM-eligible-only. The exceptions are the two emergency-access permanent-active Global Administrator accounts (break-glass), which we return to in Step 4.&lt;/p&gt;
&lt;p&gt;Activation requires MFA, approval, justification, and ticket number for control-plane and management-plane roles. Maximum activation duration is one hour for Global Administrator and Privileged Role Administrator, and four hours for less-privileged roles. Configure per role per scope; remember that PIM-for-Azure-Resources policies do not inherit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;Import-Module Microsoft.Graph.Identity.Governance
Connect-MgGraph -Scopes &apos;RoleManagement.Read.Directory&apos;,&apos;User.Read.All&apos;
$gaRoleId = (Get-MgRoleManagementDirectoryRoleDefinition `
    -Filter &quot;displayName eq &apos;Global Administrator&apos;&quot;).Id
Get-MgRoleManagementDirectoryRoleAssignment `
    -Filter &quot;roleDefinitionId eq &apos;$gaRoleId&apos;&quot; `
    -ExpandProperty Principal |
    Select-Object @{n=&apos;User&apos;;e={$_.Principal.AdditionalProperties.userPrincipalName}}, RoleDefinitionId
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This lists every standing-active Global Administrator in the tenant. Compare against your break-glass roster and your active PIM activations. Anything else is technical debt.&lt;/p&gt;
&lt;h3&gt;Step 2: Configure access reviews&lt;/h3&gt;
&lt;p&gt;Quarterly for Tier-0 and control-plane roles. Semi-annually for Tier-1 and management-plane. Annually for Tier-2 and data/workload-plane [@ms-learn-pim-access-reviews]. Turn on the ML-based review recommendations: the 30-day inactive-user Deny recommendation is the reviewer-assist baseline, and the user-to-group-affiliation Deny recommendation helps reviewers spot principals who are organizationally distant from the rest of the group&apos;s membership [@ms-learn-review-recommendations].&lt;/p&gt;
&lt;h3&gt;Step 3: Turn on every PIM Alert and tune the GA-count threshold&lt;/h3&gt;
&lt;p&gt;Enable all six behavioural PIM Alerts. Tune the &quot;There are too many Global Administrators&quot; alert to a minimum count of two and a percentage of 50% [@ms-learn-pim-alerts]. The expected steady-state count is &quot;fewer than five standing GAs, most of which are break-glass.&quot; The High-severity assignment-bypass alert is non-negotiable; route it to a 24x7 SOC queue with an incident-response runbook.Microsoft Secure Score&apos;s &quot;Limit the number of Global Administrators&quot; recommendation targets fewer than five standing GAs as the canonical baseline.&lt;/p&gt;
&lt;h3&gt;Step 4: Break-glass discipline&lt;/h3&gt;
&lt;p&gt;Two emergency-access permanent-active Global Administrator accounts. Not one, not three.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; One break-glass account is a single point of failure: if it is locked, lost, or compromised, the tenant has no emergency entry path. Three or more begin to expand the blast radius unnecessarily. Two balances the two failure modes. &lt;a href=&quot;https://paragmali.com/blog/webauthn-and-passkeys-on-windows-from-ctap-to-the-credential/&quot; rel=&quot;noopener&quot;&gt;FIDO2 hardware keys&lt;/a&gt;, stored in physical safes, with continuous sign-in alerting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Conditional Access policies can lock you out. Break-glass accounts must be excluded from every CA policy that could prevent their sign-in. Compensate with continuous sign-in alerting on every break-glass authentication event; alerts are the substitute for the gate you are deliberately removing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Step 5: Extend PIM to the four boundaries&lt;/h3&gt;
&lt;p&gt;PIM-for-Groups: gate &lt;strong&gt;ownership&lt;/strong&gt; of every directory-role-assignable group, every privileged-access security group, and every group that grants management-group-level Azure RBAC. Membership alone is insufficient; ownership is a backdoor to membership.&lt;/p&gt;
&lt;p&gt;PIM-for-Azure-Resources: gate Owner, User Access Administrator, and Contributor at the management-group scope, then explicitly at every subscription, every resource group, and every resource where the role is assignable. Inheritance does not flow; configure per scope.&lt;/p&gt;
&lt;p&gt;GDAP and Lighthouse: every CSP partner authorization must be eligible, not active. Set the customer-side PIM policy explicitly. Audit annually.&lt;/p&gt;
&lt;p&gt;PIM with Conditional Access: attach an authentication-context tag to activation policies on the privileged Entra roles. Add a CA policy that requires a compliant device and a fresh MFA challenge on activation. The activation gate becomes structurally tighter than the sign-in gate, which is the correct ordering for high-privilege actions.&lt;/p&gt;
&lt;h3&gt;Step 6: Continuous detection&lt;/h3&gt;
&lt;p&gt;Pipe PIM activation events (via Microsoft Graph audit logs, surfaced in the &lt;code&gt;AuditLogs&lt;/code&gt; and &lt;code&gt;MicrosoftGraphActivityLogs&lt;/code&gt; Azure Monitor tables) to your SIEM. Cross-correlate with Entra ID Protection sign-in risk and Microsoft Sentinel UEBA anomaly signals [@ms-learn-sentinel-ueba]. KQL templates to write: (a) GA activations outside business hours; (b) activations from non-compliant devices; (c) the assignment-bypass alert correlated with the activating principal&apos;s recent sign-in risk score; (d) managed-identity token issuance against subscription-scoped Owner.&lt;/p&gt;
&lt;h3&gt;Step 7: Mind the application-identity surface&lt;/h3&gt;
&lt;p&gt;This is the longest-running open item. Inventory every managed identity in the tenant. For each, document the role assignment, the scope, and the resource that holds it.&lt;/p&gt;
&lt;p&gt;Apply the &quot;Owner and User Access Administrator at subscription scope is dangerous&quot; rule first; tighten those to Contributor or a custom role wherever possible. Where a managed identity must hold a high-privilege role at a high scope, treat the underlying resource (Function App, Logic App, VM, AKS cluster) as a Tier-0 asset for the purposes of patching, network exposure, and code-review process. Until PIM gates application identities natively, the Tier-0-asset framing is the substitute control.&lt;/p&gt;
&lt;p&gt;That is the playbook for the user-principal side of the JIT-admin problem. The application-identity side is still being written. The next iteration of this material will be about the data-model extension that closes Robbins&apos;s gap, or the architectural successor that arrives in its place.&lt;/p&gt;
&lt;h2&gt;12. Frequently Asked Questions and Closing&lt;/h2&gt;
&lt;p&gt;Three classes of question come up every time this material is taught. The first is conceptual (&quot;what does eligible actually mean?&quot;). The second is operational (&quot;do I need MFA?&quot;). The third is adversarial (&quot;what about managed identities?&quot;). Each appears below.&lt;/p&gt;

No. Eligible assignments are permanent in most tenants -- they are the standing relationship between principal and role -- but they grant no privilege until you activate. Only the *active* state is bounded. Your admin rights still exist; they are simply not exercised continuously [@ms-learn-pim-configure].

Only if the role&apos;s activation policy is configured to require it. PIM&apos;s activation gates -- MFA at activation, approval, justification, ticket number, and activation maximum duration -- are per-role, per-scope flags the tenant sets independently. A role with `requires_mfa=false` and `requires_approval=false` is a valid (if loose) PIM configuration [@ms-learn-pim-change-default-settings].

One hour for the highest-privileged Entra directory roles, including Global Administrator and Privileged Role Administrator. The configurable range is one to twenty-four hours per role per scope [@ms-learn-pim-change-default-settings]. Tighten where you can; the activation cost is small, the standing-active surface saving is large.

No. Conditional Access gates the sign-in event. PIM bounds the assignment state. A compromised CA-gated GA still has GA privileges once they sign in -- the gate that mattered (activation) was never traversed. CA and PIM compose; PIM is not a substitute for CA, and CA is not a substitute for PIM.

No. PIM alerts via the High-severity &quot;Roles are being assigned outside of Privileged Identity Management&quot; alert when a direct assignment happens [@ms-learn-pim-alerts]. The detection is intentional rather than preventive: blocking direct assignment would break the Microsoft Graph integration surface every legitimate administrative tool uses. Preventive controls -- Conditional Access on the Graph endpoint, Azure Policy at the management-group scope, or entitlement-management workflows -- are added separately based on the tenant&apos;s tooling estate.

No. PIM&apos;s eligible/active state machine is defined over user and group principals. Service principals, managed identities, and OAuth consent grants route around PIM activation entirely. Andy Robbins&apos;s June 2022 *Managed Identity Attack Paths* series [@robbins-mip-part1], [@robbins-mip-part2], [@robbins-mip-part3] is the canonical demonstration; MITRE ATT&amp;amp;CK T1078.004 [@mitre-t1078-004] cites Robbins as primary reference. Workload ID Premium plus Conditional Access for workload identities extends sign-in-time controls to service principals (with managed identities still excluded), but does not yet introduce an eligible category for workload-identity role assignments [@ms-learn-ca-workload-identity], [@ms-learn-workload-identity-risk].

Microsoft has shifted the framing to the Enterprise Access Model: control plane, management plane, and data/workload plane [@ms-learn-eam]. The retirement of Tier-0/1/2 is partial; the practitioner community still uses the legacy terms day to day. The underlying principle -- privilege boundaries you do not cross with a single credential -- is preserved across both framings.
&lt;h3&gt;Closing&lt;/h3&gt;
&lt;p&gt;Read the section 1 vignette again. The 2026 tenant where &lt;a href=&quot;mailto:alice@contoso.com&quot; rel=&quot;noopener&quot;&gt;alice@contoso.com&lt;/a&gt; is Global Administrator for exactly one hour, with an audit log so complete the SOC 2 auditor signs it without questions, is not a configuration choice. It is the visible behaviour of an identity system whose role-assignment object carries one more field than the 2015 version did. Standing admin did not retire because operators got more disciplined. Standing admin retired because the data model grew a second state.&lt;/p&gt;
&lt;p&gt;The forty years between Saltzer and Schroeder&apos;s 1975 paper and the 2015 Azure AD PIM Preview were not lost time. UNIX &lt;code&gt;sudo&lt;/code&gt;, Kerberos delegation, DACLs, AD groups, MIM PAM, Pass-the-Hash v1 and v2, the Securing Privileged Access roadmap -- each built up the structural understanding that least privilege required a temporal mechanism, not just a static one, and that the temporal mechanism had to live on the assignment object itself, not on the group, the credential, the session, or any indirection through a separate forest. The single new field on the role-assignment object is what those forty years were preparing.&lt;/p&gt;
&lt;p&gt;What remains undone is the application-identity boundary. The same role-assignment object Microsoft retrofitted to gate user activation does not yet gate the managed identity attached to a Function App. The IMDS endpoint at &lt;code&gt;169.254.169.254&lt;/code&gt; is the canonical 2026 bypass path that proves it. Closing that gap, when it comes, will not be a patch to the existing eligible/active state machine. It will be the next chapter -- the one where the state machine learns to apply to a principal that cannot perform an interactive MFA challenge, and the activation semantics are reinvented for the non-interactive case.&lt;/p&gt;
&lt;p&gt;The story is not finished. But the first chapter -- the chapter where standing admin became visibly the anti-pattern it had always been -- is.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;privileged-identity-management&quot; keyTerms={[
  { term: &quot;Standing admin&quot;, definition: &quot;A privileged identity whose role assignment is active and permanent. Standing admin is the deployed-reality default of any pre-PIM tenant and most AD-only environments through 2026.&quot; },
  { term: &quot;Eligible assignment&quot;, definition: &quot;A PIM-managed role assignment that grants no privilege until activated. Eligible is the standing relationship between principal and role; active is the time-bounded materialization.&quot; },
  { term: &quot;Active assignment&quot;, definition: &quot;A PIM-managed role assignment that grants the role&apos;s permissions for the assignment&apos;s duration. Active assignments are permanent (legacy posture) or time-bound (after activate()).&quot; },
  { term: &quot;Activation policy&quot;, definition: &quot;Per-role-per-scope configuration of activation gates: MFA, approval, justification, ticket number, and maximum duration. Gates are independent flags.&quot; },
  { term: &quot;Authentication context (PIM with Conditional Access)&quot;, definition: &quot;A label PIM attaches to the activation event so Conditional Access policies can target activation specifically, not just sign-in.&quot; },
  { term: &quot;Bastion forest&quot;, definition: &quot;A separate Active Directory forest dedicated to housing privileged accounts. The MIM 2016 PAM on-premises pattern; superseded for new deployments by cloud-first Entra PIM.&quot; },
  { term: &quot;Shadow principal&quot;, definition: &quot;An AD object (msDS-ShadowPrincipal, Windows Server 2016) carrying a production-forest SID that the bastion KDC injects into the user&apos;s Kerberos PAC for a TTL.&quot; },
  { term: &quot;Assignment-bypass alert&quot;, definition: &quot;The High-severity PIM Alert &apos;Roles are being assigned outside of Privileged Identity Management.&apos; Fires when a privileged role is assigned directly via Microsoft Graph rather than through PIM activation. Detective, not preventive.&quot; },
  { term: &quot;Enterprise Access Model (EAM)&quot;, definition: &quot;The post-2021 Microsoft reference architecture replacing Tier-0/1/2 with control plane, management plane, and data/workload plane.&quot; },
  { term: &quot;PIM for Groups&quot;, definition: &quot;The 2023 extension of PIM eligible/active assignment to security groups and Microsoft 365 groups. Gates both membership and ownership; excludes dynamic-membership groups and on-premises-synced groups.&quot; },
  { term: &quot;GDAP (Granular Delegated Admin Privileges)&quot;, definition: &quot;The May 2022 Microsoft Partner Center capability that replaces legacy DAP standing-Global-Administrator-on-every-customer-tenant with time-bound, role-scoped delegation between partner and customer tenants.&quot; },
  { term: &quot;Managed identity&quot;, definition: &quot;An Azure-managed service principal whose credentials are issued and rotated by Azure itself. Not currently in scope for PIM activation; role assignments are permanent and active.&quot; },
  { term: &quot;IMDS endpoint&quot;, definition: &quot;The Azure Instance Metadata Service endpoint at &lt;code&gt;http://169.254.169.254/metadata/identity/oauth2/token&lt;/code&gt;, reachable only from inside the Azure resource, that returns an OAuth token for the attached managed identity.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>privileged-identity-management</category><category>entra-id</category><category>just-in-time-admin</category><category>identity-security</category><category>azure</category><category>security-architecture</category><category>zero-trust</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>