You onboard a new regional helpdesk team. You build them a role assignment scoped to a single scope tag — call it Helpdesk-West — and everyone in the change ticket signs off happy: these admins can only see and touch West devices and West policies, nothing else. Six weeks later one of those helpdesk admins opens a compliance policy they should never have been able to see, edits the grace period on it, and quietly breaks Conditional Access for a site three time zones away. When you pull the audit log, the role assignment is exactly as scoped as it was on day one. Nobody changed the tag. So how did a West-scoped admin end up editing an object that belongs to nobody's region?
Because a scope tag is not a wall. It is an allow-list of what an admin can see, and Intune quietly fills the gap for everything you forgot to put on that list. This post walks through exactly why that happens, the one property that decides it, a manual check you can run against a single object before you trust anything, and a read-only PowerShell script that enumerates every configuration profile, compliance policy and app in your tenant and flags the ones that are silently visible to every scoped admin you have.
The problem — the portal shows a tidy boundary that does not actually exist
Open Tenant administration > Roles > All roles, pick the helpdesk role, and look at its assignment. You will see a clean, reassuring picture: Members = the West helpdesk group, Scope (Groups) = West devices, Scope (Tags) = Helpdesk-West. Every field says "West." The mental model that follows is almost irresistible: this admin lives inside a box called West and cannot see out of it.
That model is wrong in one specific, dangerous way. Scope tags decide which objects an admin can see, but they only ever add visibility — an object is visible to an admin if the object and the admin's role assignment share at least one scope tag. There is no such thing as "this object has no tag, therefore it is hidden from everyone." Microsoft's RBAC documentation is blunt about the edge of this behaviour:
The West assignment does have a tag, so it is not the no-tag case. But the same open-by-default logic reaches it from the other direction — through the objects. And that is the part almost nobody checks.
Why it happens — the Default scope tag is applied to everything you did not tag
Here is the mechanism, stated exactly. Intune ships one built-in scope tag called Default. Per the official RBAC and scope tags guidance, "The default scope tag is automatically added to all untagged objects that support scope tags." You do not opt in to this and there is no way to turn it off. Every configuration profile, compliance policy and app that nobody explicitly tagged carries the Default tag, whether you can see that in the blade or not.
Now pair that with how visibility resolves. Microsoft's Group Policy analytics guidance describes the untagged case from the object's point of view in a way that generalises to every object type: "If you don't select a scope tag, then the Default scope tag is automatically used. Only admins scoped to the Default scope tag can see the imported [object]. Admins that aren't scoped to the Default scope tag don't see the imported [object]."
Read that carefully, because the leak is hiding in the word "only." An untagged object is visible to every admin whose role assignment carries the Default tag. And the Default tag is the one almost every role assignment ends up carrying — either because it was left selected when the assignment was created, or because the assignment was given no tags at all (which, per the red callout above, is the same as having all of them). So the real boundary is not "West sees West." It is "West sees West plus every object in the entire tenant that nobody remembered to tag."
0) — roleScopeTagIds = ["0"]The built-in Default tag is the tag with id 0 in every tenant — it is the one scope tag flagged isBuiltIn: true when you list them through Graph. That id is what the script later in this post keys off, though it resolves the tag by its built-in flag at runtime rather than trusting a hard-coded number.
There is also a subtle read-only property that is easy to misread. On a configuration profile, supportsScopeTags being false means the object is a legacy policy that predates scope tags; Microsoft notes that in that state "entities will not be visible to scoped users." That is the opposite failure — a legacy object nobody can see — and it is worth distinguishing from the visibility leak this post is about, which is entirely about objects that carry the Default tag and therefore are visible.
How to verify — check one object by hand before you trust any script
Before you run anything at scale, prove the mechanism to yourself against a single object you already understand. Pick one configuration profile that you are certain nobody ever tagged. In the Intune admin center, open it and go to Properties > Scope (Tags). If the only entry is Default, that object is visible to every admin in your tenant whose role assignment includes the Default tag — which is almost all of them.
If you would rather confirm it in code — which is the honest way to check, since the blade hides the underlying id — read the roleScopeTagIds property directly through Microsoft Graph. That property is documented on the configuration profile, compliance policy and app resources alike as the "List of Scope Tags for this Entity instance." An untagged object comes back with a single value: the Default tag's id, 0.
roleScopeTagIds is fully populated on the /beta endpoint and on the beta Graph PowerShell module (Microsoft.Graph.Beta.DeviceManagement.Administration). Some v1.0 list calls have historically returned the property empty or omitted it on certain object types, so the script below targets beta deliberately — and treats an empty roleScopeTagIds the same as "only Default," because an object that reports no tags at all is exactly as exposed.The fix — enumerate every object and flag the ones scoped only to Default
You cannot fix what you cannot count, and clicking Properties on a few hundred profiles by hand is not an audit. Get-ScopeTagCoverageReport.ps1 connects with the least-privilege read scopes, resolves the built-in Default tag, then walks every configuration profile, compliance policy and app in the tenant and reports which ones carry only the Default tag or no tag at all — the objects that are silently visible to every scoped admin. It changes nothing.
Get-ScopeTagCoverageReport.ps1 is free and open source. Download it from the Imran76Awan/Daily-Tasks repository on GitHub — no sign-in required. It is read-only and cannot change anything in your tenant — but always validate any script in your own environment before you rely on it.
Connect it one of two ways. For an interactive run, -UseDeviceCode gives you a browser sign-in. For a scheduled, unattended run, pass app-only certificate auth with -TenantId, -ClientId and -CertificateThumbprint — the app registration needs only the three read scopes: DeviceManagementRBAC.Read.All to list the scope tags, DeviceManagementConfiguration.Read.All for configuration profiles and compliance policies, and DeviceManagementApps.Read.All for apps. Nothing writes, so no ReadWrite scope is ever requested.
The core of the script is deliberately small. It resolves the Default tag once, builds a combined list of every object with its roleScopeTagIds, and flags anything whose tag set is empty or contains nothing but the Default id. Every Graph call is wrapped so that a failure sets an error flag and exits non-zero — it never prints "0 findings, all healthy" after a query that actually failed.
Proof it worked — tag the leaks, then watch the coverage number climb
Work the list. For each flagged object, either assign the real scope tag it should have had all along (Properties > Scope (Tags) > Edit), or make a conscious decision that it genuinely is meant to be tenant-wide — a Company Portal app, say, that every admin legitimately needs to see. There is no auto-fix here on purpose: assigning the wrong tag can hide an object from the team that actually owns it, so every entry is a judgement call, not a bulk operation.
After a tagging pass, run the script again with -ExportCsv so you have a dated before/after artifact for the change record. The leak count falls to whatever you consciously chose to leave tenant-wide, and the custom-tag coverage climbs:
| Metric | First run | After tagging pass |
|---|---|---|
| Objects scanned | 654 | 654 |
| Objects visible to all scoped admins | 143 | 18 |
| — only Default tag | 121 | 6 |
| — no tag at all | 22 | 0 |
| Custom-tag coverage | 78.1% | 97.2% |
| Left tenant-wide on purpose (documented) | — | 18 |
0 when nothing leaks, 2 when it finds objects scoped only to Default, and 1 if any Graph query failed. That 2 on the "clean" re-run is expected — the 18 objects you deliberately left tenant-wide still count as findings. Reconcile the CSV against your documented allow-list rather than chasing the exit code to zero. And remember the harder limit: none of this touches anyone holding the Entra Intune Administrator role, who sees everything regardless. Scope tags are a distributed-IT convenience, not a security boundary against a privileged identity.I'll run this coverage report against your tenant, walk you through every object that leaks past its intended scope, and help you design a scope-tag strategy that actually holds — without hiding objects from the teams that own them.
References
- Use role-based access control (RBAC) and scope tags for distributed IT — Microsoft Learn
- Import and analyze group policies (Default scope tag on untagged objects) — Microsoft Learn
- List roleScopeTags (DeviceManagementRBAC.Read.All) — Microsoft Graph beta
- deviceConfiguration resource (roleScopeTagIds, supportsScopeTags) — Microsoft Graph beta
- mobileApp resource (roleScopeTagIds) — Microsoft Graph beta
- Get-MgBetaDeviceManagementRoleScopeTag cmdlet reference — Microsoft Learn
- Ugur Koc (MVP) — Export all scope tags of all devices, apps, policies and scripts in Intune
Get-ScopeTagCoverageReport.ps1 is a read-only audit script — it never assigns, edits or removes a scope tag. Safe to run against production while you build up a picture of which objects fall outside your intended scope-tag coverage.