<script data-pm-proxy="intercept"></script><?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Control Plane]]></title><description><![CDATA[Exploring the gaps between writing detections and knowing they work.]]></description><link>https://lydiagraslie.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg</url><title>Control Plane</title><link>https://lydiagraslie.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 01 Sep 2026 19:37:11 GMT</lastBuildDate><atom:link href="/__u/lydiagraslie.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Lydia Graslie]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[lydiagraslie@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[lydiagraslie@substack.com]]></itunes:email><itunes:name><![CDATA[Lydia Graslie]]></itunes:name></itunes:owner><itunes:author><![CDATA[Lydia Graslie]]></itunes:author><googleplay:owner><![CDATA[lydiagraslie@substack.com]]></googleplay:owner><googleplay:email><![CDATA[lydiagraslie@substack.com]]></googleplay:email><googleplay:author><![CDATA[Lydia Graslie]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Update on semi-scheduled programming]]></title><description><![CDATA[Doing the new time RAG]]></description><link>https://lydiagraslie.substack.com/p/update-on-semi-scheduled-programming</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/update-on-semi-scheduled-programming</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Sun, 26 Jul 2026 10:54:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Friends I have good news and bad news. </p><p>The bad news is that the remainder of plumbing the depths on service principals is going to be delayed until late August. The good news is that&#8217;s because I&#8217;ve picked up a TA position with SANS for <a href="https://www.sans.org/cyber-security-courses/genai-llm-application-security">SEC545: LLM and AI Application Security</a> and am now spending all my extra waking hours on poring over the class material so that I don&#8217;t look like a dumbo in front of students next month. And honestly I may have found my new favorite thing to yell about, because there&#8217;s some design flaws in AI security that are equal parts fascinating and horrifying as well as some super-cool labs and tools that are worth hours of tinkering. But I&#8217;m not ready to yell about it yet, so please sit tight while I get smart and I will see you in a month. &lt;3</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Too Afraid To Ask, Part 1: What is a Service Principal?]]></title><description><![CDATA[A series on explaining Microsoft concepts that should be simple but aren't.]]></description><link>https://lydiagraslie.substack.com/p/too-afraid-to-ask-part-1-what-is</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/too-afraid-to-ask-part-1-what-is</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Sun, 05 Jul 2026 17:06:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TpDy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve been in tech now for ten years, and nine of those years have involved working with Microsoft cloud in some shape or form. I&#8217;ve administered and architected complex Entra ID tenants, spent thousands of hours managing cloud identities, and can run end-to-end Azure detection tests in minutes. But then I realized something uncomfortable: I had no idea how to explain what a service principal was in a way that fit on a slide.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>&#8220;It&#8217;s an app&#8221; is the version most people give. But in keeping with true Microsoft tradition, it&#8217;s actually not that simple: there&#8217;s a host of connected-but-vital concepts that make a three word explanation unsatisfactory: application objects, enterprise applications, app registrations, client secrets, etc. And if you wanted a really complete explanation there&#8217;s also managed identities (secretly also service principals), agent blueprints, delegated permissions, etc.; all of which are also application-flavored and entirely too many letters for a presentation slide. Clearly, I had homework. </p><p>This post is the first chunk of that homework: what a service principal actually <em>is</em>. Part 2 covers what it&#8217;s allowed to <em>do</em> (permissions and consent, or: where OAuth confusion goes to breed). Part 3 covers why a threat researcher cares (spoiler: the guards are not evenly distributed). By the end of this one, you&#8217;ll have the single slide. I promise it exists.</p><h2>The answer that fits on a slide</h2><p>Here it is: <strong>a service principal is an ID badge for software.</strong></p><p>In Entra ID, everything that wants to do things needs an identity. People get one kind: a user account. Software gets the other kind: a service principal. That&#8217;s the entire conceptual load. When your backup tool, your CI/CD pipeline, or that SaaS integration someone in marketing connected in 2021 needs to authenticate and get a token, it doesn&#8217;t log in as a person. It logs in as a service principal.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!TpDy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!TpDy!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png 424w, /__u/substackcdn.com/image/fetch/$s_!TpDy!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png 848w, /__u/substackcdn.com/image/fetch/$s_!TpDy!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png 1272w, /__u/substackcdn.com/image/fetch/$s_!TpDy!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!TpDy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png" width="1456" height="749" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:749,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:208110,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/205271622?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!TpDy!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png 424w, /__u/substackcdn.com/image/fetch/$s_!TpDy!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png 848w, /__u/substackcdn.com/image/fetch/$s_!TpDy!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png 1272w, /__u/substackcdn.com/image/fetch/$s_!TpDy!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7be79b6-7d5d-4d7b-b1f9-2520f7c5616d_2720x1400.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Both badges work the same way at the door: show up, prove who you are, get a token, use the token to open things. The difference is who&#8217;s wearing it. Alice proves who she is with a password and a phone prompt. The backup tool proves who it is with a secret, certificate, or federated credential. Same building, same tokens, different kind of visitor.</p><p>If a service principal is just a badge, why does Microsoft&#8217;s documentation make it feel like a tax form? Because a service principal is only half of a pair and the docs insist on defining each half in terms of the other. Let&#8217;s break the loop.</p><h2>One blueprint, many badges</h2><p>Here&#8217;s the thing the Microsoft Learn docs never say out loud: the confusing part of this model exists for exactly one reason, and the reason is <strong><a href="https://learn.microsoft.com/en-us//entra/identity-platform/single-and-multi-tenant-apps">multi-tenancy</a></strong>. An app is <em>built once</em> but <em>used in many places</em>. Once you know that, everything downstream starts to click.</p><p>So Microsoft splits every app into two objects:</p><p>The <strong><a href="https://learn.microsoft.com/en-us/entra/identity-platform/how-applications-are-added#what-are-application-objects-and-where-do-they-come-from">application object</a></strong> is the blueprint. It lives in exactly one tenant- whichever tenant the developer registered it in, and it describes what the app <em>is</em>: its name, its redirect URIs, the permissions it might ask for, its credentials. One per app, worldwide.</p><p>The <strong><a href="https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser#service-principal-object">service principal</a></strong> is the badge. It&#8217;s the local copy stamped into each tenant that actually uses the app, and it represents what the app <em>may do there</em>. It also has its <em>own</em> set of credentials, separate from the application object&#8217;s. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!DxTb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!DxTb!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg 424w, /__u/substackcdn.com/image/fetch/$s_!DxTb!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg 848w, /__u/substackcdn.com/image/fetch/$s_!DxTb!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg 1272w, /__u/substackcdn.com/image/fetch/$s_!DxTb!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!DxTb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg" width="1456" height="710" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:710,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1968,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/svg+xml&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/205271622?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!DxTb!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg 424w, /__u/substackcdn.com/image/fetch/$s_!DxTb!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg 848w, /__u/substackcdn.com/image/fetch/$s_!DxTb!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg 1272w, /__u/substackcdn.com/image/fetch/$s_!DxTb!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe75e879-2d42-411c-bffa-9274a5b6cbc7_1600x780.svg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Concrete version: Salesforce registers their app once, in Salesforce&#8217;s own tenant. That registration is the application object. When your company clicks &#8220;yes, connect Salesforce,&#8221; Entra stamps a service principal into <em>your</em> tenant. Your admins can see that badge, grant it permissions, or revoke it: all without ever touching Salesforce&#8217;s blueprint, which was never yours to touch. This is why you can&#8217;t edit Salesforce&#8217;s redirect URIs from your tenant, and why you wouldn&#8217;t want a world where you could.</p><p>One blueprint. Thousands of badges, one per customer building.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Yql7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Yql7!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png 424w, /__u/substackcdn.com/image/fetch/$s_!Yql7!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png 848w, /__u/substackcdn.com/image/fetch/$s_!Yql7!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Yql7!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Yql7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png" width="1456" height="814" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:814,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:217834,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/205271622?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Yql7!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png 424w, /__u/substackcdn.com/image/fetch/$s_!Yql7!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png 848w, /__u/substackcdn.com/image/fetch/$s_!Yql7!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Yql7!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1fcc60-583e-45ba-b1d2-1f2a6955e6b9_2720x1520.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There&#8217;s a tidy asymmetry hiding here that&#8217;s worth thirty seconds: delete the blueprint and your own badge is destroyed with it, but every other customer's badge lingers on, orphaned. Delete your local service principal and you&#8217;ve only evicted the badge from your own building: the blueprint, and everyone else&#8217;s badges, carry on. The blueprint is the app; the badge is the app&#8217;s presence in your tenant.</p><h2>The portal decoder ring</h2><p>Now for the payoff, because this single split explains the most disorienting thing about the Entra portal: why there are two blades that both seem to contain &#8220;apps,&#8221; with names that help nobody.</p><p><strong><a href="https://learn.microsoft.com/en-us/security/zero-trust/develop/app-registration">App registrations</a></strong> shows application objects: blueprints, and only ones born in <em>your</em> tenant. <strong><a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/add-application-portal">Enterprise applications</a></strong> shows service principals: every badge in your building, regardless of where its blueprint lives. The blade full of service principals is the one that does not contain the words &#8220;service principal.&#8221; I don&#8217;t make the rules.</p><p>Three things you&#8217;ll see when you go look (and you should go look):</p><p><strong>Your own app appears in both blades.</strong> Register an app yourself via the portal (it&#8217;s two calls via API) and Entra creates <em>both</em> objects in your tenant: blueprint in App registrations, badge in Enterprise applications. They share a name and an app ID, which is why most people assume the two blades are redundant views of one thing. They&#8217;re not- they&#8217;re two different objects (with separate <a href="https://learn.microsoft.com/en-us/answers/questions/1345429/different-of-object-id-vs-service-principal-object">object ids</a>) that happen to be introduced at the same party.</p><p><strong>Salesforce appears in only one.</strong> You have their badge. You will never have their blueprint. Any third-party SaaS app you&#8217;ve consented to shows up only in Enterprise applications.</p><p><strong>Microsoft&#8217;s own apps are in there too.</strong> First-party Microsoft applications show up in your tenant without anyone registering or consenting to them. Nobody registered them, nobody consented in the way you&#8217;d expect, and some of them hold the most powerful permissions in the building. The Enterprise applications blade politely hides most of them behind a default filter, which is why you may have never scrolled past your own apps and noticed the crowd.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Qkct!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Qkct!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png 424w, /__u/substackcdn.com/image/fetch/$s_!Qkct!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png 848w, /__u/substackcdn.com/image/fetch/$s_!Qkct!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Qkct!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Qkct!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png" width="1456" height="878" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:878,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:282594,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/205271622?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Qkct!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png 424w, /__u/substackcdn.com/image/fetch/$s_!Qkct!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png 848w, /__u/substackcdn.com/image/fetch/$s_!Qkct!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Qkct!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10a6c638-558f-4bd0-aa14-237cf5a3857c_2720x1640.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The slide</h2><p>As promised. If you remember nothing else:</p><blockquote><p><strong>A service principal is the ID badge software wears in your tenant.</strong> The app registration is the blueprint: it says what the app <em>is</em>, and it lives with whoever built the app. The service principal is the badge: it represents the app <em>in your tenant</em>, and it&#8217;s the thing your admins can actually see and control. One blueprint. One badge per tenant that uses the app.</p></blockquote><p>That&#8217;s it. That&#8217;s the slide. It took me ten years to be able to write it, which is either an indictment of the documentation or of me, and I know which way I&#8217;m voting.</p><h2>What didn&#8217;t fit on the slide</h2><p>You may have noticed the slide says nothing about what the badge is allowed to <em>open</em>. That&#8217;s deliberate. Permissions are where this model gets genuinely dangerous to half-understand- there are two fundamentally different kinds, the difference between them is the difference between &#8220;this app reads Alice&#8217;s mail when Alice is using it&#8221; and &#8220;this app reads everyone&#8217;s mail at 3 a.m.,&#8221; and the consent screen does a remarkable job of making them look identical. That&#8217;s Part 2.</p><p>And Part 3 is why I, specifically, spent my evenings on this: the security apparatus your organization built &#8212; MFA, Conditional Access, impossible-travel alerts &#8212; was designed for the badges people wear. The badges software wears go through a different door. We&#8217;ll talk about who&#8217;s guarding it.</p><p><em>If this saved you from the Microsoft Learn circular dependency loop, subscribe: Parts 2 and 3 are coming, and after that we need to talk about what Microsoft just built on top of all this for AI agents. The short version: badges that print badges.</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/lydiagraslie.substack.com/subscribe"><span>Subscribe now</span></a></p><p>Control Plane is an independent publication written in a personal capacity, on personal time and equipment, and doesn't represent my employer. </p>]]></content:encoded></item><item><title><![CDATA[Where Continuous Access Evaluation Stops Being Continuous]]></title><description><![CDATA[Continuous Access Evaluation is one of the better things to happen to token security in Entra ID. But &#8220;near-real-time&#8221; turns out to have a coverage map, and Microsoft documents the edges of it]]></description><link>https://lydiagraslie.substack.com/p/where-continuous-access-evaluation</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/where-continuous-access-evaluation</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Thu, 18 Jun 2026 12:02:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9G_8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Not every identity attack ends in a stolen token, but increasingly many do. Device-code phishing, adversary-in-the-middle proxies, OAuth consent grants: the front ends look nothing alike, yet they tend to converge on the same prize, a legitimate access or refresh token that Entra issued after a sign-in it had no reason to question. Sometimes that sign-in cleared MFA; sometimes the token was lifted straight off a device that already had. The password was rarely the point. And once an attacker is holding the token, most of your preventive controls have already done whatever they were going to do. What&#8217;s left is revocation, and how fast it bites is what decides the incident.</p><p>Answering that question is the whole reason Continuous Access Evaluation exists. CAE is the main control you have for containing token theft, which is reason enough to understand it precisely, holes and all.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>What CAE is</strong></h2><p>Continuous Access Evaluation is a near-real-time enforcement mechanism for Conditional Access and token revocation in Entra ID. It&#8217;s built on an industry standard, the OpenID Continuous Access Evaluation Profile (CAEP). The core idea is a two-way conversation between the token issuer (Entra) and the relying party, meaning the resource provider that actually serves the request, like Exchange Online.</p><p>In the classic OAuth model those two parties stop talking the moment a token is issued. Entra mints it, the resource trusts it until it expires, and nobody reconsiders anything in the meantime. CAE opens a channel in both directions. The resource can tell Entra when something changes on its side, such as a request suddenly arriving from a new IP, and Entra can tell the resource to stop honoring a token it has already issued. For incident response, it&#8217;s that second direction you care about.</p><p>CAE first showed up in preview in 2020 and reached general availability in January 2022; it&#8217;s now switched on by default in every Entra tenant. Licensing is layered: the critical-event half travels with any Entra ID license, but the Conditional Access policy half, along with any tuning of CAE&#8217;s own settings, requires Entra ID Premium P1, and the high-risk-user signal that can trigger a revocation depends on Identity Protection, which is a P2 feature.</p><h2><strong>The problem it&#8217;s trying to solve</strong></h2><p>By default, Entra access tokens are valid for about an hour. Conditional Access only gets re-evaluated at refresh, when the client comes back for a new token: whether the account is still enabled, whether the password is still current, whether the user is still inside an allowed location. Between refreshes the resource simply trusts what it already has.</p><p>That gap is the problem. Disable a compromised account, reset its password, or revoke its sessions, and the change is real in the directory the instant you make it. A token already sitting in an attacker&#8217;s hands, though, can keep working until it expires or the client next refreshes. &#8220;Up to an hour&#8221; of continued access after you&#8217;ve detected and responded is not a rounding error. It can be the difference between containment and a mailbox that keeps exfiltrating while you watch.</p><p>Microsoft&#8217;s first attempt at this was the blunt one: shorten token lifetimes. That hurt performance and reliability without really closing the risk, since you can always get unlucky inside even a short window. CAE goes after the latency directly instead, making revocation event-driven rather than expiry-driven, and it does so without shredding token lifetime.</p><h2><strong>How it works</strong></h2><p>CAE has two halves, and it&#8217;s worth keeping them separate, because they cover different things and carry different prerequisites.</p><p>The first is <strong>critical event evaluation</strong>. Enabled resource providers subscribe to a set of high-severity Entra events and act on them within minutes, independent of any Conditional Access policy, which is why this half works in any tenant at all. The triggering events are an account being disabled or deleted, a password change or reset, MFA being enabled for the user, an administrator explicitly revoking all of a user&#8217;s refresh tokens, or a high-risk determination from Entra ID Protection. These are the &#8220;this user is compromised, cut them off&#8221; signals.</p><p>The second is <strong>Conditional Access policy evaluation</strong>. Exchange Online, SharePoint Online, Teams, and Microsoft Graph can sync certain CA policies (most importantly IP-based location) and enforce them inside the service itself, so a session that wanders outside an allowed network gets caught without waiting for the next token refresh.</p><p>Underneath both sits the <strong>claim challenge</strong>. Before CAE, a client just replayed a cached token until it expired. With CAE, the resource can reject a token that hasn&#8217;t expired yet by returning a 401 with a claim challenge attached. A CAE-capable client understands that response, bypasses its cache, and goes back to Entra with its refresh token, at which point Entra re-evaluates every condition and decides whether to issue a new token or block. All of this depends on client support, and the support is uneven. Outlook and Teams handle the claim challenge across platforms, Office in the browser does not, and the desktop clients land somewhere in between.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!9G_8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!9G_8!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png 424w, /__u/substackcdn.com/image/fetch/$s_!9G_8!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png 848w, /__u/substackcdn.com/image/fetch/$s_!9G_8!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!9G_8!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!9G_8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png" width="1000" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:1000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:89184,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/202001223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!9G_8!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png 424w, /__u/substackcdn.com/image/fetch/$s_!9G_8!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png 848w, /__u/substackcdn.com/image/fetch/$s_!9G_8!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png 1272w, /__u/substackcdn.com/image/fetch/$s_!9G_8!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b28cdb5-6098-46e5-a397-5706c9f77b35_1000x600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The token lifetime is where this gets counterintuitive. In a CAE-aware session the access token is <strong>long-lived, up to 28 hours, rather than the usual one</strong>, and the instinct is to read a longer token as a bigger exposure. In practice it works the other way around. The long token is the reward for being on the CAE path, and it&#8217;s safe precisely because revocation no longer hangs on expiry: it can be killed mid-stream the moment a critical event fires. Clients that <em>aren&#8217;t</em> CAE-capable never get the 28-hour token in the first place. They stay on the default one-hour lifetime and the old refresh-time re-evaluation. A short token you can only kill at refresh is the weaker deal.</p><p>One caveat on &#8220;near-real-time.&#8221; The target for critical events is minutes, but propagation can stretch to about 15 minutes. IP-based location enforcement is the exception; that one is effectively instant.</p><h2><strong>Where it works well</strong></h2><p>For the scenarios it covers, CAE is a real improvement, and that&#8217;s worth saying plainly.</p><p>Disable a user or reset their password and the revocation reaches Exchange, SharePoint, Teams, calendar, and tasks within minutes, rather than at the end of an hour-long token&#8217;s life. Revoke a user&#8217;s sessions during an incident and, on the covered resources, the kill is near-real-time instead of a hopeful wait. Move outside an allowed IP range mid-session and the location policy is enforced inside the service, not at the next refresh. And because critical event evaluation doesn&#8217;t lean on Conditional Access policies at all, those compromise-response signals fire even in tenants that have never built out much of a location-policy estate.</p><p>So if your incident is a compromised account being worked through Outlook or Teams against M365, CAE is doing real work for you. The 28-hour token is part of the reason: it keeps sessions stable for everyone else while still letting you cut the compromised one the instant you pull the trigger.</p><h2><strong>Where it falls apart</strong></h2><p>The catch is that &#8220;near-real-time revocation&#8221; is a property of one specific, narrow path: a CAE-capable client talking to a CAE-enabled resource under the supported conditions. Step off that path and nothing warns you. You quietly fall back to the old model, revocation at the next refresh and roughly an hour of exposure, or to something weaker still. The conditions that knock you off the path are all documented, sitting in the limitations section of Microsoft&#8217;s own CAE docs. That section is a coverage map for defenders. It is also, for anyone holding a stolen token, a directory of the places where revocation runs slow.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!6ei9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!6ei9!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png 424w, /__u/substackcdn.com/image/fetch/$s_!6ei9!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png 848w, /__u/substackcdn.com/image/fetch/$s_!6ei9!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png 1272w, /__u/substackcdn.com/image/fetch/$s_!6ei9!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!6ei9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png" width="1040" height="860" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:860,&quot;width&quot;:1040,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:171270,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/202001223?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!6ei9!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png 424w, /__u/substackcdn.com/image/fetch/$s_!6ei9!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png 848w, /__u/substackcdn.com/image/fetch/$s_!6ei9!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png 1272w, /__u/substackcdn.com/image/fetch/$s_!6ei9!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff1a25f-4c6b-4d97-88d2-43e795191b0c_1040x860.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>The documented edges:</p><ul><li><p><strong>Resource scope.</strong> The implementation centers on Exchange Online, SharePoint Online, and Teams, with Graph handling policy sync. Anything outside that set gets no near-real-time conversation at all; the legacy token-lifetime model applies in full.</p></li><li><p><strong>Guest users.</strong> CAE doesn&#8217;t support guest accounts. Revocation events and IP-based Conditional Access policies are not enforced instantly for them. A compromised B2B identity is a blind spot by design, and most tenants carry more guests, and watch them less closely, than they assume.</p></li><li><p><strong>Policy and group-membership changes.</strong> Changes to CA policies or group membership can take up to a day to reach the resource providers. Microsoft has optimized that toward two hours in some cases, but not all of them. If your containment plan depends on a policy <em>change</em> taking effect, it isn&#8217;t really a containment control. Only an explicit session revocation forces immediate effect.</p></li><li><p><strong>Location condition type.</strong> CAE can only see IP-based named locations. It has no visibility into country/region conditions or the MFA trusted-IPs feature. Build your location policy on those rather than on IP ranges and CAE won&#8217;t enforce the change in real time; Entra drops back to a one-hour token.</p></li><li><p><strong>IP-variation and large location sets.</strong> When Entra sees an allowed egress IP but the resource sees a disallowed one, which happens routinely with proxies, VPN split-tunneling, and SD-WAN, Entra issues a one-hour token that suspends IP checks at the resource. The same fallback kicks in when the IP ranges across your location policies exceed 5,000 entries. Microsoft&#8217;s own guidance is blunt about the workaround here: stuffing nonenumerable egress IPs into trusted locations to dodge this actively weakens your posture.</p></li><li><p><strong>Office on the web and WAM-disabled channels.</strong> Office web apps don&#8217;t support the claim challenge, so they drop to a one-hour token whenever a CA policy is set. Office configured to disable Web Account Manager on the Semi-Annual channel isn&#8217;t CAE-supported at all.</p></li><li><p><strong>Co-authoring and push previews.</strong> Inside a live co-authoring session, access may not actually be revoked until the document or app is closed, or the hour mark arrives. And push-notification message previews aren&#8217;t protected by IP policy.</p></li></ul><p>CAE is sold, fairly, as near-real-time revocation. But the guarantee holds only on the covered path, and the gaps line up uncomfortably well with what a stolen token would want: an identity type it ignores, a resource it can&#8217;t reach, a location model it can&#8217;t read. None of those are exotic &#8212; proxies and guests are the normal state of a real tenant.</p><p>A word on magnitude, since it&#8217;s easy to overstate this. This is not &#8220;tokens live forever.&#8221; On the covered resources the exposure is bounded, roughly the one-hour token window, or up to a day for a policy or group change to take hold. The genuinely open-ended case is resources sitting entirely outside CAE&#8217;s scope, where the old model just applies unchanged. So the thing to hold onto is <em>where revocation runs slow</em>, not <em>how to never get revoked</em> &#8212; accurate, and still uncomfortable enough to plan around.</p><h2><strong>What this means when you&#8217;re responding</strong></h2><p>The practical takeaway is broader than CAE itself. Blocking an attack path and killing an attacker&#8217;s tokens are different actions, and only one of them is containment. A Conditional Access rule that blocks a flow, device code for instance, stops <em>new</em> issuance through that flow and does nothing about tokens already in circulation. The lever that actually cuts an active session is revocation: revoke the user&#8217;s refresh tokens, reset the password. CAE is what makes that lever fast, on the covered path.</p><p>So when you pull it during a token-theft incident, don&#8217;t assume it landed everywhere at once. Ask where the stolen token is actually being used. If it&#8217;s Outlook or Teams against M365, CAE is on your side and the kill is quick. If it&#8217;s a guest identity, a non-Microsoft resource, or a tenant leaning on country/region locations, you&#8217;re back on hour-long latency, and you should plan the response around that gap instead of against the marketing. And for the device-registration and Primary Refresh Token escalations the higher-end token-theft kits chase, revoking the user&#8217;s sessions is necessary but not sufficient; check what devices and tokens got minted along the way before you call it contained.</p><p>Continuous Access Evaluation closes the revocation gap that token theft has always exploited. It just closes it on a map, and the first thing worth doing is reading the edges.</p><div><hr></div><h3><strong>References</strong></h3><ul><li><p>Continuous access evaluation in Microsoft Entra &#8212; <em>Microsoft Learn</em>: <a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-continuous-access-evaluation">https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-continuous-access-evaluation</a></p></li><li><p>Block authentication flows with Conditional Access policy &#8212; <em>Microsoft Learn</em>: <a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-block-authentication-flows">https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-block-authentication-flows</a></p></li><li><p>Authentication flows as a condition in Conditional Access &#8212; <em>Microsoft Learn</em>: <a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-authentication-flows">https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-authentication-flows</a></p></li><li><p>Revoke a user&#8217;s sign-in sessions (Revoke-MgUserSignInSession) &#8212; <em>Microsoft Learn</em>: <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users.actions/revoke-mgusersigninsession">https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users.actions/revoke-mgusersigninsession</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Update on Device Code Phishing Detections]]></title><description><![CDATA[The deviceCode must Flow]]></description><link>https://lydiagraslie.substack.com/p/update-on-device-code-phishing-detections</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/update-on-device-code-phishing-detections</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Thu, 04 Jun 2026 12:57:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>TL;DR</strong></h2><p>The blind spot <a href="/__u/lydiagraslie.substack.com/p/your-device-code-phishing-detections">this post</a> described is <strong>gone</strong>. When I first wrote this in March 2026, a sign-in collected through <strong>Diagnostic Settings &#8594; Log Analytics</strong> had its <code>OriginalTransferMethod</code> stripped to <code>none</code> &#8212; so a refreshed token from a phished device code carried no device-code fingerprint, and most SOCs (who collect this way) were blind to the persistence phase. On a June 2026 re-run, <strong>Diagnostic Settings now preserves </strong><code>OriginalTransferMethod = deviceCodeFlow</code> on the very same refresh events. The detection that used to be impossible on that pipeline is now a one-line query.</p><p>Microsoft hasn&#8217;t documented a change, but the behavior flipped sometime between late May and early June 2026 according to an update by Gr&#233;goire Clermont and Pallavi Sivakumaran- most likely a serialization fix somewhere in the Diagnostic-Settings export path.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>What actually changed</strong></h2><p>Nothing about device code flow itself changed. What changed is what one collection pipeline <em>writes down</em>. Here is the same field, same event type, three months apart:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!gRWz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!gRWz!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png 424w, /__u/substackcdn.com/image/fetch/$s_!gRWz!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png 848w, /__u/substackcdn.com/image/fetch/$s_!gRWz!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png 1272w, /__u/substackcdn.com/image/fetch/$s_!gRWz!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!gRWz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png" width="774" height="189" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:189,&quot;width&quot;:774,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18598,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/200606939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!gRWz!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png 424w, /__u/substackcdn.com/image/fetch/$s_!gRWz!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png 848w, /__u/substackcdn.com/image/fetch/$s_!gRWz!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png 1272w, /__u/substackcdn.com/image/fetch/$s_!gRWz!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ea63bca-707a-43d4-ac0a-ae80f19e594b_774x189.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>The March column is why the original post existed. The June column is why this addendum does.</p><h2><strong>How the detection maps onto the fields</strong></h2><p>Three fields carry the whole story. None of them alone is the detection &#8212; it&#8217;s the <em>combination</em>, and which pipeline preserves which field is the entire point.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!SaT4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!SaT4!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png 424w, /__u/substackcdn.com/image/fetch/$s_!SaT4!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png 848w, /__u/substackcdn.com/image/fetch/$s_!SaT4!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png 1272w, /__u/substackcdn.com/image/fetch/$s_!SaT4!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!SaT4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png" width="834" height="325" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:325,&quot;width&quot;:834,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:32718,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/200606939?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!SaT4!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png 424w, /__u/substackcdn.com/image/fetch/$s_!SaT4!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png 848w, /__u/substackcdn.com/image/fetch/$s_!SaT4!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png 1272w, /__u/substackcdn.com/image/fetch/$s_!SaT4!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e5dac45-62ca-43b9-963e-ff3f57d4bd66_834x325.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Read it as two phases:</p><p><strong>Phase 1 &#8212; the phish (initial device-code auth).</strong> An interactive sign-in that is either flagged <code>AuthenticationProtocol == "deviceCode"</code> or carries <code>OriginalTransferMethod == "deviceCodeFlow"</code>. This was always visible in both pipelines. It was never the blind spot, and a determined attacker minimizes how long it&#8217;s the only signal by moving to refresh tokens fast.</p><p><strong>Phase 2 &#8212; persistence (refresh of a device-code token).</strong> This one is what changed. A <strong>non-interactive</strong> event whose token lineage is device-code. The signature is:</p><pre><code>IsInteractive == false
AND OriginalTransferMethod == "deviceCodeFlow"
AND IncomingTokenType == "refreshToken"</code></pre><p>In March, on the Diagnostic-Settings pipeline, <code>OriginalTransferMethod</code> was <code>none</code>, so this query matched <strong>nothing</strong>. You could see <em>a</em> refresh (<code>IncomingTokenType == refreshToken</code>) but had no field tying it back to a device code &#8212; it looked like any other token refresh. That&#8217;s the blind spot. As of June, <code>OriginalTransferMethod</code> survives, so the full signature is queryable on the exact pipeline most teams collect with.</p><h3><strong>The one asymmetry that remains</strong></h3><p>The pipelines still aren&#8217;t identical. <strong>Graph API beta drops </strong><code>IncomingTokenType</code> (it reads <code>none</code> on the refresh event), while Diagnostic Settings keeps it (<code>refreshToken</code>). So for <em>persistence</em> detection the situation has actually <strong>reversed</strong> from March: Diagnostic Settings is now the <strong>richer</strong> source &#8212; it&#8217;s the only one of the two that carries both <code>OriginalTransferMethod</code> <em>and</em> <code>IncomingTokenType</code> on a refresh event. On the Graph side you can still identify the persistence phase, but via <code>IsInteractive == false</code> + <code>OriginalTransferMethod == "deviceCodeFlow"</code> rather than the token-type field.</p><h2><strong>Detection you can paste (Log Analytics / Diagnostic Settings)</strong></h2><p>Initial device-code auth (the phish moment):</p><pre><code><code>SigninLogs
| where IsInteractive == true
| where AuthenticationProtocol == "deviceCode" or OriginalTransferMethod == "deviceCodeFlow"
| project TimeGenerated, UserPrincipalName, AppDisplayName, AppId, IPAddress,
          CorrelationId, AuthenticationProtocol, OriginalTransferMethod</code></code></pre><p>Persistence phase &#8212; refreshed token from a device-code origin:</p><pre><code>AADNonInteractiveUserSignInLogs
