Scope and honesty note. This is a framework and systematization piece, not a measurement study. I have not run a controlled experiment across many tenants, and there are no benchmark numbers here that I did not compute by hand from a topology I describe. Everything operational was done in isolated lab tenants I built for research during a red-team internship, never against production or third-party systems. Where I lean on other people's work I cite it. Where the model has gaps I say so.
What I am actually trying to solve
Background, and what already exists
The model
- Principals: users, groups, service principals, and the attacker's current identity. A principal is something that can hold role assignments.
- Resources: split into compute resources (a VM, an App Service, a Logic App workflow, a Container Group, an Automation Account) and data resources (Storage, Key Vault, a container registry). The split matters because only compute resources give you an execution surface.
- Managed identities: system-assigned or user-assigned, per Microsoft's own distinction. I model these as their own nodes rather than as attributes of a resource, and the reason is the whole point of the exercise, which I get to below.
- Directory boundaries: the on-prem AD forest and the Entra ID tenant, connected by whatever sync method is configured. These are context for now, not fully traversable in the current model.
The edge types
Why identities are nodes and not attributes
How Fenrir implements a narrow version of this
- READY — a satisfiable control-plane-execute edge at RG scope reaches at least one identity-bearing resource. Next step: run the exploit phase.
- BLOCKED_NO_TARGETS — control-plane-execute edges exist, but no compute resource in reach carries an identity. Next step: wait for one to appear, or use the identity-attach edge.
- BLOCKED_NO_RIGHTS — no satisfiable control-plane-execute edge at RG scope. Next step: go after non-MI data access instead.
- UNVERIFIED — the resource layer was not enumerated, because it ran with --no-resources. Next step: re-run without that flag.
A worked case study
- control-plane-execute to vm-jump01. The Contributor assignment at rg-app satisfies the precondition, so RunCommand is available. The state now includes code execution on the VM.
- imds-token from vm-jump01 to its system identity, and to uai-deploy. Execution on the host is the precondition, and it is met, so both tokens come down from 169.254.169.254. The state now holds two identities.
- The system identity's Key Vault Secrets User role is a data-plane reach to the vault's secrets. The graph draws this as an ordinary out-edge to a data resource, no further escalation required.
- Here is the part the graph earns its keep on. uai-deploy is a single node with in-edges from both vm-jump01 and the App Service. Holding its token from the VM means holding it everywhere it is attached, so the App Service is now within reach without ever attacking it directly, and the identity's Storage Blob Data Contributor role hands you the blob containers.

