The moment it genuinely felt catastrophic
This wasn’t a mild “huh, that’s odd” reaction.
It was a full-body, stomach-dropping, existential one.
While walking two senior security practitioners through OID-See, I demonstrated its default authentication model: a completely standard user authenticating via the Microsoft Azure CLI service principal.
I showed how that user could then enumerate: - Users - Groups - Service principals - App permissions - Assignment requirements - Large chunks of Conditional Access posture
And then one of them asked the question that changed the temperature of the room:
“If Azure CLI is pre-consented for all of that… what’s actually stopping me from just assigning myself Global Admin?”
That wasn’t rhetorical.
For a few long seconds, I didn’t have a satisfying answer.
Because on the surface, the evidence looked damning.
The terrifying implication we couldn’t unsee
Once you really look at the pre-consented delegated scopes on Microsoft Azure CLI, it’s hard not to spiral:
Directory.AccessAsUser.AllApplication.ReadWrite.AllGroup.ReadWrite.AllDelegatedPermissionGrant.ReadWrite.AllAppRoleAssignment.ReadWrite.All
Read literally, they appear to say:
“You can read and write basically everything in the directory.”
If that interpretation were true, the implications would be apocalyptic: - Any standard user - In any tenant - Could silently escalate to Global Administrator - Using only Microsoft first-party tooling - With no exploit, no bug, no misconfiguration
That wouldn’t be a vulnerability.
That would be a collapse of the entire trust model.
And for a few minutes, that possibility felt uncomfortably real.
Why this fear was reasonable
This wasn’t paranoia.
It was pattern recognition.
In the last few years, we’ve all watched: - “Safe” defaults turn out not to be safe - First-party apps bypass Conditional Access - Delegated permissions behave in surprising ways - Token semantics drift faster than documentation
So when you see: - Massive delegated scopes - Pre-consented - On a ubiquitous first-party app - Accessible to every user
The correct response is not calm dismissal.
The correct response is: > “Stop everything and test this properly.”
Which is exactly what we did.
Purple teaming the nightmare scenario
The hypothesis was simple:
A standard user can use Azure CLI to grant themselves Global Administrator.
We tested it directly.
And then indirectly.
And then creatively.
Attempts included: - Direct directory role assignment - Adding the user to role-assignable groups - Modifying app role assignments - Granting additional Graph permissions - Abuse via group membership writes - Lateral pivots through application ownership
Every single path failed.
Hard.
Consistently.
Unambiguously.
The directory enforcement boundary held.
That was the first exhale.
The missing mental model: delegated ≠ authoritative
The reason this looked like a catastrophe comes down to a deeply unintuitive truth:
Delegated permissions describe surface area, not authority.
They mean: > “You may attempt these operations as the signed-in user.”
They do not mean: > “You may bypass directory authorization.”
Even with: - Directory.AccessAsUser.All - Group.ReadWrite.All -
AppRoleAssignment.ReadWrite.All
The server still enforces: - Directory role checks - Protected object rules - Role-assignable group constraints
Scopes don’t grant power.
They grant reach.
And reach without authority stops at mutation.
What didn’t stop working: discovery
But something else worked flawlessly.
Enumeration.
The same token that couldn’t mutate privileged state could: - Map the entire tenant - Identify every privileged role and group - Enumerate all service principals - See which apps didn’t require assignment - Infer Conditional Access weaknesses
That’s where the fear transformed.
Not: > “This is broken.”
But: > “This collapses attacker discovery cost.”
Enumeration doesn’t give you power — it gives you time
This is the distinction that matters.
Enumeration: - Doesn’t unlock doors - Doesn’t grant admin - Doesn’t bypass controls
What it does is remove uncertainty.
Given full discovery, an attacker can: - Prioritise targets instantly - Ignore dead ends - Focus effort where governance is weakest
OID-See is explicitly built to expose this reality.
It doesn’t ask: > “Can this app escalate you?”
It asks: > “If escalation is possible anywhere, how fast do you find it?”
Active Directory déjà vu (again)
If this all feels familiar, it should.
Classic Active Directory worked the same way: - Any authenticated user could enumerate almost everything - Attacks succeeded because mutation paths were weak - BloodHound didn’t invent risk — it revealed topology
Entra ID didn’t change that model.
It modernised it.
Service principals replaced service accounts. Graph replaced LDAP. Scopes replaced ACL sprawl.
But the underlying truth stayed the same.
Where guest posture re-enters the picture
At this point, the existential fear had passed.
But a quieter, more persistent concern remained.
Guest users don’t live in the same threat model.
For members: - Broad enumeration is effectively mandatory - Security lives in governance and mutation controls
For guests: - Enumeration can be constrained - Cross-tenant access policy matters enormously - Defaults are often dangerously permissive
If guests are treated “like members”: - The discovery surface expands again - First-party apps become external reconnaissance tools - Weak governance collapses faster
This is where earlier guest-focused research kept resurfacing in my head.
Not because enumeration exists — but because who gets to enumerate matters.
Why this belongs in OID-See
OID-See already shows: > “Which service principals have broad reachability?”
The missing dimension was: > “Who else can reach them?”
Guest and external identity posture doesn’t create new exploits.
It amplifies existing risk.
So the design choice was deliberate: - Tenant-level posture only - Opportunistic collection - Conservative interpretation - Transparent amplification
No fear-mongering. No exploit claims. No false certainty.
Opportunistic, because reality demands it
Tenant policy objects sit behind Policy.Read.All.
Most delegated tokens — including Azure CLI — don’t have that.
So OID-See: - Inspects the token - Only attempts policy reads when plausible - Marks posture as unknown otherwise - Explains the limitation clearly
Unknown is not failure.
Unknown is honesty.
The sentence that survived the panic
After the fear subsided, one line stuck:
OID-See doesn’t tell you who can break in.
It tells you who already knows where all the doors are.
The initial terror came from thinking: > “What if all the doors are unlocked?”
The reality is subtler — and more dangerous:
Everyone can see the map.
And maps are power.
Not because they open doors.
But because they tell you which ones are worth kicking.
Closing thoughts
That initial existential fear was useful.
It forced proper testing. It forced better mental models. It forced restraint.
The outcome wasn’t a catastrophic vulnerability.
It was a clearer understanding of how identity risk actually works.
Directories trade secrecy for scale. They always have.
The danger isn’t that users can see.
It’s how fast weak governance collapses once they do.
OID-See exists to make that visible.
Quietly. Accurately. Without panic.
— Graham