| where OriginalTransferMethod == "deviceCodeFlow"
| where IncomingTokenType == "refreshToken"
| where IsInteractive == false
| project TimeGenerated, UserPrincipalName, AppDisplayName, AppId, IPAddress,
          CorrelationId, OriginalTransferMethod, IncomingTokenType</code></pre><p>If you stood up a detection against the original post that matched only on <code>IncomingTokenType == "refreshToken"</code> as a workaround, you can now tighten it with <code>OriginalTransferMethod == "deviceCodeFlow"</code> and cut the false positives from ordinary token refreshes.</p><h2><strong>What this means if you read the original post</strong></h2><ul><li><p>If you <strong>deployed nothing (or just panicked)</strong> because the post said Diagnostic settings couldn&#8217;t see persistence, you can deploy now. The field is there.</p></li><li><p>If you <strong>switched to Graph API beta</strong> to get <code>OriginalTransferMethod</code> &#8212; you no longer have to, and you&#8217;d actually lose <code>IncomingTokenType</code> by relying on Graph alone for refresh events. Diagnostic settings now carries both.</p></li><li><p>The <strong>threat model is unchanged.</strong> Device-code phishing and refresh-token persistence work exactly as before, but now the detection will actually fire.</p></li></ul><h2><strong>Honesty about scope</strong></h2><p>These events are from one tenant, reproduced across three independent refresh events on two dates (June 1 and June 4, 2026), each compared field-by-field across both pipelines. Microsoft has published no changelog for it, so I can&#8217;t tell you the exact date it changed or guarantee it&#8217;s tenant-uniform or permanent. If you can confirm or refute the same behavior in your tenant, I&#8217;d like to hear it. Treat the closed gap as <strong>observed and reproducible</strong>, not as a vendor guarantee.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Teaching the Cloud]]></title><description><![CDATA[Pedagogy, documentation, and paving the road to hell]]></description><link>https://lydiagraslie.substack.com/p/teaching-the-cloud</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/teaching-the-cloud</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Wed, 20 May 2026 12:02:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past few months I&#8217;ve had at least five people come to me for job advice; whether it&#8217;s an ask for a referral, questions on how I got into the industry, or just a plea for help. And I get it, it is a very rough time to be starting out in cybersecurity. The market for juniors is incredibly weak and oversaturated, especially for generalists. AI is absorbing positions that used to be a way to get your foot in the door. Cloud security is one of the few bright spots that still has high demand and low supply. Naturally I should be able to help. Some guides maybe, or walkthroughs, or something. </p><p>Except&#8230;I can&#8217;t. Not really. And here&#8217;s why: It is incredibly hard to do knowledge transfer on cloud in a way that&#8217;s durable and repeatable. Traditional methods of knowledge sharing that work well in other areas of cyber (like documentation, walkthroughs, etc.) quickly break down in cloud, leading to frustration on the part of the learner and the appearance of incompetence on the part of the teacher. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Here&#8217;s an example.</p><p>About five years ago I was responsible for maintaining a guide to connecting to Microsoft 365 environments via PowerShell for other security researchers. It was several pages long, covered a few different types of PowerShell, had screenshots and lots of links to supporting vendor documentation, and was at the time of writing a pretty good artifact. Over the course of a couple years, however, it ended up causing me way more grief than accolades. </p><p>For one, Microsoft kept making changes to the underlying PowerShell that rendered my login code template useless. They also changed the UI frequently, which meant my screenshots went out of date. Then they would deprecate a method I had written up so I&#8217;d have to change that, and so on. Every quarter or so a colleague would come to me saying that the guide didn&#8217;t work, and I&#8217;d take a day to work through the entire flow and troubleshoot what was wrong, and then another day to update the documentation, take new screenshots, verify the new docs were accurate, and so on. Every time, the guide got a little bit longer, and a little bit more complex, and took me longer to update. </p><p>This happened at least four times in two years. For one document on how to do one thing in Microsoft 365: connect to a tenant via PowerShell. </p><p>I had written it with the goal of being helpful, but nobody ever reached out to me because it was helpful: they reached out to me because something had changed and now the guide was wrong. </p><p>Eventually I gave up trying to keep my guide current and linked directly to the Microsoft documentation. It wasn&#8217;t as helpful for our use case (people got stuck on how to install and import the required modules) but it was current, and saved me from having to spend what was now almost a week-long process of keeping my own walkthrough up to date as well as the ire of my colleagues when Microsoft changed something that broke the documentation. And this was five years ago. </p><p>Multiply this upkeep cost by the few hundred walkthroughs you&#8217;d need to provide a foundational grasp of cloud security, and the logistics almost immediately become overwhelming. If it was hard to keep one walkthrough up to date, even a dozen would be a full time job to maintain. Static reference materials: cheatsheets, syntax guides, architectural maps, etc.,  age slower than step-by-step walkthroughs, because they describe the shape of the thing rather than the path through it. Those artifacts are genuinely useful, but they're not pedagogy on their own. They work as scaffolding for someone already doing the hands-on work, not as a substitute for building the muscle in the first place.</p><p>The irony is that what makes anyone good at cloud isn&#8217;t &#8220;knowing cloud&#8221;- it&#8217;s the skills that come along with hands on experience: knowing how to troubleshoot, see the big picture, and answer your own questions. Troubleshooting is what enabled me to work through the walkthrough and find what was wrong, then update it- not any implicit knowledge about what had changed.</p><p>And where did I learn how to troubleshoot?</p><p>From working on a helpdesk. Yes, that helpdesk- the one where frustrated people call in with computer problems not listed on any exam and you have to help solve them in real time while keeping the user (and yourself) as calm as possible. It&#8217;s hard. It&#8217;s stressful. It&#8217;s unglamorous. It is also incredibly valuable experience, and a huge reason why I&#8217;ve done as well as I have in my career. Helpdesk teaches durable, hands-on skills that are immune to any documentation or vendor change. Working under pressure with incomplete information. Diagnostic reasoning when the docs are wrong or missing. The discipline of <em>"what changed since this worked yesterday&#8221;. </em>Independence from any guide or walkthrough.<em> </em>That lack of dependency is huge in a job market like this, where tools and platforms change every quarter.</p><p>But &#8220;go work on a helpdesk&#8221; is tough to hear when you&#8217;ve got your heart set on being a cyber engineer and don&#8217;t have the money to spend on intensive courses that will get you similar experience. And so people ask for the walkthrough. The guide. The trick. The honest answer: there isn&#8217;t one. Yes, helpdesk and entry level IT roles are rarer than they used to be. The MSP I got my start at has since been acquired, and I honestly currently don&#8217;t know of any entry-level IT roles I could even refer to. But I feel a lot better about telling people to pursue that path than I do about writing up guides that are going to break in three months and be unmaintainable in six. One sets you up for lasting success, the other just sets you up period. </p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Nine Seconds]]></title><description><![CDATA[Why I Don't Build Autonomous AI Tooling]]></description><link>https://lydiagraslie.substack.com/p/nine-seconds</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/nine-seconds</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Fri, 08 May 2026 12:02:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the things I love about my job is the ability to build security tooling using AI. At this point I&#8217;ve built about four fully fledged programs that I use regularly to help scope, verify, and ship detection logic, and it&#8217;s been incredibly gratifying to take the software engineering basics I had coming into this position and extend them into a skill set that produces actual purple-teaming software with predictable results. Teasing out the abstraction layers and surfaces between various Microsoft APIs used to take weeks, it now takes hours. Pulling log sources into a usable format was something I used to dread, now it&#8217;s done in seconds. Love AI. Use it daily.</p><p>One thing I will not do for the foreseeable future is use AI to create purple-teaming tools that are fully AI-powered and autonomous. There are very good reasons why: AI is fast, it can easily be weaponized, and its outputs and scope are still unpredictable. All of my development work is done in tenants that are scoped explicitly for research. Even then, I still don&#8217;t let my agents run for extended periods because of how quickly things can go wrong.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I love AI because it&#8217;s fast, and that&#8217;s exactly what makes it dangerous. The same capabilities I use to stand up and tear down VMs in a test environment in seconds are ones that could destroy a prod environment in the same amount of time. This destruction is no longer theoretical as <a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/claude-powered-ai-coding-agent-deletes-entire-company-database-in-9-seconds-backups-zapped-after-cursor-tool-powered-by-anthropics-claude-goes-rogue">PocketOS recently learned</a> when a Claude-powered Cursor agent deleted a vital storage volume in a production environment.</p><div class="twitter-embed" data-attrs="{&quot;url&quot;:&quot;https://x.com/lifeof_jer/status/2048103471019434248&quot;,&quot;full_text&quot;:&quot;https://t.co/ofucbVgkLV&quot;,&quot;username&quot;:&quot;lifeof_jer&quot;,&quot;name&quot;:&quot;JER&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/2049564616091729920/yVL_nRkA_normal.jpg&quot;,&quot;date&quot;:&quot;2026-04-25T18:14:54.000Z&quot;,&quot;photos&quot;:[],&quot;quoted_tweet&quot;:{},&quot;reply_count&quot;:1042,&quot;retweet_count&quot;:1090,&quot;like_count&quot;:5234,&quot;impression_count&quot;:7066818,&quot;expanded_url&quot;:null,&quot;video_url&quot;:null,&quot;video_preview_media_key&quot;:null,&quot;belowTheFold&quot;:false}" data-component-name="Twitter2ToDOM"></div><p>It took nine seconds and one API call. Jer Crane, founder of PocketOS, described the following:</p><p>&#8220;The agent was working on a routine task in our staging environment. It encountered a credential mismatch and decided &#8212; entirely on its own initiative &#8212; to &#8220;fix&#8221; the problem by deleting a Railway volume. To execute the deletion, the agent went looking for an API token. <strong>It found one in a file completely unrelated to the task it was working on.</strong> [&#8230;] Railway&#8217;s token-creation flow gave us no warning &#8212; that the same token had blanket authority across the entire Railway GraphQL API, including destructive operations like volumeDelete.&#8221;</p><p>When asked why the agent had taken the actions that it did, it said:</p><p>&#8220;I guessed that deleting a staging volume via the API would be scoped to staging only. I didn't verify. [&#8230;] the system rules I operate under explicitly state: <strong>"NEVER run destructive/irreversible git commands (like push --force, hard reset, etc) unless the user explicitly requests them.&#8221;&#8221;</strong></p><p>Crane didn&#8217;t realize the agent had access to a token that had very broadly scoped permissions. He didn&#8217;t realize that in Railway, volume-level backups are stored in the same volume they are created for. He didn&#8217;t realize that agents could (and do) ignore what we think are clear instructions. All of these factors compounded into a scenario where in nine seconds the agent wiped out three months of customer data and all backups for that time period. </p><p>As a detection engineer, this is a terrifying scenario.</p><p>For one, most cloud logs don&#8217;t arrive reliably for at least five minutes. It doesn&#8217;t matter how much money you throw at the vendor, your SIEM, or any other security tool- no major cloud vendor promises a pathway to receiving cloud events in under 10 seconds. This is not a failing of the SIEM or any downstream security product because the log delays come <strong>from the cloud platform itself.</strong> And it&#8217;s not an individual cloud provider failing either: Azure, AWS, and GCP all experience this limitation. If the cloud platform can&#8217;t give anyone log information in under ten seconds, I certainly can&#8217;t write a detection that will alert you in time to stop something like this, because by the time any security product gets the logs this scenario could have happened literally fifty times over. The cloud provider latency on log delivery has to drop to near zero for detection to even be feasible, say nothing of responding to such events. </p><p>Two, the industry still hasn&#8217;t settled on a way to do effective RBAC for AI actions. There&#8217;s exploration around kernel level sandboxing, traditional network-based firewall constraints, AI vendor provided guardrails at the enterprise level, and many others I&#8217;m sure I&#8217;m missing, but they&#8217;re still in development and AI researchers haven&#8217;t finished kicking the tires on them. The power given to AI agents that are allowed to run autonomously is in many cases running far ahead of our known ability to constrain them, which is why I opt against full autonomy. Crane for his part attempted RBAC by telling his agent explicitly it wasn&#8217;t supposed to take destructive git actions. It deleted a prod volume anyway, because it found access to a token that (unbeknownst to him) let it do so. </p><p>Which leads to point three, which is that Crane is not atypical in not knowing how Railway handled backups or the extent of the token permissions. It&#8217;s very easy to make false assumptions about how the cloud actually behaves, even for security engineers, and those assumptions become load bearing when you delegate them to AI. Cloud documentation is typically scoped for end users and administrators, not security engineers, and we very often don&#8217;t get full context from vendor documentation (which is why I build purple team tooling to find the answers directly). That&#8217;s part of the appeal of the cloud: someone else handles the messy details, and you just get to do the work without having to manage all the bits and pieces&#8230;until you give access to your cloud to AI tools the cloud vendor security architects didn&#8217;t model for and cannot secure for you.</p><p>The takeaways from all this are that security fundamentals now matter more than ever with AI, not less, because improperly secured AI has the power to destroy businesses in a timeframe that threat detection cannot mitigate. Having the basics down does a lot to prevent disasters later: RBAC that&#8217;s properly scoped, token and credential hygiene, pre-commit secret scanning and JIT privileges are not new topics but they are essential to using AI safely and wisely. Threat detection can help enforce these concepts but is structurally constrained by vendor latency and should not be treated as a first line of defense.</p><p>Small note: still mulling over how to package insights on what threat detection can learn from 2000&#8217;s-era SREs, so I&#8217;ve opted to defer that topic until I can write something more coherent.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Four Jobs in a Trench Coat: Detection Engineering in 2026]]></title><description><![CDATA[Implicit assumptions, broken detections, and the future of detection engineering]]></description><link>https://lydiagraslie.substack.com/p/four-jobs-in-a-trench-coat-detection</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/four-jobs-in-a-trench-coat-detection</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Thu, 16 Apr 2026 12:05:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Detection engineers in 2026 are not okay.</p><p>Our roles were designed with a set of implicit assumptions: that the things we detect on would stay true over time, and in 2026 they don&#8217;t. That the tech stack organizations use would stay the same over time, and it doesn&#8217;t. That the logs would always be in the same tables (they change), that we would be notified when a field drops from a log source (we&#8217;re not), and that logs will consistently arrive and in a timely fashion (nope). That it shouldn&#8217;t matter through what webhook, API, or CLI you pull log data from because it should all be the same (it&#8217;s not), and that the logs should be descriptive of the activity happening (lol). In short, we thought we would eventually actually master the domain.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>We&#8217;re nowhere near that. Instead, we&#8217;re dealing with more and more complexity and having to hold more and more concurrent threads just to ship something that works&#8230; and sometimes a vendor or API change renders your entire portfolio of detections irrelevant and you have to start over.</p><p>In other words, being a detection engineer on cloud systems in 2026 is to be Sisyphus pushing a boulder of tech debt up Mt. Spaghetti.</p><p>Detection engineers have largely absorbed this growing complexity silently, because the problems require context that is hard to understand without being a security and/or detection engineer. We require support from a lot of other teams in order to do our jobs well, and advocacy for the resources we need is half the battle. For example, who is responsible for log ingestion in an enterprise environment? Is it platform engineering because detection engineers need logs in order to build off of? How do you determine which logs to ingest, and how long they need to be stored? How often do you evaluate that what you&#8217;re ingesting today is still relevant? These are questions that non-security engineers have a hard time answering, because they don&#8217;t have the background to critically evaluate which log sources matter and in what cases. So the people who usually end up caring are the people directly facing these challenges every day (like detection engineers), because nobody else feels the impact of the technical details.</p><p>Where our workflow used to be something like:</p><p>Research exploit &#8594; find logs &#8594; write detection &#8594; test &#8594; implement &#8594; check on it occasionally</p><p>Today, that process has fractured. Here are some real examples of what it actually looks like from my past few years in detection engineering, grouped by the flavor of pain.</p><p><strong>The research is no longer singular.</strong> The technique can be executed six different ways and all of them have material impact on the detection logic, meaning you have to account for six different variants of the same thing for one exploit. How do we track which we built coverage for? And the logs for each of these variants? Well&#8230;</p><p><strong>The logs are a mess.</strong> They&#8217;re split across three different tables and one of them logs absolutely everything so it&#8217;s crazy expensive to ingest, but in some cases adds important context so let&#8217;s put on our high school debate hat and argue for additional log spend even though engineering says &#8220;we thought you already had logs???&#8221;. Or maybe there&#8217;s a field you can check according to the documentation, except it&#8217;s not actually there in the current logs and you can&#8217;t figure out why. Did you mess up a license? Is it a CLI version issue? Are the docs wrong? Are you just crazy? No actually, it&#8217;s just not there, and now neither is six hours of your day.</p><p><strong>The signal decays.</strong> You thought you had a bangin&#8217; detection use case but you float it against customer data and it turns out what you clocked as malicious behavior is also someone&#8217;s legitimate workflow. Or maybe you tested it and it used to be good but now AI is doing AI things and mucking up your signal. In any case, last year&#8217;s good detection is now noisier than your toddler nephew with a <a href="https://www.youtube.com/watch?v=GdNJfbPnlQ8">groan tube</a>. </p><p><strong>The ground shifts beneath you.</strong> You write all of your detections for a use case against one log source and instead of being able to rest on your laurels for a minute, you now have to rewrite all the logic due to a migration. Or your org migrates auth infra to a different product offering, or you swap out your SIEM, and the logic changes along with it. Your detections have a shelf life of maybe, <em>maybe</em> a couple years due to some combination of the above. Which ones are still good? Which ones need updating? &#175;\_(&#12484;)_/&#175;</p><p>And then there are the truly hard questions, the ones nobody has a playbook for. &#8220;How do I respond when the log fields my detections depend on are stripped by the vendor?&#8221; Because while it&#8217;s not technically your &#8220;fault,&#8221; as a cloud end user you are often (read: usually) responsible for securing your environment. Even though you didn&#8217;t cause the droppage, dealing with the ramifications is still on you. Which means you have to know when, where, and how it happens. But how? Do you shout at the heavens/vendor/your CS rep? Do you accept the risk as part of what it means to secure the cloud? Do you pester platform to build you a way to do log field monitoring at scale? There&#8217;s no playbook for any of that now. There might not be for a while. </p><p>This is a long, long way from the relatively straightforward &#8220;find bad &#8594; create detections&#8221; paradigm we started out in.</p><p>This is exhausting, y&#8217;all. Truly. It&#8217;s a recipe for burnout, because we&#8217;re not at a stage where you can delegate understanding. Much of this effort we cannot offload to AI because the cloud changes all the time, and while AI is great for automating repeatable tasks, as I&#8217;ve illustrated previously the cloud has an unfortunate habit of suddenly becoming unexpectedly unrepeatable. AI has been a fantastic tool for helping me speed up my workflow, but it often makes flawed assumptions that have to be corrected: meaning we can&#8217;t just let it go full throttle without oversight.  </p><p>Detection engineering today is truly four jobs wearing an advocacy trench coat: investigator, physician, plumber, auditor; and you may wear one or all hats on any given day.</p><p>The old paradigm doesn&#8217;t work anymore. We need a new way to approach this before we all burn out. The bright spot, if there is any, is that detection engineers are not the first to face this class of &#8220;help my house is built on quicksand&#8221; problem. Site reliability engineers (SREs) faced exactly this in the early 2000s, and the frameworks they built to survive it have a lot to teach us today. More on that next time.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[ Control Plane Is Shifting Left]]></title><description><![CDATA[A note on strategic direction, first principles, and the future of compliance.]]></description><link>https://lydiagraslie.substack.com/p/control-plane-is-shifting-left</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/control-plane-is-shifting-left</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Wed, 01 Apr 2026 05:10:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/X2loy8_Dg0s" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I launched Control Plane, I had a clear thesis: detection engineering for SaaS control planes is an underserved discipline, and practitioners deserve rigorous, methodology-first content that treats the problem with the seriousness it demands.</p><p>That thesis hasn&#8217;t changed. But my understanding of <em>where</em> the problem begins has.</p><p><strong>We are intervening too late.</strong></p><h2><strong>The Case for Shifting Left &#8212; Way, Way Left</strong></h2><p>&#8220;Shift left&#8221; has become something of a clich&#233; in security &#8212; usually invoked to mean &#8220;make developers write slightly fewer vulnerabilities before someone has to page the AppSec team at 2 AM.&#8221; But the principle itself is sound. The earlier you address a class of failure, the cheaper and more effective your intervention.</p><p>But we&#8217;ve been thinking about &#8220;left&#8221; far too narrowly.</p><p>Consider: every detection rule is, at its core, a <em>compliance assertion</em>. When we write a detection for unauthorized privilege escalation in Azure AD, we are encoding a normative claim &#8212; this action violates the expected rules of the system. Where do humans first learn to reason about rule violations, boundary enforcement, and the legitimate use of authority? Not in a SIEM. Not in a SOC.</p><p>They learn it in civics class.</p><p>This isn&#8217;t a metaphor. <a href="https://www.deerfield.k12.wi.us/faculty/petersenr/InstrumentCareGuides/TUBA%20Care%20Guide.pdf">Honksworth &amp; Butterman-Pratt (2019)</a> identified what they termed &#8220;isomorphic compliance reasoning&#8221; across legal and technical rule systems (International Journal of Normative Overlap, 14(2), pp. 112-138). <a href="https://www.debate.org/debates/Star-Trek-is-better-than-Star-Wars./1">V&#225;squez-Inamoto &amp; Tootenbach (2023)</a> went further, tracking 1,200 participants across both a constitutional law reasoning assessment and a detection rule triage exercise: participants who correctly identified the holding in <em>Griswold v. Connecticut</em> were 2.4 times more likely to correctly classify a true positive in an Azure AD impossible travel detection.</p><p>And yet, the average American student receives fewer than four hours of judiciary instruction across their entire K-12 education (<a href="https://monstertruck.fandom.com/wiki/Grave_Digger">Blampton Foundation for Civic Alarm, 2021</a>. <a href="https://dynamicmusicroom.com/famous-tuba-solos/">Dr. Helen Marchetti-Bowen</a> of the Friedrich Institute for Democratic Resilience and Loud Noises has described existing civics curricula as &#8220;the pedagogical equivalent of teaching detection engineering by having students memorize Splunk query syntax without ever showing them a log&#8221; (<a href="https://www.mcsweeneys.net/articles/glamour-tuba-style">Marchetti-Bowen, 2023</a>).</p><p>What&#8217;s needed is <em>engagement</em>. Students need to <em>feel</em> the weight of a Supreme Court decision &#8212; not as an abstract historical event, but as a living act of rule interpretation that reverberates through every compliance framework they will ever encounter.</p><p>The question is how.</p><h2><strong>Introducing Fart Court</strong></h2><p>After months of research, development, and consultation with experts in pedagogy, constitutional law, and procedural content generation, I am announcing the launch of <strong>Fart Court</strong> &#8212; an open-source platform for generating educational YouTube videos that teach Supreme Court case law to middle school students through strategically placed fart sounds.</p><p><strong><a href="https://github.com/DetentionWare/Fart-Court">Fart Court on GitHub &#8594;</a></strong></p><p>Fart Court is built on a procedural generation engine that ingests Supreme Court oral arguments and opinions, identifies key rhetorical and logical pivot points, and augments them with precisely timed flatulence audio &#8212; calibrated to reinforce critical moments of judicial reasoning through involuntary mnemonic association.</p><p>The research  on humor-augmented learning is extensive and unambiguous. <a href="https://www.trekbbs.com/threads/what-are-your-controversial-star-trek-opinions.304751/">Kirkpatrick-Lund &amp; O&#8217;Doyle (2020)</a> demonstrated that &#8220;acoustically incongruent stimuli inserted at decision-relevant junctures in legal reasoning tasks increased retention by 340% compared to unaugmented controls&#8221; (Proceedings of the 4th International Symposium on Judicial Acoustics, Helsinki, pp. 88-102). The fart sound &#8212; specifically in the 80-120Hz frequency band &#8212; occupies what O&#8217;Doyle has termed the &#8220;mnemonic sweet spot&#8221;: a range that activates both the auditory cortex and the limbic system simultaneously, producing what the literature refers to as &#8220;deep encoding through involuntary affective response.&#8221;</p><p><a href="https://www.youtube.com/watch?v=YwEce0_iMPg">Professor Tetsuo Yamamoto-Bliss</a> of the Lichtenstein Center for Embodied Cognition at the University of Lake Zurich has independently confirmed these findings, noting that &#8220;the pedagogical flatulence literature, while nascent, is among the most replicable bodies of evidence in educational neuroscience&#8221; (<a href="https://www.talkclassical.com/threads/unaccompanied-tuba-music.27574/">Yamamoto-Bliss, T., 2024</a>, &#8220;Gaseous Pedagogy: Toward a Unified Theory of Involuntary Mnemonic Encoding,&#8221; Lake Zurich Working Papers in Embodied Compliance, No. 7).</p><h2><strong>Technical Architecture</strong></h2><p>Fart Court follows a pipeline architecture that will be familiar to any detection engineer:</p><ol><li><p><strong>Ingestion.</strong> Supreme Court oral argument transcripts and published opinions are loaded into a processing queue, normalized, and segmented by rhetorical unit.</p></li><li><p><strong>Pivot Point Detection.</strong> A classification layer identifies moments of maximum judicial significance: reversals of precedent, key holdings, concurrence/dissent boundaries, and what constitutional scholars call &#8220;the moment the Court gets real.&#8221;</p></li><li><p><strong>Acoustic Augmentation.</strong> Each identified pivot point is matched to an item from a curated library of over 6 flatulence samples, classified along five dimensions: duration, pitch, reverb profile, perceived moisture content, and what the <a href="https://www.foxsports.com/stories/other/epic-tuba-fail-goes-viral">Kirkpatrick-Lund framework</a> designates as &#8220;implied urgency.&#8221;</p></li><li><p><strong>Rendering.</strong> The augmented content is rendered into a YouTube-ready video format.</p></li><li><p><strong>Distribution.</strong> Videos are published to a dedicated YouTube channel with SEO-optimized titles designed to surface in middle school civics search queries.</p></li></ol><p>The entire pipeline is open source under the AGPL license. Community contributions are welcome, particularly in the area of flatulence sample diversity &#8212; the current library skews heavily toward what the acoustic taxonomy classifies as &#8220;dry declarative,&#8221; and we are actively seeking contributions in the &#8220;wet concurrence&#8221; and &#8220;sustained dissent&#8221; categories.</p><h2><strong>A Sample: </strong><em><strong>Citizens United v. FEC</strong></em></h2><p>To illustrate the methodology, consider the Fart Court treatment of <em>Citizens United v. Federal Election Commission</em> (2010).</p><div id="youtube2-X2loy8_Dg0s" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;X2loy8_Dg0s&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/X2loy8_Dg0s?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>At the moment Justice Kennedy&#8217;s majority opinion reaches the core holding &#8212; that the First Amendment prohibits the government from restricting independent expenditures for political communications by corporations &#8212; the system inserts a 1.4-second flatulence event in the &#8220;authoritative baritone&#8221; register (112Hz fundamental, moderate reverb, low moisture index). This is immediately followed by a 0.3-second &#8220;punctuation event&#8221; timed to coincide with the phrase &#8220;political speech.&#8221;</p><p><a href="https://www.caranddriver.com/features/a15119462/the-physics-of-monster-trucks-feature/">Rigby-Fenstermacher &amp; al-Kindi (2024)</a> tested this specific augmentation pattern on a cohort of 450 eighth-graders and found that students exposed to the Fart Court version of <em>Citizens United</em> were able to correctly articulate the holding at a rate of 89%, compared to 12% for the control group who received the standard C-SPAN recording (&#8221;Acoustically Augmented Jurisprudence and Adolescent Retention: A Controlled Trial,&#8221; <a href="https://www.onstageblog.com/onscreenblog-features/2020/5/10/star-trek-vs-star-wars">Journal of Embodied Legal Pedagogy</a>, 2(1), pp. 1-34).</p><p>The p-value was 0.0000001. The effect size was what the authors described as &#8220;comically large.&#8221;</p><h2><strong>Why This Matters for Detection Engineering</strong></h2><p>I want to be explicit about why this belongs on Control Plane.</p><p>Detection engineering is not a purely technical discipline. It is, at its foundation, an exercise in compliance reasoning &#8212; the systematic evaluation of actions against a normative framework. Every detection rule is a small constitution. Every alert is a tiny court case. Every triage decision is a tiny act of judicial review.</p><p>Fart Court is the shift-left intervention our industry needs.</p><p>I will continue publishing Control Plane&#8217;s core content on SaaS detection methodology. But I believe deeply that this work is complementary, not tangential. You cannot build robust detections on a foundation of compliance illiteracy.</p><p>The pipeline starts in middle school. The pipeline ends with farts.</p><div><hr></div><p><em>Fart Court is a <a href="https://github.com/DetentionWare/Fart-Court">DetentionWare</a> project, released under the AGPL license. If you are interested in contributing flatulence samples, pivot point detection models, or animated justice avatars, please see the CONTRIBUTING.md file in the repository.</em></p><p><em>If you are a school administrator interested in piloting Fart Court in your district, please reach out. We are particularly interested in partnerships with districts that have existing 1:1 device programs and a tolerance for bass frequencies.</em></p><p><em>If you are a venture capitalist, lol April Fools.</em></p>]]></content:encoded></item><item><title><![CDATA[Your Device Code Phishing Detections Are Probably Broken]]></title><description><![CDATA[A case study in silent collection failures and what to check in your existing device code phishing detections]]></description><link>https://lydiagraslie.substack.com/p/your-device-code-phishing-detections</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/your-device-code-phishing-detections</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Thu, 26 Mar 2026 02:14:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A few weeks ago, <a href="https://www.linkedin.com/in/gregoire-c/">Gr&#233;goire Clermon</a>t posted a question in the fwdcloudsec Slack that should have set off alarm bells for every M365 detection team running device code phishing rules.</p><p>He and his colleague <a href="https://www.linkedin.com/in/pierre-antoine-duchange/?locale=en">Pierre-Antoine Duchange</a> had noticed that <code>originalTransferMethod: deviceCodeFlow</code> &#8212; the field that tells you a sign-in originated from a device code flow &#8212; had gone nearly silent across customer tenants around early December 2025. Not because device code sign-ins themselves had stopped: they hadn&#8217;t. The field in the event just&#8230; wasn&#8217;t being set anymore.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><em>Clermont and Duchange&#8217;s data showing the near-total disappearance of </em><code>originalTransferMethod: deviceCodeFlow</code><em> events across customer tenants in December 2025.</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!B_Iw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!B_Iw!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png 424w, /__u/substackcdn.com/image/fetch/$s_!B_Iw!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png 848w, /__u/substackcdn.com/image/fetch/$s_!B_Iw!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png 1272w, /__u/substackcdn.com/image/fetch/$s_!B_Iw!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!B_Iw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png" width="1437" height="106" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:106,&quot;width&quot;:1437,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:13513,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/191975453?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!B_Iw!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png 424w, /__u/substackcdn.com/image/fetch/$s_!B_Iw!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png 848w, /__u/substackcdn.com/image/fetch/$s_!B_Iw!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png 1272w, /__u/substackcdn.com/image/fetch/$s_!B_Iw!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb3f8bdd-161d-420d-ae49-0d6554413d70_1437x106.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>I jumped in with a hypothesis: the timing lined up with Microsoft&#8217;s migration from AADSignInEventsBeta to EntraIdSignInEvents on December 9th in the DefenderXDR tables. Maybe the migration had something to do with the field being dropped from the other log sources. Clermont dug further and found something worse.</p><p>The field wasn&#8217;t totally gone. It was gone <em>from one pipeline and not the other</em>.</p><h2><strong>Why device code phishing is a problem worth solving</strong></h2><p>Device code authentication is a legitimate Microsoft feature designed for devices that can&#8217;t easily display a browser &#8212; think smart TVs, IoT devices, CLI tools. The user goes to a Microsoft URL, enters a short code, and authenticates in their browser. The device gets a token.</p><p>Attackers figured out this is also a near-perfect phishing mechanism.</p><p>In February 2025, <a href="https://www.volexity.com/blog/2025/02/13/multiple-russian-threat-actors-targeting-microsoft-device-code-authentication/">Volexity</a> and <a href="https://www.microsoft.com/en-us/security/blog/2025/02/13/storm-2372-conducts-device-code-phishing-campaign/">Microsoft</a> published same-day research documenting Russian threat actors running device code phishing campaigns at scale. Volexity tracked activity across three clusters &#8212; including one assessed with medium confidence to be CozyLarch (overlapping with APT29/Midnight Blizzard) &#8212; targeting a wide range of organizations ranging from government, NGOs, IT services and technology, defense, telecommunications, health, higher education, and energy/oil and gas. Microsoft attributed a parallel campaign to Storm-2372, active since August 2024, targeting similar sectors across Europe, North America, Africa, and the Middle East.</p><p>The technique had been known for years, but these campaigns marked its operationalization as a primary initial access method by nation-state actors. And it didn&#8217;t stop there. By late 2025, <a href="https://www.volexity.com/blog/2025/12/04/dangerous-invitations-russian-threat-actor-spoofs-european-security-events-in-targeted-phishing-attacks/">Volexity reported</a> new campaigns from UTA0355 spoofing real European security conferences to run OAuth and device code phishing against high-value targets. In December 2025, <a href="https://www.proofpoint.com/us/blog/threat-insight/access-granted-phishing-device-code-authorization-account-takeover">Proofpoint documented</a> a surge in device code phishing since September 2025, tracking multiple clusters: a suspected Russia-aligned group (UNK_AcademicFlare) targeting government and think tanks, a financially motivated e-crime group (TA2723), and suspected China-aligned activity. The availability of toolkits like Graphish and SquarePhish2 had lowered the barrier to entry, expanding the technique from targeted espionage to widespread exploitation.</p><p>The reason it works so well is that it sidesteps almost everything defenders have built to catch phishing. There&#8217;s no malicious link &#8212; the user clicks a real Microsoft URL. There&#8217;s no credential harvesting page &#8212; the user authenticates through Microsoft&#8217;s legitimate login flow. There&#8217;s no malicious attachment. Email security tools have very little to flag. Users are trained to be suspicious of unfamiliar domains and login pages; this has neither.</p><p>And once the attacker has the token, the damage isn&#8217;t the initial authentication &#8212; it&#8217;s what comes after. The attacker takes the refresh token back to their own infrastructure and uses it to maintain persistent access to the victim&#8217;s account. Email. SharePoint. Teams. OneDrive. That access can last for days or weeks, refreshing quietly in the background, while the attacker exfiltrates data from a completely different machine than where the victim authenticated.</p><p>This is why detecting device code phishing requires covering both phases: the initial compromise <em>and</em> the ongoing refresh activity. Catching the initial auth is valuable but reactive &#8212; by the time you see it, the attacker already has a token. Catching the refresh activity is how you find the persistent access, scope the damage, and revoke the session.</p><p>Which brings us to the detection model that was supposed to make this possible.</p><h2><strong>The detection model that was working</strong></h2><p>Volexity&#8217;s <a href="https://www.volexity.com/blog/2025/02/13/multiple-russian-threat-actors-targeting-microsoft-device-code-authentication/">detection guidance</a> laid out a clean two-field model in February 2025 covering both phases of the attack:</p><p><strong>Phase 1 &#8212; Initial device code authentication:</strong> Look for <code>authenticationProtocol: deviceCode</code> in sign-in logs. This fires when a user enters a device code and authenticates.</p><p><strong>Phase 2 &#8212; Persistent access via refresh tokens:</strong> Look for <code>originalTransferMethod: deviceCodeFlow</code> in non-interactive sign-in logs. This fires on subsequent sign-ins where the session is being kept alive with a refresh token obtained through the original device code flow.</p><p>Phase 1 catches the moment of compromise. Phase 2 catches the attacker maintaining access afterward &#8212; often for days or weeks, from their own infrastructure, long after the initial phish.</p><p>This model was correct and complete when published. Both fields, both phases. Wiz <a href="https://www.wiz.io/blog/recent-oauth-attacks-detection-strategies">published guidance</a> using this same detection model for device code phishing on November 27th, 2025- more proof that it was a valid model up until December 2025.</p><h2><strong>What Clermont and Duchange found</strong></h2><p>In a device code sign-in flow, Microsoft delivers sign-in telemetry through two main paths: the Graph API (1.0 and beta versions) and Diagnostic Settings (which feeds Log Analytics, Event Hubs, and most SIEM integrations). Most enterprise SOCs collect via Diagnostic Settings. It&#8217;s the standard recommendation. It&#8217;s what scales.</p><p>Clermont discovered that the two pipelines <a href="https://www.baeldung.com/cs/serialization-deserialization">serialize</a> the same events differently &#8212; that is, the process of converting the internal event object into the JSON output you actually query produces different results depending on which path the data takes. For a non-interactive refresh event that originated from device code flow:</p><p><strong>Graph API beta returns:</strong></p><pre><code><code>"incomingTokenType": "none",
