Here is the finding, stated plainly, because it deserves to sting a little: a Conditional Access policy scoped to “all the platforms except the ones I trust” does not block clients that fail to declare a platform. It ignores them. Petri’s research put numbers behind what a few of us have watched happen in sign-in logs for years — the null-platform client walks past your carefully built device-platform rule and nobody gets an alert.
This is not a vulnerability. It’s a logic gap, and the logic is doing exactly what you told it to. But it breaks the mental model, so let’s fix the model first.
Why “unknown” isn’t the same as “everything else”
When you build the device platforms condition and choose Select device platforms, Entra hands you a finite list: Android, iOS, Windows, Windows Phone, macOS, Linux. That list is derived from the user-agent string the client presents at authentication. No usable user-agent, no platform. And there is no checkbox for “the ones I couldn’t identify.”
So when you configure include Android, iOS, Windows, macOS → exclude the corporate ones, a client that reports no platform matches none of your selected values. It isn’t excluded. It isn’t included. It’s simply outside the set the policy evaluates, and a policy that never matches never blocks.
Who shows up naked like this? Legacy browsers and embedded webviews, custom user-agents from scripts and integrations, some API clients hitting token endpoints directly, and the occasional appliance that someone stood up in 2016 and forgot. Not your fleet of managed laptops. Which is precisely why nobody notices — until they audit.
The audit, before the argument
Find your exposure before you decide it doesn’t apply to you. Pull sign-ins with a blank operating system. Read-only, paginated, no changes to anything:
Then list every policy that leans on the platform condition, so you know which rules are lying to you:
Two ways to close it
Option A — the block-list. Keep the platform condition, keep selecting platforms and excluding your trusted ones. It’s the intuitive shape and it’s what most people already have.
Option B — block by default. Set the device platforms condition to Any device (which includes unsupported and unknown), then attach a grant control: require a compliant device or a Hybrid Azure AD joined device. An unidentified client can’t be compliant. It can’t be hybrid-joined. It gets blocked because it fails the grant, not because you named its platform.
On coverage of the null-platform client: Option B wins, and it isn’t close. A won’t catch what it can’t enumerate. B blocks everything that can’t prove itself. Running score 1–0.
On rollback safety: B wins again. It’s a single policy with a clear grant control — flip it to report-only, watch the sign-in logs, then enable. A tempts you into a sprawl of include/exclude edits where one wrong platform tick locks out a division. 2–0.
On maintenance: B. New platforms and new mangled user-agents both fall into “Any device” automatically. A needs babysitting every time the taxonomy shifts. 3–0.
On false positives: A wins the one round. Block-by-default will catch your unregistered-but-legitimate integrations, and you’ll spend a week granting exceptions. Run it in report-only first and read the ClientAppUsed column. 3–1.
The verdict
Block by default. Configure it under Conditional Access > New policy > Conditions > Device platforms > Any device, pair it with a require-compliant grant, and start in report-only. Platform-scoped exclusions were never a security boundary; they were a convenience that read like one.
Keep Option A only for what it’s actually good at: applying different grants per platform — app protection policies on iOS and Android, say. Even then, back it with a platform-agnostic block underneath. The named-platform rule shapes the experience. The catch-all does the enforcing. Don’t confuse the two, or the unknown device will keep confusing them for you.