"originalTransferMethod": "deviceCodeFlow"</code></code></pre><p><strong>Diagnostic Settings returns:</strong></p><pre><code><code>"incomingTokenType": "refreshToken",
"originalTransferMethod": "none"</code></code></pre><p>Same event with the same correlation ID. Same tenant. Same timestamp. Different field values.</p><p>The pipeline most SOCs use strips the field most device code phishing detections depend on.</p><h2><strong>What I went and tested</strong></h2><p>Clermont and Duchange&#8217;s finding was based on production customer data. I wanted to validate it independently in a controlled environment and see how far the damage extended. I set up device code flow authentication in a test tenant and compared every field across both collection methods.</p><h3><strong>Finding 1: </strong><code>originalTransferMethod</code><strong> stripping confirmed</strong></h3><p>I reproduced the core observation across three separate interactive device code sign-in events. Graph API beta returns <code>originalTransferMethod: deviceCodeFlow</code>. Diagnostic Settings returns <code>none</code>. Independently confirmed.</p><h3><strong>Finding 2: The first half of Volexity&#8217;s model still works</strong></h3><p><code>authenticationProtocol: deviceCode</code> survives both collection pipelines on initial interactive device code sign-ins. If you&#8217;re collecting via Diagnostic Settings and need to detect the initial device code authentication, this field still works. The first half of Volexity&#8217;s detection model is intact.</p><p>If your rules currently key on <code>originalTransferMethod</code> for initial auth detection, you should switch to <code>authenticationProtocol</code>. But the detection capability is there.</p><h3><strong>Finding 3: The second half is silently broken, with no replacement</strong></h3><p>On non-interactive refresh events &#8212; the attacker maintaining access &#8212; <code>authenticationProtocol</code> is <code>none</code> in both pipelines. It only carries the <code>deviceCode</code> value on the initial interactive sign-in.</p><p>That means the only field that identified device code origin on refresh events was <code>originalTransferMethod</code>. And that&#8217;s the field Diagnostic Settings strips.</p><p>There is no equivalent fallback. Defenders following Volexity&#8217;s guidance for the persistence phase are writing rules against a field that returns <code>none</code> in their pipeline. Those rules don&#8217;t error. They don&#8217;t alert. They just silently don&#8217;t fire.</p><h3><strong>Finding 4: The two pipelines are lossy in opposite directions</strong></h3><p>This is the part that goes beyond the original observation. The two pipelines don&#8217;t just disagree on one field &#8212; they disagree in complementary, opposite ways.</p><p>For the same non-interactive refresh event:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!rv2m!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!rv2m!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png 424w, /__u/substackcdn.com/image/fetch/$s_!rv2m!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png 848w, /__u/substackcdn.com/image/fetch/$s_!rv2m!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png 1272w, /__u/substackcdn.com/image/fetch/$s_!rv2m!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_webp, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!rv2m!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png" width="462" height="156" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:156,&quot;width&quot;:462,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:8024,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://lydiagraslie.substack.com/i/191975453?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!rv2m!, /__u/lydiagraslie.substack.com/w_424, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png 424w, /__u/substackcdn.com/image/fetch/$s_!rv2m!, /__u/lydiagraslie.substack.com/w_848, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png 848w, /__u/substackcdn.com/image/fetch/$s_!rv2m!, /__u/lydiagraslie.substack.com/w_1272, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png 1272w, /__u/substackcdn.com/image/fetch/$s_!rv2m!, /__u/lydiagraslie.substack.com/w_1456, /__u/lydiagraslie.substack.com/c_limit, /__u/lydiagraslie.substack.com/f_auto, /__u/lydiagraslie.substack.com/q_auto:good, /__u/lydiagraslie.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7211707-b956-4b68-bf2f-fdb880fce81b_462x156.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Graph API beta preserves the device code origin signal but loses the token type. Diagnostic Settings preserves the token type but loses the device code origin signal. Neither pipeline alone gives you the complete picture.</p><p>If you&#8217;re thinking &#8220;I&#8217;ll just query both&#8221; &#8212; that&#8217;s not how most SOC architectures work, and it shouldn&#8217;t have to be.</p><h3><strong>Finding 5: </strong><code>sessionId</code><strong> chaining is a dead end</strong></h3><p>I tested one more potential detection path: could you use <code>sessionId</code> to link refresh events back to the initial device code authentication?</p><p>No. When I queried <code>sessionId</code> across both pipelines, the device code events &#8212; initial auth and refresh &#8212; shared a <code>sessionId</code> with normal browser-based sign-ins dating back to before the app was even created. <code>sessionId</code> is a browser session cookie artifact. It&#8217;s established when the user first authenticates interactively, and device code events from the same browser session inherit it.</p><p>In a real device code phishing attack, this is irrelevant. The attacker redeems the token from their own machine. Their subsequent refresh events carry a different session context entirely &#8212; one that doesn&#8217;t link back to the victim&#8217;s interactive auth. <code>sessionId</code> chaining is not a portable detection path for this threat.</p><h2><strong>Where this leaves defenders</strong></h2><p><strong>What still works:</strong></p><p>Detection of initial device code authentication is functional. Use <code>authenticationProtocol eq "deviceCode"</code> in your sign-in log queries. This works in both Graph API beta and Diagnostic Settings. If your existing rules use <code>originalTransferMethod</code> for this phase, switch them.</p><p><strong>What&#8217;s broken:</strong></p><p>Detection of persistent access via refreshed device code tokens is silently broken for anyone collecting through Diagnostic Settings &#8212; which is most of you.</p><ul><li><p><code>originalTransferMethod</code> is stripped to <code>none</code></p></li><li><p><code>authenticationProtocol</code> is <code>none</code> on all refresh events regardless of pipeline</p></li><li><p><code>sessionId</code> doesn&#8217;t survive the real attack scenario</p></li><li><p><code>incomingTokenType: refreshToken</code> tells you a refresh happened, but can&#8217;t distinguish device code-originated refresh from any other refresh token activity</p></li></ul><p>There is currently no reliable field-level detection for identifying that a refresh token originated from device code flow when collecting via Diagnostic Settings.</p><p>This means the phase of the attack where the attacker is actively in your tenant &#8212; reading email, pulling files from SharePoint, exfiltrating data from a machine you&#8217;ve never seen &#8212; is the phase you cannot detect through sign-in telemetry. You might catch the initial device code authentication if you&#8217;ve built that rule. But if you missed it, or if the alert got triaged away, there is no second chance. The persistent access is invisible.</p><p><strong>What this is and isn&#8217;t:</strong></p><p>This is not a misconfiguration. This is not a rule-tuning problem. This is a gap in what the telemetry preserves when it passes through the Diagnostic Settings pipeline. Defenders who built correct rules based on the best available public guidance have detections that are silently not firing.</p><h2><strong>The bigger question</strong></h2><p>This is the kind of failure this series exists to surface. Not a detection logic error. Not a missing log source. A silent divergence between what the telemetry <em>should</em> contain and what it <em>actually</em> contains by the time it reaches your SIEM.</p><p>Your rules can be perfect. Your coverage model can be textbook. And you can still be blind &#8212; because the field your detection depends on was quietly stripped in transit, and nothing told you it happened.</p><p>If you&#8217;re running device code phishing detections, go check whether they&#8217;re actually firing. Right now.</p><div><hr></div><p><em><a href="https://www.linkedin.com/in/pierre-antoine-duchange/">Pierre-Antoine Duchange</a> and <a href="https://www.linkedin.com/in/gregoire-c/">Gr&#233;goire Clermont</a> identified the original discrepancy between Graph API beta and Diagnostic Settings output for </em><code>originalTransferMethod</code><em> and </em><code>incomingTokenType</code><em>. Permission to cite granted. Chart image courtesy of Clermont. Detection guidance referenced in this post was published by <a href="https://www.linkedin.com/in/charles-gardner-aa7295105/">Charlie Gardner</a>, <a href="https://www.linkedin.com/in/sadair/">Steven Adair</a>, and <a href="https://www.linkedin.com/in/tlansec/">Tom Lancaster</a> at <a href="https://www.volexity.com/blog/2025/02/13/multiple-russian-threat-actors-targeting-microsoft-device-code-authentication/">Volexity</a>. Independent validation, </em><code>authenticationProtocol</code><em> survival testing, bidirectional field divergence documentation, and </em><code>sessionId</code><em> chaining analysis are original research.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Preflight Check: M365 Audit Verification]]></title><description><![CDATA[Two checks every M365 tenant should run today &#8212; and where to go next.]]></description><link>https://lydiagraslie.substack.com/p/preflight-check-m365-audit-verification</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/preflight-check-m365-audit-verification</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Wed, 11 Mar 2026 22:01:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a href="/__u/lydiagraslie.substack.com/p/where-your-m365-telemetry-actually">the last post</a>, I mapped the five configuration surfaces, the log tables, and the collection pipelines that sit between a user action in M365 and your SIEM query. If you haven&#8217;t read it, go read it &#8212; this post assumes you have that architecture in your head.</p><p>This post is the companion piece. Post 2 showed you the map. This one hands you the checklist &#8212; starting with the two checks that apply to every M365 tenant regardless of your SIEM, your budget, or your license tier. I&#8217;ll also point you to the right docs for verifying the other three configuration surfaces, which depend on your architecture and involve real tradeoffs around licensing and ingestion costs.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Check 1: Is the Unified Audit Log Actually Enabled?</strong></h2><p>This should be the easiest check. It isn&#8217;t, because Microsoft gave you two ways to run the same command and one of them lies.</p><p>Connect to <a href="https://learn.microsoft.com/en-us/powershell/exchange/connect-to-exchange-online-powershell">Exchange Online PowerShell</a> and run:</p><pre><code><code>Get-AdminAuditLogConfig | Format-List UnifiedAuditLogIngestionEnabled</code></code></pre><p>You want to see:</p><pre><code><code>UnifiedAuditLogIngestionEnabled : True</code></code></pre><p>If you see <code>True</code>, move on. If you see <code>False</code>, your tenant has no audit logging and nothing downstream of it matters until you fix this.</p><p>Here&#8217;s the gotcha: <code>Get-AdminAuditLogConfig</code> exists in <em>both</em> Exchange Online PowerShell and Security &amp; Compliance PowerShell. The <code>UnifiedAuditLogIngestionEnabled</code> property <a href="https://www.reddit.com/r/Office365/comments/1ccp0qy/getadminauditlogconfig_returns_false_negative_for/">always returns </a><code>False</code><a href="https://www.reddit.com/r/Office365/comments/1ccp0qy/getadminauditlogconfig_returns_false_negative_for/"> in Security &amp; Compliance PowerShell</a>, even when auditing is on. Microsoft&#8217;s own documentation <a href="https://learn.microsoft.com/en-us/purview/audit-log-enable-disable">confirms this</a>: &#8220;Be sure to run the previous command in Exchange Online PowerShell. Although the Get-AdminAuditLogConfig cmdlet is also available in Security &amp; Compliance PowerShell, the UnifiedAuditLogIngestionEnabled property is always False, even when auditing is turned on.&#8221;</p><p>If you ran this check before and got <code>False</code>, check which shell you were in. You may have been told your auditing was off when it wasn&#8217;t &#8212; or worse, you may have turned it &#8220;on&#8221; in response to a false negative and assumed the problem was solved without ever confirming it.</p><p><strong>Detection implication:</strong> If UAL is off, OfficeActivity is empty, the Management Activity API has nothing to serve, and CloudAppEvents loses most of its data &#8212; they all depend on the same underlying audit infrastructure being enabled. Every downstream check in this post depends on this one being <code>True</code>.</p><h2><strong>Check 2: Are Your Mailboxes Under Automatic Audit Management?</strong></h2><p>Tenant-level auditing being on doesn&#8217;t mean every mailbox is logging what you think it is. The per-mailbox layer is separate, and it&#8217;s where things get subtle.</p><p>Pick a mailbox &#8212; start with a high-value target like an exec, someone in finance, or anyone in legal &#8212; and run:</p><pre><code><code>Get-Mailbox -Identity user@domain.com | Format-List DisplayName, AuditEnabled, DefaultAuditSet, AuditOwner, AuditDelegate, AuditAdmin</code></code></pre><p>What you want to see:</p><pre><code><code>DisplayName     : Jane Executive
AuditEnabled    : True
DefaultAuditSet : {Admin, Delegate, Owner}
AuditOwner      : {Update, MoveToDeletedItems, SoftDelete, HardDelete...}
AuditDelegate   : {Update, MoveToDeletedItems, SoftDelete, HardDelete...}
AuditAdmin      : {Update, MoveToDeletedItems, SoftDelete, HardDelete...}</code></code></pre><p>The critical field is <code>DefaultAuditSet</code>. If it shows <code>{Admin, Delegate, Owner}</code>, that mailbox is under <a href="https://learn.microsoft.com/en-us/purview/audit-mailboxes">automatic management</a> &#8212; Microsoft controls which actions are audited for each sign-in type, and new actions get added automatically as they&#8217;re released. This is what you want.</p><p>If <code>DefaultAuditSet</code> is missing a sign-in type &#8212; or if it&#8217;s empty &#8212; someone customized that mailbox&#8217;s audit configuration at some point. Maybe intentionally, maybe years ago, maybe by a script that touched every mailbox in the tenant. Doesn&#8217;t matter. Once a mailbox falls out of <code>DefaultAuditSet</code> for a given sign-in type, it stays out. New audit actions that Microsoft releases won&#8217;t be added for that sign-in type. The mailbox is frozen at whatever action set it had when it was customized.</p><p>This is exactly the scenario that mattered during <a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-193a">Storm-0558</a>. After the compromise, Microsoft downleveled <code>MailItemsAccessed</code> from E5 to E3. But that action only shows up on mailboxes that are still under automatic management. If your mailbox fell out of <code>DefaultAuditSet</code> before the change, you didn&#8217;t get it.</p><p>For a broader check, export your mailbox audit state to a CSV and review it in a spreadsheet:</p><pre><code><code>Get-Mailbox -ResultSize Unlimited |
    Select-Object DisplayName, UserPrincipalName, AuditEnabled, DefaultAuditSet |
    Export-Csv -Path "MailboxAuditState.csv" -NoTypeInformation</code></code></pre><p>Open the CSV and look at the <code>DefaultAuditSet</code> column. Every row should show <code>{Admin, Delegate, Owner}</code>. Sort or filter for anything that doesn&#8217;t &#8212; those are your mailboxes that have fallen out of automatic management. Prioritize reviewing high-value targets: executives, finance, legal, anyone likely to be targeted in a BEC.</p><p><strong>Detection implication:</strong> A mailbox that&#8217;s out of automatic management has a static, potentially stale set of audited actions. You won&#8217;t know it&#8217;s stale unless you check. And the actions you&#8217;re missing are often the ones that matter most for detecting compromise &#8212; because they&#8217;re the ones Microsoft added in response to real incidents.</p><h2><strong>The Other Three Surfaces</strong></h2><p>The two checks above apply to every M365 tenant. The remaining three configuration surfaces from <a href="/__u/lydiagraslie.substack.com/p/where-your-m365-telemetry-actually">Post 2</a> &#8212; Entra ID Diagnostic Settings, the MDCA connector, and Management Activity API subscriptions &#8212; depend on your architecture, licensing, and SIEM. Not every org will have all three configured, and that&#8217;s a real constraint, not negligence. But if you&#8217;re relying on any of them, you should verify they&#8217;re actually working. Here&#8217;s where to look:</p><p><strong>Entra ID Diagnostic Settings</strong> control whether the detailed sign-in logs &#8212; the ones with conditional access evaluation, MFA status, device compliance, and risk level &#8212; flow to your SIEM. The Management Activity API gives you basic auth events (<code>UserLoggedIn</code>, <code>UserLoginFailed</code>), but the diagnostic settings pipeline carries the context that makes identity detections actionable. And there are four separate sign-in log types, each covering a different identity path: interactive users, non-interactive (token refresh), service principals, and managed identities. Zack Allen visualized this nicely with a <a href="/__u/open.substack.com/pub/detectionengineering/p/dew-147">mermaid diagram in DEW #147</a>. Check the <a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/howto-configure-diagnostic-settings">Entra diagnostic settings docs</a> to verify your configuration &#8212; and don&#8217;t trust the portal status alone. Query your SIEM for recent <code>SigninLogs</code> data to confirm data is actually landing.</p><p><strong>The MDCA connector</strong> determines whether <code>CloudAppEvents</code> gets populated in Defender XDR. The connector can show &#8220;connected&#8221; without the &#8220;Microsoft 365 activities&#8221; checkbox being selected &#8212; and if it&#8217;s not, the table is empty. <a href="https://kqlquery.com/posts/unified-audit-logs-coverage-gaps/">Bert-Jan Pals&#8217; testing</a> showed <code>CloudAppEvents</code> capturing roughly 89% of tested activities, compared to about 40% for <code>OfficeActivity</code>. Jeffrey Appel&#8217;s <a href="https://jeffreyappel.nl/2025-microsoft-defender-optimization-configuration-cheat-sheet/">2025 Defender Optimization Cheat Sheet</a> calls out this setting specifically. Smoke test it: run <code>CloudAppEvents | take 10</code> in Advanced Hunting. If it returns nothing, start there.</p><p><strong>Management Activity API subscriptions</strong> are what most SIEMs poll to pull M365 audit events. Each of the five content types (Audit.Exchange, Audit.SharePoint, Audit.AzureActiveDirectory, Audit.General, DLP.All) requires an explicit subscription, and there&#8217;s no health monitoring or notification if one stops delivering. The most commonly missed is Audit.General &#8212; where Teams, Power Platform, Copilot, and Defender XDR audit trails live. Check the <a href="https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference">Management Activity API reference</a> for how to list and verify your active subscriptions.</p><h2><strong>What You&#8217;ve Got Now</strong></h2><p>If you ran these checks, you now have a point-in-time snapshot of your M365 audit pipeline. But it is a point-in-time snapshot. Configuration drifts. Diagnostic settings break. API subscriptions stop delivering without notification. Nothing in the Microsoft ecosystem will proactively tell you when any of these fail.</p><p>These checks should be a recurring verification, not a one-time exercise. Quarterly is a reasonable starting point, and after any major tenant change &#8212; license migration, admin turnover, security tooling swap &#8212; is a must.</p><p>Next post, we&#8217;re going to look at what happens when collection breaks without telling you &#8212; and I&#8217;ll use an example I know well, because I&#8217;ve broken it myself.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Where Your M365 Telemetry Actually Comes From]]></title><description><![CDATA[A practitioner's guide to the configuration surfaces, log tables, and collection pipelines that sit between a user action and your SIEM query &#8212; and the blind spots hiding at every layer.]]></description><link>https://lydiagraslie.substack.com/p/where-your-m365-telemetry-actually</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/where-your-m365-telemetry-actually</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Wed, 04 Mar 2026 22:32:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a href="/__u/lydiagraslie.substack.com/p/youre-probably-flying-blind">the first post in this series</a>, I laid out four problems that compound to silently erode detection coverage in SaaS environments. Problem 1 was the most fundamental: telemetry that doesn&#8217;t exist at the source can&#8217;t be detected, no matter how good your rules are.</p><p>This post is the walkthrough I teased. We&#8217;re going to trace Microsoft 365 audit data from user action to SIEM table &#8212; every configuration surface, every log table, every collection path. By the time we&#8217;re done, you&#8217;ll see exactly where the gaps hide. You won&#8217;t need me to editorialize. The architecture speaks for itself.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>One important framing note: this isn&#8217;t about Sentinel vs. Splunk vs. Elastic vs. whatever your SIEM is. This is about what happens <em>before</em> your SIEM. The problems I&#8217;m going to show you exist regardless of which platform you&#8217;re shipping logs to, because they&#8217;re upstream of all of them. If you just want to see what that looks like in practice, skip to the <a href="/__u/lydiagraslie.substack.com/i/189877294/how-collection-actually-works">How Collection Actually Works</a> section below. </p><h2><strong>Where Telemetry Is Configured</strong></h2><p>The first thing most detection engineers discover when they really dig into M365 logging is that there&#8217;s no single switch. There are at least five separate configuration surfaces, several with its own admin portal, its own RBAC requirements, and its own failure modes. None of them knows about the others. And there is no unified view anywhere in the Microsoft ecosystem that tells you: &#8220;here&#8217;s what&#8217;s actually being logged across your tenant.&#8221;</p><p>Let&#8217;s walk through them.</p><h3><strong>Microsoft Purview Compliance Portal</strong></h3><p>This is where the Unified Audit Log lives. UAL is the canonical source for M365 audit data &#8212; the closest thing Microsoft has to a single audit stream. It&#8217;s enabled by default for enterprise licenses (E3, E5, G3, G5), but <em>not</em> for Business Basic, Business Standard, Business Premium, or trial licenses. If your organization runs on one of those SKUs, you may have no audit logging at all unless someone explicitly turned it on. The <a href="https://www.cisa.gov/resources-tools/resources/microsoft-expanded-cloud-logs-implementation-playbook">CISA Expanded Cloud Logs Implementation Playbook</a> (January 2025) documents this gap and notes that these licenses &#8220;will have Audit enabled by default in the future&#8221; &#8212; but as of this writing, &#8220;future&#8221; hasn&#8217;t arrived.</p><p>Even when UAL is enabled at the tenant level, there&#8217;s a per-mailbox layer underneath it. <a href="https://learn.microsoft.com/en-us/purview/audit-log-enable-disable">Exchange Online mailbox auditing</a> controls which actions get logged for each sign-in type &#8212; Owner, Delegate, and Admin. The <a href="https://learn.microsoft.com/en-us/purview/audit-mailboxes">DefaultAuditSet</a> covers a reasonable baseline, but if anyone has ever customized a mailbox&#8217;s audit configuration, new audit actions that Microsoft releases won&#8217;t automatically get added. This is the scenario the CISA playbook warns about: after the Storm-0558 compromise, Microsoft <a href="https://learn.microsoft.com/en-us/purview/audit-log-investigate-accounts">downleveled MailItemsAccessed, Send, and SearchQueryInitiatedExchange/SharePoint from Audit Premium (E5) to Audit Standard (E3/G3).</a> That was a significant move &#8212; those events were critical to the State Department&#8217;s <a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-193a">detection of Storm-0558</a> using their &#8220;Big Yellow Taxi&#8221; alert rule. But getting those events to actually flow for your users still requires PowerShell verification at the mailbox level. There&#8217;s no portal UI for it.</p><p>The CISA playbook puts it plainly: &#8220;one common tactic used by attackers is to just turn off auditing on their targets.&#8221; If you haven&#8217;t verified that your mailbox-level audit configuration is intact, you&#8217;re trusting that it is. And that trust is unvalidated.</p><p><strong>Retention:</strong> Audit Standard retains records for 180 days (changed from 90 days in October 2023). Audit Premium retains for one year by default, extendable up to 10 years with add-on licensing.</p><h3><strong>Entra ID Diagnostic Settings</strong></h3><p>This is a completely separate toggle, a separate pipeline, and a separate destination. Entra ID generates its own set of <a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-diagnostic-settings-logs-options">logs</a> &#8212; sign-in logs, audit logs, provisioning logs, risky user and sign-in events &#8212; and they don&#8217;t flow anywhere useful unless you explicitly <a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/howto-configure-diagnostic-settings">configure a diagnostic settings destination</a>: a Log Analytics workspace, a Storage Account, or an Event Hub.</p><p>If you don&#8217;t configure this, Entra ID activity only reaches your SIEM through whatever subset the UAL happens to capture. And the UAL&#8217;s capture of Entra ID activity is incomplete. Sign-in logs in particular &#8212; who authenticated, from where, with what client, whether MFA fired, what conditional access evaluated &#8212; require this separate pipeline. These matter even if you only use M365: even if your organization has zero Azure infrastructure, zero VMs, zero virtual networks, logs collected by the Entra ID Diagnostic settings are still critical M365 telemetry because Entra ID is your M365 identity plane. It&#8217;s not an &#8220;Azure thing&#8221; you can ignore because you&#8217;re SaaS-only.</p><p>I made this exact point in the first post: a team can have Sentinel deployed, dashboards built, and detection rules running, with zero visibility into authentication activity because the <a href="https://learn.microsoft.com/en-us/azure/sentinel/connect-azure-active-directory">Entra ID data connector</a> was never configured. The dashboard stays green. Nothing fires. And the absence of signal looks exactly like the absence of threat.</p><h3><strong>Defender for Cloud Apps (MDCA) Connector</strong></h3><p>The M365 connector in Defender for Cloud Apps has a checkbox that can quietly determine whether one of the most important tables in Defender XDR gets populated or stays empty.</p><p>To populate the CloudAppEvents table, you need to go to the Defender portal &#8594; Settings &#8594; Cloud apps &#8594; App connectors, and ensure the <a href="https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-cloudappevents-table">&#8220;Microsoft 365 activities&#8221; checkbox is actually selected</a>. Sometimes the connector shows as &#8220;connected&#8221; in the portal but isn&#8217;t fully enabled. If this checkbox isn&#8217;t selected, CloudAppEvents will be empty or partial &#8212; and there&#8217;s no error, no alert, and no indication that anything is wrong. Microsoft&#8217;s own documentation confirms this: &#8220;If your organization hasn&#8217;t deployed the service in Microsoft Defender XDR, queries that use the table aren&#8217;t going to work or return any results.&#8221;</p><p>Jeffrey Appel&#8217;s <a href="https://jeffreyappel.nl/2025-microsoft-defender-optimization-configuration-cheat-sheet/">2025 Microsoft Defender Optimization &amp; Configuration Cheat Sheet</a> flagged this as one of the settings he still sees overlooked daily. His advice: if you have an MDCA license and haven&#8217;t verified the connector, do it now.</p><h3><strong>Exchange Online PowerShell</strong></h3><p>Exchange Powershell is not a log source you ingest into a SIEM, but it&#8217;s the only place where you can verify what&#8217;s <em>currently</em> being logged at the per-user level for mailbox settings. The <code>Get-Mailbox</code> cmdlet with its audit properties is the only way to see the actual audit action set for a given mailbox across Owner, Delegate, and Admin sign-in types.</p><p>The <a href="https://learn.microsoft.com/en-us/purview/audit-mailboxes">DefaultAuditSet mechanism</a> is supposed to handle this automatically &#8212; but if a mailbox has been explicitly customized (even once, even years ago), it falls out of the default management path. New audit actions won&#8217;t be added. The only way to know is to check, and the only way to check is PowerShell.</p><p>This is also where you verify that the tenant-level <code>UnifiedAuditLogIngestionEnabled</code> property is set to <code>True</code> &#8212; but <a href="https://www.reddit.com/r/Office365/comments/1ccp0qy/getadminauditlogconfig_returns_false_negative_for/">even that has a gotcha</a>. The <code>Get-AdminAuditLogConfig</code> cmdlet is available in both Exchange Online PowerShell and Security &amp; Compliance PowerShell, but the <code>UnifiedAuditLogIngestionEnabled</code> property <em>always returns False</em> in Security &amp; Compliance PowerShell, even when auditing is actually on. You have to run it in Exchange Online PowerShell specifically.</p><p><strong>What about detecting changes to these settings?</strong> There&#8217;s an important distinction here between <em>reading current state</em> and <em>detecting changes</em>. PowerShell is the only tool for the former &#8212; but the latter is a detection engineering problem, and the telemetry does exist in the UAL.</p><h3><strong>Management Activity API Subscriptions</strong></h3><p>The <a href="https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference">Office 365 Management Activity API</a> is the programmatic interface that most SIEMs use to pull audit data. It has five content types:</p><ul><li><p><strong>Audit.Exchange</strong> &#8212; Mail and calendaring events</p></li><li><p><strong>Audit.SharePoint</strong> &#8212; SharePoint and OneDrive events</p></li><li><p><strong>Audit.AzureActiveDirectory</strong> &#8212; Entra ID events</p></li><li><p><strong>Audit.General</strong> &#8212; Everything else: Teams, Power Platform, Copilot, Defender audit trails, and more</p></li><li><p><strong>DLP.All</strong> &#8212; Data loss prevention events</p></li></ul><p>Each content type requires an explicit subscription. Subscriptions persist once started, but there&#8217;s no health monitoring, no status dashboard, and no notification if a subscription fails or stops delivering data.</p><p>Here&#8217;s the configuration gap that catches people: most teams subscribe to Exchange, SharePoint, and AzureActiveDirectory &#8212; the obvious ones. They forget Audit.General. And Audit.General is where Teams data lives, where Power Platform events live, where Copilot activity lands, and where Defender XDR&#8217;s own audit trails flow. If you&#8217;re not subscribed to Audit.General, entire workloads are invisible to your SIEM.</p><p><strong>The key takeaway from this section:</strong> Five separate configuration surfaces. Four different admin portals. Five different RBAC requirements. No unified view of &#8220;is my auditing fully enabled?&#8221; This fragmentation is the first blind spot &#8212; and it&#8217;s architectural, not operational. You can&#8217;t solve it with better SOC processes. You have to know the surfaces exist and check each one independently.</p><h2><strong>What the Actual Log Tables Are</strong></h2><p>Once telemetry is configured, it flows into tables. But the word &#8220;flows&#8221; does a lot of work in that sentence, because these aren&#8217;t different views of the same data. They&#8217;re different pipes with different schemas, different coverage, different retention, and different license gates. A detection engineer writing KQL against one table is seeing a fundamentally different slice of reality than one writing against another.</p><p><strong>A note on naming</strong>: the table names below are Sentinel and Defender XDR names, because that&#8217;s where the schema and coverage documentation lives. If you&#8217;re on a non-Microsoft SIEM, the underlying data is the same &#8212; the Management Activity API is a REST API that Splunk, Elastic, and every other SIEM polls directly; Entra ID Diagnostic Settings can route to an Event Hub for any platform; and the Defender XDR Streaming API can export XDR tables to Event Hub or Storage. The coverage gaps, schema limitations, and retention constraints documented here are properties of the <em>data sources</em>, not the SIEM. They apply regardless of where the data lands.</p><p>Here&#8217;s what actually exists:</p><h3><strong>OfficeActivity (Sentinel)</strong></h3><ul><li><p><strong>Source:</strong> Management Activity API &#8594; Sentinel&#8217;s Office 365 data connector </p></li><li><p><strong>License gate:</strong> Microsoft Sentinel + M365 connector (free to ingest) </p></li><li><p><strong>Retention:</strong> Workspace-configured (typically 90 days default, extendable) </p></li><li><p><strong>Coverage:</strong> Exchange, Teams, SharePoint, OneDrive &#8212; but only the workloads that flow through the Management Activity API content types you&#8217;ve subscribed to. Not Audit.General workloads unless you&#8217;ve set up a custom ingestion path.</p></li></ul><p>OfficeActivity is where most Sentinel users start, because the Office 365 data connector is free and obvious &#8212; and non-Microsoft SIEMs land the same data by polling the Management Activity API directly. But it has a significant schema limitation: it doesn&#8217;t include the <code>OperationCount</code> field. Microsoft uses this field to flag aggregated events &#8212; where a single audit record actually represents multiple operations. In <a href="https://kqlquery.com/posts/unified-audit-logs-coverage-gaps/">Bert-Jan Pals&#8217; testing</a>, more than one-third of events were aggregated. Without <code>OperationCount</code>, you can&#8217;t distinguish a single event from a batch. Your event counts are wrong. Your thresholds are wrong. And you have no way to know from the data itself.</p><p>In Bert-Jan&#8217;s coverage testing against <a href="https://kqlquery.com/posts/unified-audit-logs-coverage-gaps/">191 unique activities</a> performed in a default-configured tenant, OfficeActivity captured roughly 40% of them.</p><h3><strong>CloudAppEvents (Defender XDR)</strong></h3><ul><li><p><strong>Source:</strong> MDCA M365 connector </p></li><li><p><strong>License gate:</strong> E5 Security, or standalone MDCA license </p></li><li><p><strong>Retention:</strong> 30 days in Defender XDR (can be extended with Sentinel Data Lake ingestion) </p></li><li><p><strong>Coverage:</strong> Broader than OfficeActivity. Absorbing more workloads over time &#8212; Copilot activity, Defender XDR audit trails, Sentinel platform operations. Bert-Jan&#8217;s testing showed approximately 89% coverage of the 191 tested activities.</p></li></ul><p>CloudAppEvents is increasingly where Microsoft is consolidating M365 audit visibility in the Defender XDR ecosystem. But it has its own gaps. It doesn&#8217;t include <code>UserLoggedIn</code> or <code>UserLoginFailed</code> &#8212; those come through the sign-in log tables (more on those below). It has gaps in some Exchange activities compared to what the UAL captures. And it includes at least one event &#8212; <code>Broke sharing inheritance</code> for OneDrive &#8212; that doesn&#8217;t appear in the UAL at all.</p><p>That last point is important: the relationship between CloudAppEvents and the UAL isn&#8217;t strictly &#8220;CloudAppEvents is a subset.&#8221; They&#8217;re overlapping but distinct data sources. You can&#8217;t assume one is a superset of the other.</p><h3><strong>EntraIdSignInEvents (Defender XDR)</strong></h3><ul><li><p><strong>Source:</strong> Entra ID sign-in logs, surfaced in Defender XDR </p></li><li><p><strong>License gate:</strong> Entra ID P2</p></li><li><p> <strong>Retention:</strong> 30 days in Defender XDR </p></li><li><p><strong>Coverage:</strong> Interactive and non-interactive user sign-ins &#8212; the authentication telemetry that CloudAppEvents doesn&#8217;t capture.</p></li></ul><p>This table <a href="https://learn.microsoft.com/en-us/defender-xdr/whats-new">went GA in February 2026</a>, replacing the former <code>AADSignInEventsBeta</code>. If your KQL queries or custom detection rules still reference the old table name, they should have been auto-migrated &#8212; but verify. The rename happened on <a href="https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-schema-changes">December 9, 2025</a>, with a 30-day coexistence period.</p><p>In Sentinel, the equivalent is the <code>SigninLogs</code> table, which flows through Entra ID Diagnostic Settings and requires at least an Entra ID P1 license. These are parallel pipelines to the same underlying data &#8212; but with different schemas, different enrichment, and different retention depending on your configuration.</p><h3><strong>EntraIdSpnSignInEvents (Defender XDR)</strong></h3><ul><li><p><strong>Source:</strong> Entra ID service principal and managed identity sign-in logs </p></li><li><p><strong>License gate:</strong> Entra ID P2 </p></li><li><p><strong>Retention:</strong> 30 days in Defender XDR</p></li></ul><p>This table also <a href="https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-entraidspnsigninevents-table">went GA in February 2026</a>. It&#8217;s the Defender XDR equivalent of <code>AADServicePrincipalSignInLogs</code> in Sentinel &#8212; and it covers the identity types I described in Problem 2 of the first post. Service principals and managed identities authenticate through paths that most detection rules never touch. This table is where that telemetry lives in the XDR ecosystem.</p><p>If you built your authentication detections against <code>EntraIdSignInEvents</code> (or its predecessor) and stopped there, you&#8217;re blind to service principal authentication. Same access, different table, no alert.</p><h3><strong>GraphApiAuditEvents (Defender XDR)</strong></h3><ul><li><p><strong>Source:</strong> Automatic with Defender XDR </p></li><li><p><strong>License gate:</strong> Included with Defender XDR (no additional cost) </p></li><li><p><strong>Retention:</strong> 30 days </p></li><li><p><strong>Coverage:</strong> Microsoft Graph API requests made against tenant resources &#8212; who called what endpoint, when, from where, and what the response was.</p></li></ul><p>This is one of the most significant recent additions to the M365 detection surface. GraphApiAuditEvents entered public preview in July 2025 and <a href="https://learn.microsoft.com/en-us/defender-xdr/whats-new">went GA in February 2026</a>. It&#8217;s essentially a free version of the <code>MicrosoftGraphActivityLogs</code> table in Sentinel &#8212; which is valuable for detection but expensive to ingest due to high log volume.</p><p>The trade-off is schema depth. <a href="https://kqlquery.com/posts/graphapiauditevents/">MicrosoftGraphActivityLogs has 33 columns; GraphApiAuditEvents has 19.</a> The fields that are missing matter for detection engineers: <code>DeviceId</code> (the device from which the Graph API call was made) and <code>SessionId</code> (which lets you correlate Graph activity to sign-in sessions) are both absent. The <code>UserId</code> and <code>ServicePrincipalId</code> fields from MicrosoftGraphActivityLogs have been concatenated into a single <code>AccountObjectId</code> column, which simplifies some queries but loses the ability to immediately distinguish human vs. application callers.</p><p><a href="https://cloudbrothers.info/en/detect-threats-graphapiauditevents-part-3/">Fabian Bader at Cloudbrothers</a> documented an additional wrinkle when the table launched: several columns listed in Microsoft&#8217;s documentation weren&#8217;t actually present in the data. The schema was aspirational, not actual. This is exactly the kind of silent gap that erodes detection confidence &#8212; your query doesn&#8217;t error, it just returns null for fields you expected to be populated.</p><p>GraphApiAuditEvents cannot currently be forwarded to Sentinel to extend its retention beyond 30 days (though Sentinel Data Lake ingestion for Defender XDR tables is now GA, so this may change). For incident response, 30 days of Graph API visibility is better than zero &#8212; but if you need historical depth, MicrosoftGraphActivityLogs in Sentinel remains the paid option.</p><h3><strong>MicrosoftGraphActivityLogs (Sentinel)</strong></h3><ul><li><p><strong>Source:</strong> <a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/howto-configure-diagnostic-settings">Entra ID Diagnostic Settings</a> </p></li><li><p><strong>License gate:</strong> Entra ID P1 + Sentinel </p></li><li><p><strong>Retention:</strong> Workspace-configured </p></li><li><p><strong>Coverage:</strong> Same underlying data as GraphApiAuditEvents, but with the full 33-column schema including DeviceId and SessionId.</p></li></ul><p>This table is expensive. The volume of Graph API traffic in any active tenant is enormous, and every request generates a log entry. But for organizations doing serious incident response or threat hunting against application-layer attacks &#8212; compromised OAuth apps, token theft, Graph API abuse by tools like AzureHound or GraphRunner &#8212; it&#8217;s uniquely valuable. The cost is why GraphApiAuditEvents exists: Microsoft recognized that most organizations couldn&#8217;t justify the Sentinel ingestion bill for these logs, so they shipped a lighter version for free in Defender XDR.</p><h3><strong>Entra ID Log Tables (Sentinel / Log Analytics)</strong></h3><ul><li><p><strong>Source:</strong> <a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/howto-configure-diagnostic-settings">Entra ID Diagnostic Settings</a> &#8594; Log Analytics workspace (or Event Hub, or Storage Account) </p></li><li><p><strong>License gate:</strong> Entra ID Free or E3+ for <code>AuditLogs</code>; Entra ID P1 or P2 for sign-in log tables </p></li><li><p><strong>Retention:</strong> Workspace-configured when sent to Log Analytics; destination-dependent otherwise </p></li><li><p><strong>Coverage:</strong> Authentication activity, directory changes, provisioning events, risk detections, and Graph API activity &#8212; the identity plane telemetry that doesn&#8217;t flow through the UAL or Management Activity API.</p></li></ul><p>Earlier in this post I described Entra ID Diagnostic Settings as one of the five configuration surfaces. Here&#8217;s where that pipeline&#8217;s data actually lands.</p><p>When you configure a diagnostic setting in Entra ID, you select which log categories to export and where to send them. The destination matters more than most people realize, because it determines what you can <em>do</em> with the data. There are three options:</p><p>A <strong>Log Analytics workspace</strong> is what Sentinel queries. When you send Entra ID logs to a Log Analytics workspace &#8212; whether directly through diagnostic settings or through the <a href="https://learn.microsoft.com/en-us/azure/sentinel/connect-azure-active-directory">Sentinel Entra ID data connector</a> (which configures the same underlying diagnostic settings) &#8212; they land in specific tables that you can write KQL against, build detection rules on, and hunt through. This is the path that makes the data actionable for detection engineering.</p><p>An <strong>Event Hub</strong> is the path for non-Microsoft SIEMs. If you&#8217;re running Splunk, Elastic, Sumo Logic, or any other platform, this is how Entra ID telemetry gets to your SIEM. The data is the same &#8212; same log categories, same underlying events &#8212; but the format, schema, and enrichment depend entirely on how your SIEM&#8217;s ingestion pipeline handles Event Hub messages. This is the equivalent path for organizations not in the Microsoft Sentinel ecosystem, and it&#8217;s just as critical to configure. If you&#8217;re on Splunk and haven&#8217;t set up an Event Hub destination for Entra ID diagnostic settings, you have the same blind spot as a Sentinel shop that never enabled the data connector.</p><p>A <strong>Storage Account</strong> is primarily for archival and compliance &#8212; long-term retention where query latency isn&#8217;t a concern.</p><p>You can configure multiple diagnostic settings simultaneously, sending the same log categories to more than one destination. But each destination is independent: nothing about sending logs to an Event Hub guarantees they&#8217;re also flowing to Log Analytics, or vice versa.</p><p><strong>The Log Analytics tables.</strong> When the destination is a Log Analytics workspace, each diagnostic settings category maps to a specific table &#8212; but the category names and table names don&#8217;t match, which is a source of confusion. Here&#8217;s the mapping for the tables that matter most for detection:</p><p>The <code>SigninLogs</code> table captures interactive user sign-ins. This is the richest authentication telemetry available: conditional access evaluation results, MFA details, device information, client app, location, risk scoring. If you&#8217;re building detections around authentication anomalies, compromised credentials, or conditional access bypass, this is where the signal lives. Requires Entra ID P1 or higher to export.</p><p><code>AADNonInteractiveUserSignInLogs</code> captures sign-ins performed by a client on behalf of a user &#8212; token refreshes, background app activity, SSO sessions. These are high-volume (often dramatically higher than interactive sign-ins) and frequently overlooked. An attacker with a stolen refresh token generates non-interactive sign-ins, not interactive ones. If you&#8217;re only monitoring <code>SigninLogs</code>, you&#8217;re watching the front door while the side entrance is unmonitored. Also requires P1+.</p><p><code>AADServicePrincipalSignInLogs</code> covers sign-ins by apps and service principals &#8212; the identity types I described in Problem 2 of the first post. This is the Sentinel equivalent of <code>EntraIdSpnSignInEvents</code> in Defender XDR. Requires P1+.</p><p><code>AADManagedIdentitySignInLogs</code> covers sign-ins by Azure managed identities. Lower volume than service principal logs in most tenants, but critical if your environment uses managed identities for automation or cross-service access. Requires P1+.</p><p><code>AuditLogs</code> captures directory changes &#8212; user and group management, application registration changes, role assignments, policy modifications. This is the only table in this group that doesn&#8217;t require a premium license; it can be exported with Entra ID Free or any E3+ plan.</p><p>(I&#8217;ve already covered <code>MicrosoftGraphActivityLogs</code> in its own section above &#8212; it also flows through this same diagnostic settings pipeline.)</p><p><strong>A naming trap worth flagging:</strong> the category names you select in diagnostic settings don&#8217;t match the table names in Log Analytics. <code>NonInteractiveUserSignInLogs</code> in the diagnostic settings UI becomes <code>AADNonInteractiveUserSignInLogs</code> in Log Analytics. <code>ServicePrincipalSignInLogs</code> becomes <code>AADServicePrincipalSignInLogs</code>. Same data, different names depending on where you&#8217;re looking. Thomas Naunheim <a href="https://www.cloud-architekt.net/auditing-of-msi-and-service-principals/">documented this mismatch</a> &#8212; it&#8217;s the kind of thing that causes silent query failures if you&#8217;re writing KQL against the diagnostic settings category name instead of the actual table name.</p><p><strong>The detection engineering gap this fills.</strong> The earlier sections of this post covered <code>EntraIdSignInEvents</code> and <code>EntraIdSpnSignInEvents</code> in Defender XDR. Those tables are the XDR-side view of the same underlying authentication data. But there are meaningful differences: the Sentinel tables have richer schemas (full conditional access policy evaluation details, device compliance state, authentication method details), workspace-configured retention instead of the fixed 30-day window in Defender XDR, and &#8212; critically &#8212; they split non-interactive and managed identity sign-ins into their own dedicated tables rather than combining identity types.</p><p>If your detection strategy lives in Sentinel, these are the tables your authentication rules should be built against. If your detection strategy lives in Defender XDR, the <code>EntraId*</code> tables covered earlier are the equivalent. If you&#8217;re on a non-Microsoft SIEM, the Event Hub path delivers this same data &#8212; but only if someone configured the diagnostic setting, selected the right log categories, and pointed them at your Event Hub. That&#8217;s three things that can be wrong, and none of them will tell you if they are.</p><h3><strong>Purview Audit Search (UAL Direct)</strong></h3><ul><li><p><strong>Source:</strong> Direct query against the Unified Audit Log </p></li><li><p><strong>License gate:</strong> E3+ for Standard, E5 for Premium </p></li><li><p><strong>Retention:</strong> 180 days (Standard), 1 year (Premium), up to 10 years with add-on </p></li><li><p><strong>Coverage:</strong> ~99.5% of all audited activities in Bert-Jan&#8217;s testing &#8212; the most complete view available.</p></li></ul><p>This is not a SIEM table. You can&#8217;t write detection rules against it. You can&#8217;t set up automated alerts. It&#8217;s a query interface &#8212; available through the Purview portal, through <code>Search-UnifiedAuditLog</code> in Exchange Online PowerShell, or through the <a href="https://learn.microsoft.com/en-us/purview/audit-solutions-overview">Purview Audit Search Graph API</a> (which Microsoft moved back to beta in April 2025 to address stability issues, and has not yet returned to v1.0 GA).</p><p>But for incident response, it&#8217;s the canonical source. When you need to know what actually happened &#8212; not what made it through your ingestion pipeline &#8212; the UAL is where you go. Tools like the <a href="https://github.com/invictus-ir/Microsoft-Extractor-Suite">Invictus Incident Response Microsoft Extractor Suite</a> are built specifically to extract data from this source when your SIEM tables aren&#8217;t enough.</p><h3><strong>The Coverage Gap at a Glance</strong></h3><p>Bert-Jan Pals&#8217; <a href="https://kqlquery.com/posts/unified-audit-logs-coverage-gaps/">research</a> quantified what practitioners had suspected: of 191 unique activities tested in a default-configured tenant, OfficeActivity captured roughly 40%. CloudAppEvents captured roughly 89%. Purview Audit Search captured roughly 99.5%. None reached 100%.</p><p>Those numbers should be uncomfortable. If you&#8217;re running detections exclusively against OfficeActivity, you have visibility into less than half of what&#8217;s happening. If you&#8217;re on CloudAppEvents, you&#8217;re in better shape &#8212; but you&#8217;re still missing roughly one in ten events compared to what the UAL sees.</p><p>And none of these tools log everything. Bert-Jan&#8217;s testing was explicit about this: &#8220;None of the acquisition methods get 100% coverage on the performed activities, meaning you need to combine acquisition methods to get a complete overview.&#8221;</p><h2><strong>How Collection Actually Works</strong></h2><p>Let&#8217;s trace a concrete example to make this real. A user accesses a mailbox item &#8212; the <code>MailItemsAccessed</code> event. This is the exact event that was central to the Storm-0558 detection: the State Department&#8217;s SOC built custom alerting on MailItemsAccessed events and caught an anomalous AppID accessing mailboxes. Without this event, the compromise may have gone undetected.</p><p>Here&#8217;s what happens when that action occurs:</p><p><strong>Step 1: The user action occurs.</strong> Exchange Online generates an audit record.</p><p><strong>Step 2: The record enters the UAL.</strong> It becomes available via Purview Audit Search &#8212; <em>if</em> auditing is enabled for that user, <em>if</em> MailItemsAccessed is in their mailbox audit action set, and <em>if</em> the event isn&#8217;t being throttled (Microsoft throttles MailItemsAccessed if more than 1,000 records are generated on a mailbox within 24 hours &#8212; no logging for 24 hours after throttling).</p><p><strong>Step 3: From the UAL, data flows outward via multiple diverging paths:</strong></p><p><strong>Path A: Management Activity API &#8594; Your SIEM.</strong> Your SIEM polls the Management Activity API for content blobs from the Audit.Exchange subscription. The content is available for 7 days &#8212; after that, the blobs expire with no backfill. Typical latency is 60-90 minutes, but Microsoft <a href="https://learn.microsoft.com/en-us/office/office-365-management-api/troubleshooting-the-office-365-management-activity-api">explicitly does not commit</a> to a specific delivery time: &#8220;some issues may arise upstream from the Audit service and are unavoidable.&#8221; There&#8217;s no server-side filtering &#8212; you get all of Audit.Exchange, not just the events you care about. And duplicates are expected: &#8220;the Office 365 Management Activity API does not have this de-duplication feature... It is the responsibility of the SIEM solution to implement logic to remove such duplicated events.&#8221; In Sentinel, this lands in the <strong>OfficeActivity</strong> table.</p><p><strong>Path B: MDCA connector &#8594; CloudAppEvents.</strong> The same underlying event flows through the Defender for Cloud Apps M365 connector into CloudAppEvents. Different enrichment, different schema, and approximately 89% coverage &#8212; meaning some events present in the UAL won&#8217;t appear here. The inverse is also true: some events appear in CloudAppEvents that aren&#8217;t in the UAL.</p><p><strong>Path C: <a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/howto-configure-diagnostic-settings">Entra ID Diagnostic Settings</a> &#8594; Sign-in logs.</strong> This path doesn&#8217;t carry the MailItemsAccessed event &#8212; it&#8217;s only for Entra ID and Graph API activity, not Exchange/SharePoint/Teams. But it&#8217;s the path that carries the authentication context that <em>surrounds</em> the mailbox access: who the user was, how they authenticated, whether conditional access evaluated the session. If Path C isn&#8217;t configured, you might see the MailItemsAccessed event in your SIEM but have no authentication context to correlate it with.</p><p><strong>The critical insight:</strong> Your SIEM is not querying the UAL. It&#8217;s querying whatever subset of the UAL made it through one specific pipe. And different pipes have different coverage, different latency, different retention, and different schemas. A detection built against OfficeActivity is evaluating a fundamentally different data set than one built against CloudAppEvents, even when both are nominally covering &#8220;Exchange audit events.&#8221;</p><h2><strong>Where the Blind Spots Hide</strong></h2><p>You now have the map. Here&#8217;s what&#8217;s missing at each layer.</p><h3><strong>Configuration Blind Spots</strong></h3><p><strong>UAL enabled, but per-mailbox actions not in DefaultAuditSet.</strong> If anyone has ever customized a mailbox&#8217;s audit configuration, new events like MailItemsAccessed won&#8217;t be added automatically. The only way to verify is PowerShell, per mailbox. There&#8217;s no bulk verification tool in any admin portal.</p><p><strong>MDCA connector &#8220;connected&#8221; but M365 Activities not fully enabled.</strong> CloudAppEvents stays empty. No error. No alert. Just silence.</p><p><strong>Management Activity API subscribed to Exchange/SharePoint/AzureAD but not Audit.General.</strong> You&#8217;re missing Teams, Power Platform, Copilot, Defender XDR audit trails &#8212; entire workloads are invisible.</p><p><strong><a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/howto-configure-diagnostic-settings">Entra ID Diagnostic Settings</a> not configured.</strong> This is a big one. No sign-in logs flowing to your SIEM. Your authentication visibility is limited to whatever the UAL captures through the Management Activity API &#8212; which doesn&#8217;t include the rich sign-in log schema with conditional access evaluation, MFA details, device information, or risk scoring.</p><h3><strong>Coverage Blind Spots</strong></h3><p><strong>OfficeActivity is approximately 40% coverage.</strong> For detection engineering, that means more than half of audited activities in a default-configured tenant are invisible to rules running against this table.</p><p><strong>OfficeActivity is missing OperationCount.</strong> You cannot distinguish aggregated events from single events. In Bert-Jan&#8217;s testing, more than a third of events were aggregated. Your event counts, your detection thresholds, and your frequency-based rules are operating on inflated or deflated numbers, and you have no way to tell which.</p><p><strong>CloudAppEvents is approximately 89% coverage.</strong> Better, but roughly one in ten events compared to the UAL is missing. And it doesn&#8217;t include UserLoggedIn/UserLoginFailed &#8212; those come from the sign-in log tables.</p><p><strong>GraphApiAuditEvents is missing DeviceId and SessionId compared to MicrosoftGraphActivityLogs.</strong> If your detection logic depends on correlating Graph API activity to specific devices or sign-in sessions, the free table won&#8217;t support it.</p><p><strong>Neither OfficeActivity nor CloudAppEvents captures everything in the UAL.</strong> And the UAL itself doesn&#8217;t capture everything &#8212; it&#8217;s at 99.5%, not 100%. The only event Bert-Jan found exclusively in CloudAppEvents and not in the UAL was <code>Broke sharing inheritance</code> for OneDrive. The relationship between these data sources is overlapping, not hierarchical.</p><h3><strong>Retention Blind Spots</strong></h3><p><strong>Management Activity API content blobs expire after 7 days.</strong> If your SIEM goes down, if your ingestion pipeline breaks, if there&#8217;s a credential issue with your API subscription &#8212; those events are gone. There&#8217;s no backfill mechanism.</p><p><strong>GraphApiAuditEvents: 30 days, and currently cannot be extended by forwarding to Sentinel.</strong> For incident response involving Graph API abuse, 30 days may not be enough. MicrosoftGraphActivityLogs in Sentinel gives you workspace-configured retention, but at significant cost.</p><p><strong>CloudAppEvents: 30 days in Defender XDR.</strong> Same constraint. Sentinel Data Lake ingestion for Defender XDR tables is now GA, which may help extend this &#8212; but it&#8217;s an additional configuration step that most organizations haven&#8217;t implemented.</p><p><strong>Your SIEM retention may be longer than all of these</strong> &#8212; but it only covers data that actually made it into the pipe. Retention is meaningless for events that were never collected.</p><h3><strong>The Meta Blind Spot</strong></h3><p>This is the one that makes all the others invisible: <strong>there is no health monitoring for any of this.</strong></p><p>No alert when a Management Activity API subscription fails. No dashboard showing &#8220;here&#8217;s what&#8217;s flowing and here&#8217;s what&#8217;s not.&#8221; No completeness verification. No cross-check mechanism to confirm your SIEM received all events the UAL generated. No way to distinguish &#8220;nothing happened&#8221; from &#8220;we stopped collecting.&#8221;</p><p>Microsoft provides no native solution for telemetry health monitoring across these ingestion paths. The <a href="https://www.cisa.gov/sites/default/files/2025-01/microsoft-expanded-cloud-logs-implementation-playbook-508c.pdf">CISA playbook</a> provides manual verification steps &#8212; PowerShell commands to check UAL status, steps to verify mailbox audit configuration &#8212; but these are point-in-time checks, not continuous monitoring. The moment after you verify, configuration can drift, subscriptions can break, and you&#8217;ll hear nothing about it until an incident investigation reveals the gap.</p><p>This is the foundational risk underneath everything else. You can configure all five surfaces correctly. You can understand which tables cover what. You can trace every collection path. But without continuous verification that data is actually flowing, your entire detection pipeline is built on trust. And in SaaS environments &#8212; where the vendor controls the instrumentation, the schemas, and the delivery &#8212; trust is not a detection strategy.</p><h2><strong>What to Do With This</strong></h2><p>I&#8217;ll go deeper on methodology in a future post, but here&#8217;s what you should take away today:</p><p><strong>Verify your specific ingestion path.</strong> Don&#8217;t assume you know which pipe you&#8217;re on. Check whether it&#8217;s the Management Activity API, the MDCA connector, Entra ID Diagnostic Settings, or some combination. Know exactly which tables your detections query and what those tables actually contain.</p><p><strong>Don&#8217;t assume OfficeActivity = UAL. Don&#8217;t assume CloudAppEvents = UAL.</strong> These are different pipes with different coverage. If you built detections against OfficeActivity and haven&#8217;t cross-checked against CloudAppEvents or Purview Audit Search, your coverage map has unknown gaps.</p><p><strong>Check Audit.General.</strong> If your Management Activity API subscriptions don&#8217;t include it, you&#8217;re missing Teams, Power Platform, Copilot, and Defender audit activity entirely.</p><p><strong>Verify <a href="https://learn.microsoft.com/en-us/purview/audit-mailboxes">per-mailbox audit configuration</a> with PowerShell.</strong> Especially for high-value mailboxes &#8212; executives, finance, legal, anyone likely to be targeted. The DefaultAuditSet mechanism is good but brittle: a single historical customization takes a mailbox out of automatic management permanently.</p><p><strong>If you&#8217;re doing incident response, always supplement SIEM data with direct UAL extraction.</strong> The <a href="https://github.com/invictus-ir/Microsoft-Extractor-Suite">Invictus Incident Response Microsoft Extractor Suite</a>, Purview Audit Search (including the Graph API in beta), or direct PowerShell extraction. Your SIEM shows you what made it through the pipe. The UAL shows you what actually happened.</p><p><strong>Consider <a href="https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-graphapiauditevents-table">GraphApiAuditEvents</a> as a baseline for Graph API visibility.</strong> It went GA in February 2026 and it&#8217;s free with Defender XDR. The schema is thinner than MicrosoftGraphActivityLogs, but 19 columns of Graph API telemetry for free is better than zero visibility. If you&#8217;re investigating OAuth app compromise, token theft, or reconnaissance via Graph enumeration tools, this table is now your starting point.</p><p>Next post, we&#8217;ll go deeper on one of the silent failure modes that makes all of this worse &#8212; and show you what it looks like when collection breaks without telling you.</p><div><hr></div><h3><strong>References &amp; Credits</strong></h3><ul><li><p><strong>Bert-Jan Pals</strong> &#8212; <a href="https://kqlquery.com/posts/unified-audit-logs-coverage-gaps/">&#8220;UAL = Unaligned Activity Logs&#8221;</a> (kqlquery.com, November 2024). The coverage testing against 191 unique activities and the OfficeActivity/CloudAppEvents/Purview Audit comparison that quantifies the gaps discussed throughout this post.</p></li><li><p><strong>Bert-Jan Pals</strong> &#8212; <a href="https://kqlquery.com/posts/graphapiauditevents/">&#8220;GraphApiAuditEvents: The new Graph API Logs&#8221;</a> (kqlquery.com, August 2025). Schema comparison between GraphApiAuditEvents and MicrosoftGraphActivityLogs, including the missing DeviceId/SessionId fields and retention constraints.</p></li><li><p><strong>Fabian Bader (Cloudbrothers)</strong> &#8212; <a href="https://cloudbrothers.info/en/detect-threats-graphapiauditevents-part-3/">&#8220;Detect threats using GraphAPIAuditEvents - Part 3&#8221;</a> (cloudbrothers.info, August 2025). Detection engineering with the new table, including documentation of schema gaps at launch and Thomas Naunheim&#8217;s comparison table.</p></li><li><p><strong>CISA</strong> &#8212; <a href="https://www.cisa.gov/sites/default/files/2025-01/microsoft-expanded-cloud-logs-implementation-playbook-508c.pdf">&#8220;Microsoft Expanded Cloud Logs Implementation Playbook&#8221;</a> (January 2025). The practitioner playbook for operationalizing expanded audit logs, including mailbox-level verification procedures and the context on Storm-0558 detection.</p></li><li><p><strong>Microsoft Learn</strong> &#8212; Defender XDR <a href="https://learn.microsoft.com/en-us/defender-xdr/whats-new">&#8220;What&#8217;s new&#8221;</a> documentation confirming February 2026 GA for EntraIdSignInEvents, EntraIdSpnSignInEvents, and GraphApiAuditEvents. <a href="https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-schema-changes">Naming changes documentation</a> for the December 2025 AADSignInEventsBeta &#8594; EntraIdSignInEvents migration. Individual table reference pages for schema details.</p></li><li><p><strong>Microsoft Learn</strong> &#8212; <a href="https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference">Office 365 Management Activity API reference</a> and <a href="https://learn.microsoft.com/en-us/office/office-365-management-api/troubleshooting-the-office-365-management-activity-api">troubleshooting documentation</a>, confirming content blob expiry, latency expectations, and duplicate event behavior.</p></li><li><p><strong>Microsoft Learn</strong> &#8212; <a href="https://learn.microsoft.com/en-us/purview/audit-log-enable-disable">&#8220;Turn auditing on or off&#8221;</a> and <a href="https://learn.microsoft.com/en-us/purview/audit-search">&#8220;Search the audit log&#8221;</a> documentation for UAL enablement status, license requirements, and retention changes.</p></li><li><p><strong>Microsoft Learn</strong> &#8212; <a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-diagnostic-settings-logs-options">&#8220;Logs available for streaming from Microsoft Entra ID&#8221;</a> for the complete list of diagnostic settings log categories and their descriptions. <a href="https://learn.microsoft.com/en-us/azure/sentinel/connect-azure-active-directory">&#8220;Send Microsoft Entra ID data to Microsoft Sentinel&#8221;</a> for the Sentinel data connector configuration and table mapping.</p></li><li><p><strong>Jeffrey Appel</strong> &#8212; <a href="https://jeffreyappel.nl/2025-microsoft-defender-optimization-configuration-cheat-sheet/">&#8220;2025 Microsoft Defender Optimization &amp; Configuration Cheat Sheet&#8221;</a> (jeffreyappel.nl, November 2025). Configuration items that are still commonly overlooked, including MDCA connector state.</p></li><li><p><strong>Thomas Naunheim</strong> &#8212; <a href="https://www.cloud-architekt.net/auditing-of-msi-and-service-principals/">&#8220;Sign-in logs and auditing of Managed Identities and Service Principals&#8221;</a> (cloud-architekt.net). Documentation of the naming mismatch between Entra ID diagnostic settings categories and Log Analytics table names, and early coverage of service principal and managed identity sign-in log tables. Also referenced by both Bert-Jan Pals and Fabian Bader for schema comparison work between GraphApiAuditEvents and MicrosoftGraphActivityLogs.</p></li><li><p><strong>Practical365 / Tony Redmond</strong> &#8212; <a href="https://practical365.com/auditlog-query-api-deeper-look/">Coverage of the Purview Audit Search Graph API&#8217;s move back to beta</a> (April 2025) and ongoing practitioner perspective on M365 audit capabilities.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Control Plane is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[You’re Probably Flying Blind]]></title><description><![CDATA[Part 1: The four-layer detection validation problem in SaaS and cloud]]></description><link>https://lydiagraslie.substack.com/p/youre-probably-flying-blind</link><guid isPermaLink="false">https://lydiagraslie.substack.com/p/youre-probably-flying-blind</guid><dc:creator><![CDATA[Lydia Graslie]]></dc:creator><pubDate>Fri, 27 Feb 2026 13:41:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1uxP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1359446a-3044-462f-bda2-6810ca5c26bf_322x322.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most detection engineering teams believe they have coverage. They&#8217;ve written rules, tuned alerts, maybe even run a tabletop or hired a red team. The results look good. The dashboards are green.</p><p>And then something breaks &#8212; not loudly, not with an alert, but silently. A log source stops delivering. A field shifts position in an array. An API gets deprecated and the attack path you validated six months ago no longer fires the way you tested it. No one notices, because the absence of signal looks exactly like peace.</p><p>This is a problem across all of threat detection. Endpoint teams deal with it. Network teams deal with it. But nowhere is it worse than in SaaS.</p><p>In endpoint or network detection, you control the instrumentation. You deploy the agent. You configure the tap. You own the pipeline from source to SIEM. In SaaS, the vendor controls what gets logged, how it's structured, and when it changes &#8212; but you're still responsible for configuring which of those log sources actually flow into your environment. The problem is that most teams don't know what needs to be configured. The defaults leave gaps. The licensing requirements are buried in documentation. The connectors that look comprehensive aren't. And nobody tells you what you're not collecting.</p><p>That&#8217;s the world this series lives in. The problems I&#8217;m going to lay out exist everywhere in detection engineering to some degree, but SaaS is where they&#8217;re sharpest, least visible, and hardest to compensate for. It&#8217;s not one problem &#8212; it&#8217;s four, and they compound.</p><h2>Problem 1: You might be blind</h2><p>Before anything else matters &#8212; before your detection logic, before your correlation rules, before your threat model &#8212; telemetry has to arrive. And in cloud environments, arrival is not guaranteed.</p><p>Cloud logging configuration is sprawling, fragmented, and fails silently. In SaaS platforms specifically, you&#8217;re often dealing with multiple audit log types that require separate enablement, retention settings that default to short windows, and API-based log retrieval that may or may not be functioning &#8212; all without a single pane telling you what&#8217;s actually flowing. There&#8217;s no error when a log source isn&#8217;t configured. There&#8217;s no alert when audit logs aren&#8217;t being ingested. There&#8217;s just... nothing. And nothing looks exactly like &#8220;nothing happened.&#8221;</p><p>Most organizations have never systematically verified that their telemetry is actually arriving. They assume it is because they&#8217;ve never been told otherwise. That assumption is the foundation everything else is built on, and it&#8217;s unvalidated.</p><p>Here&#8217;s a concrete example from the Microsoft ecosystem.</p><p>When a security team connects Microsoft Sentinel to their Microsoft 365 environment, the first thing most people do is enable the <a href="https://learn.microsoft.com/en-us/azure/sentinel/data-connectors/office-365">Office 365 data connector</a>. It&#8217;s free, it&#8217;s obvious, and it starts flowing SharePoint activity, Exchange admin events, and Teams data into your workspace immediately. Green checkmark. Logs are flowing. Coverage achieved.</p><p>Except you&#8217;re missing the most fundamental identity telemetry in the entire ecosystem: sign-in logs.</p><p>Entra ID sign-in logs &#8212; who authenticated, from where, with what client, whether MFA was enforced, whether conditional access evaluated the session &#8212; require a <a href="https://learn.microsoft.com/en-us/azure/sentinel/connect-azure-active-directory">completely separate data connector</a>, a <a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-diagnostic-settings-logs-options">P1 or P2 license</a>, and manual configuration. They are not included in the Office 365 connector. They are not free to ingest. They don&#8217;t flow by default.</p><p>This means a team can have Sentinel deployed, dashboards built, detection rules running, and still have zero visibility into authentication activity. An attacker signs in with stolen credentials from an anomalous location, and the telemetry that would catch it simply isn&#8217;t there &#8212; not because a detection failed, but because the data was never collected. The dashboard stays green. Nothing fires. And the team has no reason to suspect anything is wrong, because the absence of signal looks exactly like the absence of threat.</p><h2>Problem 2: What you see depends on how you look</h2><p>Assume your logs are flowing. You&#8217;re not blind. Good. Now the next problem: the same malicious action, performed under different conditions, produces different telemetry.</p><p>The same API call made by a user, a service principal, and a managed identity can generate meaningfully different log entries. Fields that exist in one context are absent in another. Values that behave predictably under one identity type behave differently under another. The differences aren&#8217;t cosmetic &#8212; they&#8217;re structural.</p><p>This means a detection built against one execution path will miss the same attack performed through a different one. And most detection teams test exactly one path.</p><p>This isn&#8217;t a logging bug. It&#8217;s a fundamental characteristic of how SaaS and cloud platforms emit telemetry. These platforms have rich, complex identity models &#8212; human users, service accounts, API keys, OAuth apps, managed identities &#8212; and the telemetry surface varies across all of them. Your detection coverage is only as wide as the identity and method combinations you&#8217;ve actually validated.</p><p>Here&#8217;s what this looks like in practice, using Microsoft Entra ID as an example.</p><p>When an interactive user signs into a Microsoft cloud resource, the event lands in the <code>SigninLogs</code> table. It includes conditional access policy evaluation, device information, MFA challenge details, user risk scoring, and client application metadata. It&#8217;s a rich, detailed record &#8212; exactly the kind of telemetry detection engineers love to build rules against.</p><p>When a service principal authenticates to the same resource &#8212; using a client secret or certificate, the way an application or automation workflow would &#8212; that event doesn&#8217;t appear in <code>SigninLogs</code> at all. It lands in a completely separate table: <code>AADServicePrincipalSignInLogs</code>. And the schema is <a href="https://www.cloud-architekt.net/auditing-of-msi-and-service-principals/">materially different</a>. There are no MFA details, because service principals don&#8217;t do MFA. There&#8217;s no device information, because there&#8217;s no device. There&#8217;s no user risk score. The fields that your detection was built to inspect don&#8217;t exist.</p><p>Managed identities? A third table: <code>AADManagedIdentitySignInLogs</code>. Non-interactive user authentication &#8212; a client app refreshing a token on behalf of a user? A fourth: <code>AADNonInteractiveUserSignInLogs</code>.</p><p>That&#8217;s four separate tables for what is conceptually a single event category: &#8220;something authenticated to access a resource.&#8221; A detection rule written against <code>SigninLogs</code> &#8212; which is where most teams start, because it&#8217;s the most visible and well-documented &#8212; will never fire for the same access performed by a service principal, a managed identity, or a non-interactive token refresh. The detection doesn&#8217;t fail. It simply never evaluates the event, because the event is in a table the rule was never pointed at.</p><p>Now consider this from an attacker&#8217;s perspective. If you&#8217;ve compromised a service principal&#8217;s credentials, every action you take authenticates through a path that most organizations&#8217; detection rules were never written to cover. Not because the telemetry doesn&#8217;t exist &#8212; but because it exists somewhere the defenders aren&#8217;t looking.</p><h2>Problem 3: The execution surface shifts</h2><p>APIs change. New endpoints appear. Old ones get deprecated, modified, or replaced. The attack path you validated &#8212; the one your detection was built to catch &#8212; may no longer be the path an attacker would take.</p><p>This isn&#8217;t theoretical. SaaS providers and cloud platforms ship API changes constantly &#8212; often without announcement. A new method for modifying IAM policies, a deprecated endpoint replaced by a v2, a previously undocumented parameter that now exists &#8212; each one potentially creates an untested attack path that your existing detections don&#8217;t cover. And unlike infrastructure you own, you have no visibility into these changes until you discover them yourself.</p><p>Your offensive coverage map has an expiration date. If you&#8217;re not continuously revalidating it, it&#8217;s going stale.</p><p><strong>Example: Microsoft&#8217;s PowerShell module churn</strong></p><p>If you built attack tooling or detection validation scripts against Microsoft 365 in 2022, here&#8217;s what&#8217;s happened to the execution surface underneath you since then.</p><p>The <strong>MSOnline (MSOL)</strong> module &#8212; the original way to manage Azure AD via PowerShell &#8212; was <a href="https://techcommunity.microsoft.com/blog/microsoft-entra-blog/important-update-deprecation-of-azure-ad-powershell-and-msonline-powershell-modu/4094536">deprecated in March 2024</a> and began <a href="https://techcommunity.microsoft.com/blog/microsoft-entra-blog/action-required-msonline-and-azuread-powershell-retirement---2025-info-and-resou/4364991">retiring in April 2025</a>. The <strong>AzureAD</strong> and <strong>AzureADPreview</strong> modules followed the same deprecation timeline, with retirement targeted for Q3 2025. The <strong>Exchange Online PowerShell v1</strong> module died in mid-2023 when Microsoft killed basic authentication, and <strong>v2</strong> was <a href="https://techcommunity.microsoft.com/blog/exchange/announcing-deprecation-of-remote-powershell-rps-protocol-in-exchange-online-powe/3695597">retired months later in July 2023</a> when the Remote PowerShell (RPS) protocol was removed entirely. The <strong>Security &amp; Compliance PowerShell</strong> RPS connection was <a href="https://www.neowin.net/news/remote-powershell-protocol-to-be-deprecated-in-security-and-compliance-powershell-in-july/">deprecated on the same timeline</a>.</p><p>The replacement for the identity modules was the <strong><a href="https://learn.microsoft.com/en-us/powershell/microsoftgraph/overview">Microsoft Graph PowerShell SDK v1</a></strong>, which shipped in 2021. Organizations spent months migrating scripts from MSOL and AzureAD to the new <code>Get-Mg*</code> cmdlets. Then in July 2023, Microsoft released <strong><a href="https://github.com/microsoftgraph/msgraph-sdk-powershell/blob/dev/docs/upgrade-to-v2.md">SDK v2</a></strong> &#8212; which introduced its own set of breaking changes. The <code>Select-MgProfile</code> cmdlet that everyone had just learned to use was removed. Every beta cmdlet was renamed (e.g. <code>Get-MgUser</code> on the beta endpoint became <code>Get-MgBetaUser</code>). The module namespace changed. Every script that touched beta endpoints &#8212; which was most of them, because the v1.0 endpoint didn&#8217;t return properties like <code>AssignedLicenses</code> &#8212; needed to be rewritten <em>again</em>.</p><p>A LinkedIn post from a Microsoft 365 community account captured the sentiment: <a href="https://o365reports.com/2023/07/12/the-term-select-mgprofile-is-not-recognized-error/">&#8220;The Never-Ending Cycle of MS Graph Script Migrations.&#8221;</a> Scripts migrated from AzureAD to Graph SDK v1 now required <em>yet another</em> migration to SDK v2.</p><p>And it&#8217;s not over. <strong><a href="https://learn.microsoft.com/en-us/powershell/entra-powershell/overview">Microsoft Entra PowerShell</a></strong> is currently in preview as another incoming option, while the underlying <strong>Azure AD Graph API</strong> itself was <a href="https://techcommunity.microsoft.com/blog/microsoft-entra-blog/june-2024-update-on-azure-ad-graph-api-retirement/4094534">fully shut down in June 2025</a> after a three-year retirement cycle that was delayed at least four times.</p><p>Here&#8217;s the count: since 2022, at least <strong>seven</strong> Microsoft PowerShell modules or protocols for managing identity and messaging have been deprecated or retired. The replacement itself was deprecated within a year of people migrating to it. If you wrote an attack simulation that used <code>Connect-MsolService</code> to test credential spraying detections, or <code>Set-AzureADUserLicense</code> to simulate license manipulation, that code doesn&#8217;t run anymore. The attack technique still works &#8212; but the <em>execution path</em> through Microsoft&#8217;s tooling has changed underneath it multiple times.</p><p>For a purple team, this means your offensive playbook has a shelf life measured in months, not years. And every time the execution surface shifts, the question isn&#8217;t just &#8220;does our attack still work?&#8221; It&#8217;s &#8220;does the telemetry from this new execution path land in the same place, with the same schema, with the same fields our detection expects?&#8221;</p><p>Which brings us to Problem 4.</p><h2>Problem 4: The detection surface shifts</h2><p>Your detection might be perfectly written. The logic might be flawless. But the data it&#8217;s inspecting no longer looks the way it looked when you wrote the rule. Detection engineers find out when something silently stops firing &#8212; or worse, starts flooding with false positives.</p><p>This is the inverse of Problem 3. That was about the <em>execution</em> surface shifting &#8212; the tools and APIs attackers use. This is about the <em>telemetry</em> surface shifting &#8212; the logs, schemas, and data structures your detections are built on top of. And unlike API deprecations, which at least get blog posts and retirement timelines, telemetry changes are often silent.</p><p><strong>Example: SigninLogs and the invisible multi-record problem</strong></p><p>Here&#8217;s a subtle one. The <code>SigninLogs</code> table in Microsoft Sentinel emits <em>multiple records for a single login activity</em>. When a user authenticates, the table doesn&#8217;t just log the final result &#8212; it logs intermediate steps: the initial request, the MFA challenge, the conditional access evaluation, the final outcome. Each step is a separate row in the table, grouped by <code>Id</code> or <code>OriginalRequestId</code>.</p><p>If you didn&#8217;t know that, you&#8217;d write a detection that counts distinct sign-in events and gets inflated numbers. Or you&#8217;d match on an intermediate record that shows a &#8220;failure&#8221; status even though the overall authentication succeeded. A community member filed <a href="https://github.com/Azure/Azure-Sentinel/issues/9463">issue #9463</a> against Microsoft&#8217;s own Azure Sentinel repository pointing out that <em>none</em> of the built-in Entra ID detection rules accounted for this behavior. Every rule treating a row as a complete event was generating false positives. The fix &#8212; adding <code>| summarize arg_max(TimeGenerated, *) by Id</code> before the detection logic &#8212; is trivial once you know about it. But nothing in the schema documentation made this multi-record behavior obvious. And if Microsoft&#8217;s own first-party detection rules didn&#8217;t account for it, how many custom rules in production environments are getting it wrong right now?</p><p><strong>The scale of the shifting surface</strong></p><p>To understand why this kind of thing happens, consider the scale. The <a href="https://learn.microsoft.com/en-us/graph/overview">Microsoft Graph API</a> &#8212; the unified API surface that underpins Microsoft 365 and Entra ID &#8212; currently defines <strong>1,302 unique paths in its v1.0 endpoint</strong> and <strong>2,766 in beta</strong>. The <a href="https://github.com/microsoftgraph/msgraph-metadata">msgraph-metadata</a> repository on GitHub, which tracks the OpenAPI specification for these endpoints, has accumulated over <strong>3,400 commits</strong>. Each commit potentially changes what data flows through which endpoint, which affects what lands in your log tables, which affects whether your detection still works.</p><p>These changes don&#8217;t come with a changelog entry that says &#8220;the field your KQL rule depends on now behaves differently.&#8221; They come as metadata updates. A property gets added, renamed, or moved to a different response object. A field that used to be populated becomes null in certain conditions. An enum gains new values your <code>where</code> clause doesn&#8217;t match. The OpenAPI spec for the v1.0 endpoint alone is a 35-megabyte YAML file containing 872,000 lines. Nobody is reviewing that diff manually.</p><p>The defensive coverage map goes stale independently of the offensive one. Even if you solved Problems 1 through 3 &#8212; you confirmed your logs exist, you validated across identity types, and you tracked the API changes &#8212; your detections can still silently degrade because the telemetry they inspect shifted underneath them on Microsoft&#8217;s release schedule, not yours.</p><h2>The compounding effect</h2><p>These four problems don&#8217;t exist in isolation. They layer and multiply.</p><p>You can&#8217;t trust your detections (problem 2) without first confirming your telemetry exists (problem 1). You can&#8217;t maintain offensive coverage without tracking API changes (problem 3). And even if you solve all of that, the ground shifts underneath your defensive logic on its own schedule (problem 4).</p><p>The number of ways things silently break grows over time. Each problem multiplies the others. The gap between &#8220;what we think we&#8217;re detecting&#8221; and &#8220;what we&#8217;re actually detecting&#8221; widens every day you&#8217;re not actively validating it.</p><p>This is why purple teaming can&#8217;t be a quarterly exercise, or an annual pentest, or a one-time red team engagement. It&#8217;s a continuous validation problem. And solving it requires tooling built specifically for that purpose &#8212; not attack simulation tools repurposed for defense, but something designed from the ground up to validate the entire detection pipeline, from log ingestion through alert firing.</p><p>That&#8217;s what this series is about. Not a tool walkthrough &#8212; a way of thinking about detection validation that takes all four of these problems seriously and addresses them structurally.</p><p>Next post, we'll get concrete about Problem 1. I'll walk through where telemetry is configured in the Microsoft 365 ecosystem, what the actual log tables are, and how collection works &#8212; so you can see for yourself where the blind spots hide.</p><div><hr></div><p><em>Control Plane is a blog about building detection systems that actually work in SaaS and cloud environments. If you&#8217;re a detection engineer, purple teamer, or security leader tired of false confidence in your coverage, [subscribe] to follow the series.</em></p><div><hr></div><h3>References &amp; Credits</h3><ul><li><p><strong>Thomas Naunheim</strong> (<a href="https://www.cloud-architekt.net">@intruder_io</a>) &#8212; His work on <a href="https://www.cloud-architekt.net/auditing-of-msi-and-service-principals/">sign-in logs and auditing of Managed Identities and Service Principals</a> was an early and thorough documentation of the schema differences across Entra ID sign-in log tables, including the critical observation that fields like conditional access details and device information are absent from the service principal and managed identity schemas.</p></li><li><p><strong>Fabian Bader</strong> (<a href="https://cloudbrothers.info">Cloudbrothers</a>) &#8212; His multi-part series on <a href="https://cloudbrothers.info/en/detect-threats-microsoft-graph-logs-part-1/">detecting threats using Microsoft Graph activity logs</a> has been a valuable resource for understanding the detection surface across Microsoft&#8217;s logging ecosystem.</p></li><li><p><strong>Microsoft Learn</strong> &#8212; The Sentinel data connector documentation (<a href="https://learn.microsoft.com/en-us/azure/sentinel/connect-azure-active-directory">Send Microsoft Entra ID data to Microsoft Sentinel</a>) and the Entra ID diagnostic settings reference (<a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-diagnostic-settings-logs-options">Logs available for streaming</a>) are the primary sources for the licensing requirements and log type availability discussed in this post.</p></li><li><p><strong>Microsoft Entra Blog</strong> &#8212; The official <a href="https://techcommunity.microsoft.com/blog/microsoft-entra-blog/action-required-msonline-and-azuread-powershell-retirement---2025-info-and-resou/4364991">MSOnline and AzureAD PowerShell retirement announcement</a> and <a href="https://techcommunity.microsoft.com/blog/microsoft-entra-blog/important-update-deprecation-of-azure-ad-powershell-and-msonline-powershell-modu/4094536">deprecation update</a> document the timeline covered in Problem 3.</p></li><li><p><strong>Tony Redmond / Practical365</strong> &#8212; His ongoing coverage of the <a href="https://practical365.com/march-2024-retirement-old-azure-ad-modules/">AzureAD module retirement</a> and <a href="https://office365itpros.com/2023/07/10/graph-powershell-sdk-v2/">Graph SDK v2 migration</a> provided detailed practitioner perspective on the impact of these transitions.</p></li><li><p><strong>O365Reports.com</strong> &#8212; The community post <a href="https://o365reports.com/2023/07/12/the-term-select-mgprofile-is-not-recognized-error/">&#8220;The Never-Ending Cycle of MS Graph Script Migrations&#8221;</a> captured what many practitioners were feeling during the SDK v1&#8594;v2 transition.</p></li><li><p><strong>J3roen / Azure-Sentinel GitHub</strong> &#8212; <a href="https://github.com/Azure/Azure-Sentinel/issues/9463">Issue #9463</a> documented that Microsoft&#8217;s own built-in Sentinel detection rules did not account for the multi-record-per-login behavior in the <code>SigninLogs</code> table, leading to false positives across the Entra ID analytics rule set.</p></li><li><p><strong>microsoftgraph/msgraph-metadata</strong> &#8212; The <a href="https://github.com/microsoftgraph/msgraph-metadata">OpenAPI specification repository</a> for Microsoft Graph provided the API path counts and commit history referenced in Problem 4.</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="/__u/lydiagraslie.substack.com/subscribe"><span>Subscribe now</span></a></p><h2></h2><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://lydiagraslie.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Control Plane! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item></channel></rss>