<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Quarkslab's blog - Cloud</title><link href="http://blog.quarkslab.com/" rel="alternate"></link><link href="http://blog.quarkslab.com/feeds/cloud.atom.xml" rel="self"></link><id>http://blog.quarkslab.com/</id><updated>2026-04-30T00:00:00+02:00</updated><entry><title>Auditing Application Permissions in Microsoft Entra ID: Hidden Risks, Pitfalls, and Quarkslab's QAZPT Tool</title><link href="http://blog.quarkslab.com/auditing-application-permissions-in-microsoft-entra-id-hidden-risks-pitfalls-and-quarkslabs-qazpt-tool.html" rel="alternate"></link><published>2026-04-30T00:00:00+02:00</published><updated>2026-04-30T00:00:00+02:00</updated><author><name>Sébastien Rolland</name></author><id>tag:blog.quarkslab.com,2026-04-30:/auditing-application-permissions-in-microsoft-entra-id-hidden-risks-pitfalls-and-quarkslabs-qazpt-tool.html</id><summary type="html">&lt;p&gt;This blog post explores Entra ID applications, the complexities of auditing application permissions in Microsoft Entra ID, highlighting hidden risks and pitfalls. It introduces Quarkslab's QAZPT tool, designed to compute and visualize effective permissions in an Entra ID tenant, providing insights into the full picture of permissions and inheritance paths.&lt;/p&gt;</summary><content type="html">&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;If you work in security, development, or cloud architecture, and your organization uses Microsoft Azure or Microsoft 365, there is a high chance you have already come across Azure applications, whether intentionally or not. You may have even read the &lt;a href="https://www.wiz.io/blog/azure-active-directory-bing-misconfiguration"&gt;Wiz blog post&lt;/a&gt; detailing how they were able to modify Bing search results because of Microsoft's own applications misconfigurations.
However, as common as they are, Azure applications can be hard to fully understand, and they can hide, through their own complexity and the Microsoft permission model, more serious security risks than most other Microsoft Entra ID entities, if not properly managed.&lt;/p&gt;
&lt;p&gt;Beyond applications themselves, understanding how permissions actually propagate across an Entra ID tenant is surprisingly difficult in practice. Permissions inherit through ownership, federated identity, group membership, and several less obvious paths; the data needed to map all of this is scattered across multiple APIs and portal sections. The gap between what is configured and what is effectively reachable through inheritance is where most of the practical risk lives, and it is also where existing tooling tends to stop short.&lt;/p&gt;
&lt;p&gt;This post is structured in three parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Azure Applications: Definition, permissions, and hidden complexities&lt;/strong&gt; is a deep dive into how AppRegistrations and Service Principals work, the permission types they expose or require, and the credential behaviors that are not always visible from the Azure Portal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditing permissions in Entra ID: a real challenge&lt;/strong&gt; explains why auditing permissions in a real tenant is hard in practice, walks through the transitive inheritance paths that make manual analysis impractical at scale, and reviews the existing tooling landscape.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quarkslab Azure Permission Tracker (QAZPT): Mapping the full picture&lt;/strong&gt; introduces &lt;a href="https://github.com/quarkslab/QAZPT"&gt;QAZPT&lt;/a&gt;, an open-source tool we built to compute and visualize effective permissions in an Entra ID tenant, with examples of its outputs and use cases.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="azure-applications-definition-permissions-and-hidden-complexities"&gt;Azure Applications: Definition, permissions, and hidden complexities&lt;/h2&gt;
&lt;p&gt;Applications within Microsoft Entra ID (formerly Azure Active Directory) actually refer to Application Registrations, shortened to AppRegistration, and Enterprise Applications, also known as Application Service Principals. Applications are entities that represent a software program or a service that can interact with Microsoft Entra ID and other Microsoft services, such as Azure Resource Manager (ARM). In simpler terms, they act as a layer between your software and Microsoft cloud services.&lt;/p&gt;
&lt;p&gt;Their use cases are various, from enabling single sign-on (SSO) for users on a custom application or a third-party service, to allowing applications to access APIs or resources on behalf of a user, or independently, without user interaction.&lt;/p&gt;
&lt;p&gt;If you are using a calendar application, an email client, or any other software that integrates with Microsoft 365 services (did you have to create an account, or to agree to share some data from your Office 365 account with that HR platform your company is using?), chances are high that you are interacting with an external application. The web application you visit to read your mails (&lt;a href="https://outlook.office.com"&gt;https://outlook.office.com&lt;/a&gt;) is itself an application registered in Microsoft Entra ID (Outlook Web App), which you cannot see because it is a built-in, core, and non-configurable application.&lt;/p&gt;
&lt;p&gt;One of the most popular applications, for developers and power users, is probably Microsoft Graph. It is a RESTful web API that allows access to Microsoft 365 services data, such as users, groups, applications, mails, etc.&lt;/p&gt;
&lt;p&gt;By default, any member user in an Entra ID tenant can register applications, unless this ability has been restricted (as it should be). To do that, one can use the Azure Portal, the Entra ID admin center, or the Microsoft Graph API.&lt;/p&gt;
&lt;h3 id="definition"&gt;Definition&lt;/h3&gt;
&lt;p&gt;Applications in Entra ID are represented by two main objects: the Application Registration (AppRegistration) and the Service Principal (ServicePrincipal). While they are closely related, they serve different purposes which are important to understand and not very intuitive nor well explained in Microsoft documentation.&lt;/p&gt;
&lt;p&gt;An Application Registration (AppRegistration) is a global template or blueprint for an application within an Entra ID tenant. It defines the application's identity, configuration, redirection URI, and settings. It also includes the permissions the application will expose or require. As this part is of course a bit more complicated than that, we will detail it in the next section. Even though the AppRegistration is a template object, credentials for authentication (client secrets, certificates) are created and stored on it. They can be viewed and managed from the Web Portal, in the Entra ID section, under "App registrations", or through the Microsoft Graph API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;https://graph.microsoft.com/v1.0/applications&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer {token}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;An Application Service Principal (shortened to ServicePrincipal) is the actual instance of the application within a specific Entra ID tenant. It represents the application's identity and permissions in that tenant. When an application is registered through the web portal, a corresponding Service Principal is automatically created in the same tenant. The Service Principal is what is used during authentication and authorization processes when the application interacts with resources or APIs. They can be viewed and managed from the Web Portal, in the Entra ID section, under "Enterprise applications", or through the Microsoft Graph API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;https://graph.microsoft.com/v1.0/servicePrincipals&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer {token}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: Managed Identities are also referenced as Service Principals in Entra ID. They are a special type of Service Principal, primarily used by Azure resources to access other Azure or Entra ID services without needing to manage credentials. They are created and managed by Azure itself (&lt;code&gt;systemAssigned&lt;/code&gt;) when you enable Managed Identity on an Azure resource, or you can create them manually (&lt;code&gt;userAssigned&lt;/code&gt;) and assign them to one or several resources. It's like a simplified, auto-piloted Service Principal for Azure resources.&lt;/p&gt;
&lt;p&gt;While only one AppRegistration can exist for an application in its home tenant, multiple Service Principals can be created in different tenants if the application is used across multiple organizations. When, for example, a user consents (delegate permission) to an application, a Service Principal is created in their tenant based on the AppRegistration.&lt;/p&gt;
&lt;h3 id="permissions"&gt;Permissions&lt;/h3&gt;
&lt;p&gt;One of the biggest challenges when dealing with Azure applications is understanding their permissions model, which not many people actually know about, or that some think they know about but do not really understand. This confusion primarily stems from the lack of clear documentation from Microsoft, the complexity of the model itself, and the AppRegistration/ServicePrincipal duality.&lt;/p&gt;
&lt;p&gt;There are, of course, the Entra ID roles (Global Administrator, Application Administrator, Directory Reader, etc.) that can be assigned to them (the Service Principal, if you remember the previous section), even if it is not a common practice and is highly not recommended.&lt;/p&gt;
&lt;p&gt;However, the main way to grant access to resources for applications is through application permissions (also known as App roles), or delegated permissions (also known as OAuth2 permissions or scopes).&lt;/p&gt;
&lt;h4 id="types-of-permissions-and-their-specificities"&gt;Types of permissions and their specificities&lt;/h4&gt;
&lt;p&gt;Applications can expose, or require, two types of permissions.&lt;/p&gt;
&lt;h5 id="application-permissions-roles"&gt;Application permissions / roles&lt;/h5&gt;
&lt;p&gt;These are permissions that can be granted to applications, users, groups, or some combination of those. When granted to an application, they allow it to perform actions in another application without any user interaction. When granted to a user or a group, they appear as claims in the issued token and act as application-level roles for that user. In short, an app role is a way for an application to define capabilities and to assign other applications, users, or groups to them so they can accomplish specific tasks.&lt;/p&gt;
&lt;p&gt;Below is a screenshot of an AppRegistration configured to require the application permission &lt;code&gt;User.Read.All&lt;/code&gt; from the application &lt;code&gt;Microsoft Graph&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="AppRegistration portal showing required application permission" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/appRegistrationPortal_apps.png" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;As the administrator consent has not been granted, the associated Service Principal does not have this permission yet.&lt;/p&gt;
&lt;p&gt;For an application to create its own application role permissions, it must define them in the &lt;strong&gt;App roles&lt;/strong&gt; section. Below is the app role &lt;code&gt;MyCustomScopeRole.Read.All&lt;/code&gt; defined, so that another application or a user/group can be assigned to it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="AppRegistration portal showing app role" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/appRegistration_app_role.png" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;Application roles can be assigned either through the AppRegistration &lt;code&gt;API permissions&lt;/code&gt; section for applications (as shown above), or through the Service Principal for both applications and users/groups. Below is the assignment of the &lt;code&gt;MyCustomScopeRole.Read.All&lt;/code&gt; app role to a user from the Service Principal:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Application role assignment to a user" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/sp_user_assignment.png" width="100%"/&gt;&lt;/p&gt;
&lt;h5 id="delegated-oauth2-scope-permissions"&gt;Delegated / OAuth2 scope permissions&lt;/h5&gt;
&lt;p&gt;These are permissions that applications can be granted on behalf of a user. Once the user has authenticated and consented to the permissions, the application can perform actions as the user, within the scope of the granted permissions. This is commonly used for applications that need to access user data or perform actions on behalf of a user.&lt;/p&gt;
&lt;p&gt;What is sometimes unclear is that users can delegate any permission to an application without any error, but the application will only be able to perform actions that the user is actually allowed to perform.&lt;/p&gt;
&lt;p&gt;Delegated permissions can sometimes require admin consent, depending on the sensitivity of the permissions being requested, before the application is allowed to request them. Additionally, administrators can pre-consent to delegated permissions for applications, so that users do not have to individually consent to them and &lt;strong&gt;will not be prompted&lt;/strong&gt; when using the application.&lt;/p&gt;
&lt;p&gt;Below is a screenshot of an AppRegistration configured and allowed to request the delegated permission &lt;code&gt;email&lt;/code&gt; from the application &lt;code&gt;Microsoft Graph&lt;/code&gt; for users that will use it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="AppRegistration portal showing delegated permissions" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/appRegistrationPortal_delegated.png" width="100%"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unlike application permissions, which are granted to the Service Principal once admin consent is given, delegated permissions are granted on a per-user basis and only take effect once a user has authenticated and consented through the application (or once an administrator has pre-consented on their behalf).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In order for an application to define its own permissions to be used by other applications, it must declare them in the &lt;strong&gt;Expose an API&lt;/strong&gt; section. Below is the scope &lt;code&gt;MyCustomScope.Read&lt;/code&gt; defined, so that another AppRegistration can request it from users to delegate it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Expose an API section" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/appRegistration_expose_api.png" width="100%"/&gt;&lt;/p&gt;
&lt;h4 id="illicit-consent-grant-attacks"&gt;Illicit consent grant attacks?&lt;/h4&gt;
&lt;p&gt;You probably have already heard about consent phishing attacks, where an attacker tricks a user into consenting to an application that requests excessive permissions, allowing the attacker to gain access to sensitive data or perform actions on behalf of the user. If you have not, you can read more about it in the &lt;a href="https://learn.microsoft.com/en-us/defender-office-365/detect-and-remediate-illicit-consent-grants"&gt;Microsoft documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While this type of attack is still current and relevant, something that most people do not know is that the consent prompt does not always concern delegated permissions only. Application permissions can also be targeted in this way.&lt;/p&gt;
&lt;p&gt;If we take our earlier example of the AppRegistration with the &lt;code&gt;User.Read.All&lt;/code&gt; application permission from Microsoft Graph, an attacker with sufficient privileges to create or manage an AppRegistration who successfully tricks an administrator into consenting to that application will see the associated Service Principal immediately granted that permission, in addition to an administrator consent for the delegated permission &lt;code&gt;email&lt;/code&gt;, meaning regular users will not be prompted for permission delegation at all.&lt;/p&gt;
&lt;p&gt;Below is the consent prompt that would be displayed to an administrator in this case:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Admin consent prompt" class="align-center" height="50%" src="resources/2026-04-30_ms-azure-permissions-inheritance/admin_consent.png"/&gt;&lt;/p&gt;
&lt;p&gt;Immediately after consenting, the Service Principal has the application permission granted:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Service Principal with application permission" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/sp_permissions.png" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;This is another good reason to pay attention to prompts and consent requests, even when the application is registered in your own tenant, and to never use an administrator account for regular activities.&lt;/p&gt;
&lt;h3 id="credentials"&gt;Credentials&lt;/h3&gt;
&lt;p&gt;Depending on the use case of the application and the authentication flow wanted, applications may need to authenticate themselves to Microsoft Entra ID to obtain access tokens for accessing resources or APIs. For this, credentials can be registered to the AppRegistration, either through the Web Portal or the Microsoft Graph API. Credentials can be client secrets (basically passwords), certificates, or federated identities. However, we are talking about Microsoft here, so this cannot be that simple.&lt;/p&gt;
&lt;h4 id="service-principal-credentials-creation-its-not-a-bug-its-a-feature"&gt;Service Principal credentials creation: it's not a bug, it's a feature&lt;/h4&gt;
&lt;p&gt;As mentioned earlier, credentials are created and managed from the AppRegistration object, either through the Web Portal or the Microsoft Graph API. However, what is not very new nor well known (but should be) is that credentials can also be created for the Service Principal object itself, but only via the Graph API.&lt;/p&gt;
&lt;p&gt;As these credentials can only be created from the Graph API, they are therefore not visible from the Web Portal, and can go unnoticed during permission reviews.&lt;/p&gt;
&lt;p&gt;For example, let's create a new client secret for a Service Principal using the Graph API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://graph.microsoft.com/v1.0/serviceprincipals(appId='00000000-0000-0000-0000-000000000001')/addPassword"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$bearer&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'{"displayName":"test"}'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"@odata.context"&lt;/span&gt;:&lt;span class="s2"&gt;"https://graph.microsoft.com/v1.0/&lt;/span&gt;&lt;span class="nv"&gt;$metadata&lt;/span&gt;&lt;span class="s2"&gt;#microsoft.graph.passwordCredential"&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"customKeyIdentifier"&lt;/span&gt;:null,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"displayName"&lt;/span&gt;:null,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"hint"&lt;/span&gt;:&lt;span class="s2"&gt;"m_q"&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"keyId"&lt;/span&gt;:&lt;span class="s2"&gt;"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"&lt;/span&gt;,
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"secretText"&lt;/span&gt;:&lt;span class="s2"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The newly created client secret is not visible from the AppRegistration object, either from the Web Portal or the Graph API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://graph.microsoft.com/v1.0/applications(appId='00000000-0000-0000-0000-000000000001')"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$bearer&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.passwordCredentials'&lt;/span&gt;
&lt;span class="o"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that this is currently not true for federated identity credentials, which for now can only be created for the AppRegistration object, or managed identities.&lt;/p&gt;
&lt;h4 id="federated-identities-a-nice-privilege-escalation-feature"&gt;Federated identities: a nice privilege escalation feature&lt;/h4&gt;
&lt;p&gt;Federated identities allow other identities to impersonate the application without needing to manage credentials directly. This can be used to enable scenarios where an external identity provider authenticates on behalf of the application, Kubernetes Service Accounts access resources, or for other cross-identity scenarios.&lt;/p&gt;
&lt;p&gt;While this is a good feature for avoiding the management of secrets, it can also pose security risks, and be easily forgotten about or not properly monitored. An attacker could leverage a compromised identity to impersonate the application, or, in a post-exploitation scenario, configure this feature to maintain persistence.&lt;/p&gt;
&lt;h5 id="the-poc-is-in-production"&gt;The PoC is in production&lt;/h5&gt;
&lt;p&gt;Even though it is currently not documented, the Graph API endpoint &lt;code&gt;/servicePrincipals/{id}/federatedIdentityCredentials&lt;/code&gt; exists and allows listing the federated identities of a Service Principal, but returns an error when trying to create one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://graph.microsoft.com/v1.0/servicePrincipals(appId='00000000-0000-0000-0000-000000000001')/federatedIdentityCredentials"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$bearer&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'    {&lt;/span&gt;
&lt;span class="s1"&gt;      "name": "f",&lt;/span&gt;
&lt;span class="s1"&gt;      "issuer": "https://login.microsoftonline.com/{tenant_id}/v2.0",&lt;/span&gt;
&lt;span class="s1"&gt;      "subject": "{id_of_managed_identity}",&lt;/span&gt;
&lt;span class="s1"&gt;      "audiences": [&lt;/span&gt;
&lt;span class="s1"&gt;        "api://AzureADTokenExchange"&lt;/span&gt;
&lt;span class="s1"&gt;      ]&lt;/span&gt;
&lt;span class="s1"&gt;    }'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;:&lt;span class="s2"&gt;"Request_BadRequest"&lt;/span&gt;,&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Property  is not currently supported."&lt;/span&gt;,...&lt;span class="o"&gt;}}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If this behavior becomes supported in the future, it could lead to even more unnoticed credentials for applications: federated identity credentials are not included in the returned JSON object when querying the AppRegistration or Service Principal through the Graph API. They can only be viewed through the Azure Portal, or by requesting the dedicated &lt;code&gt;/federatedIdentityCredentials&lt;/code&gt; endpoint, which is not very intuitive and can be easily missed during permission reviews.&lt;/p&gt;
&lt;p&gt;A new federated identity credential for an AppRegistration can be created either from the Web Portal or the Graph API, using the dedicated endpoint mentioned above. For example, let's authorize a managed identity to impersonate an application by creating a new federated identity credential for the associated AppRegistration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://graph.microsoft.com/v1.0/applications/&amp;lt;application_id&amp;gt;/federatedIdentityCredentials"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$bearer&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;--data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;'{&lt;/span&gt;
&lt;span class="s1"&gt;    "name": "myFederatedIdentityCredential",&lt;/span&gt;
&lt;span class="s1"&gt;    "issuer": "https://login.microsoftonline.com/&amp;lt;tenant_id&amp;gt;/v2.0",&lt;/span&gt;
&lt;span class="s1"&gt;    "subject": "&amp;lt;id_of_managed_identity&amp;gt;",&lt;/span&gt;
&lt;span class="s1"&gt;    "audiences": [&lt;/span&gt;
&lt;span class="s1"&gt;      "api://AzureADTokenExchange"&lt;/span&gt;
&lt;span class="s1"&gt;    ]&lt;/span&gt;
&lt;span class="s1"&gt;}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5 id="managed-identities-and-federated-identities-the-rules-change-during-the-game"&gt;Managed Identities and federated identities: the rules change during the game&lt;/h5&gt;
&lt;p&gt;Recall that a Federated Identity Credential cannot be created for Service Principals, but only for AppRegistrations: the feature is "not currently supported".
Recall also that Managed Identities are represented as Service Principals. So, does that mean that we cannot create federated identities for them? The rules are not the same for everyone.&lt;/p&gt;
&lt;p&gt;Federated identities can in fact be created for User-Assigned Managed Identities, but not through the Graph API (which only allows reading them). Even though Managed Identities are represented as Service Principals, they are actually managed by Azure Resource Manager (ARM); the only way to create federated identities for them is therefore through the ARM API or the Azure Portal.&lt;/p&gt;
&lt;p&gt;For example, let's create a new federated identity credential for a User-Assigned Managed Identity using the ARM API's dedicated endpoint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;PUT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://management.azure.com/subscriptions/&amp;lt;subscription_id&amp;gt;/resourceGroups/&amp;lt;rg_name&amp;gt;/providers/Microsoft.ManagedIdentity/userAssignedIdentities/&amp;lt;managed_identity_name&amp;gt;/federatedIdentityCredentials/&amp;lt;federated_identity_credential_name&amp;gt;?api-version=2024-11-30"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-H&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$bearer&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--data&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;'{&lt;/span&gt;
&lt;span class="s1"&gt;  "properties": {&lt;/span&gt;
&lt;span class="s1"&gt;    "audiences": [&lt;/span&gt;
&lt;span class="s1"&gt;      "api://AzureADTokenExchange"&lt;/span&gt;
&lt;span class="s1"&gt;    ],&lt;/span&gt;
&lt;span class="s1"&gt;    "issuer": "https://login.microsoftonline.com/&amp;lt;tenant_id&amp;gt;/v2.0",&lt;/span&gt;
&lt;span class="s1"&gt;    "subject": "&amp;lt;id_of_managed_identity&amp;gt;"&lt;/span&gt;
&lt;span class="s1"&gt;  }&lt;/span&gt;
&lt;span class="s1"&gt;}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="the-credentials-matrix-as-a-summary"&gt;The credentials matrix as a summary&lt;/h4&gt;
&lt;p&gt;In order to summarize the different types of credentials, their creation methods, and their visibility, here is a matrix that can be used as a quick reference. Note that this is true at the time of writing, but Microsoft can change the rules whenever they want, so it is important to always check the latest documentation and test the actual behavior when reviewing permissions and credentials for applications.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: center;"&gt;Entity Type&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Secret&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Certificate&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Federated Identity&lt;/th&gt;
&lt;th style="text-align: center;"&gt;API&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Web Portal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt;AppRegistration&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅ - Graph&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt;ServicePrincipal (regular)&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅ - Graph&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt;ServicePrincipal (Managed Identity, User Assigned)&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅ - ARM - Graph: Read-Only&lt;/td&gt;
&lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt;ServicePrincipal (Managed Identity, System Assigned)&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="the-concrete-differences-between-appregistration-and-serviceprincipal"&gt;The concrete differences between AppRegistration and ServicePrincipal&lt;/h3&gt;
&lt;p&gt;AppRegistration and Service Principal are two different objects, closely related and often confused, mainly because they share too many properties and settings, where they should not. They have different purposes, and owning one or the other does not mean having the same control or capabilities. This is why security auditors need to pay attention to both objects when reviewing application permissions and transitiveness.&lt;/p&gt;
&lt;p&gt;Below is a screenshot summarizing the main differences and similarities between the two objects:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Comparison table between AppRegistration and ServicePrincipal" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/sp_vs_appregistration.png" width="60%"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Owning an AppRegistration gives you the ability to manage it, and its associated Service Principals as well; the reverse is not true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Both AppRegistration and Service Principal can have their own credentials. However, AppRegistration credentials allow authenticating as the application &lt;strong&gt;in any tenant where a Service Principal exists&lt;/strong&gt;, while Service Principal credentials can only be used for that Service Principal itself. This is why you must not give application permissions or Entra ID roles to Service Principals for which you do not own the AppRegistration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Defined application roles and exposed API scopes can be found on both objects. This can be confusing in the case of the Service Principal, as it may lead to thinking they are permissions actually granted to it, while they are only definitions inherited from the AppRegistration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AppRegistration exposes the required application roles and delegated permissions under the same section &lt;code&gt;requiredResourceAccess&lt;/code&gt;, differentiating them only by the &lt;code&gt;type&lt;/code&gt; property (&lt;code&gt;Scope&lt;/code&gt; for delegated permissions, &lt;code&gt;Role&lt;/code&gt; for application roles). The Service Principal, in contrast, shows actual permissions granted to it, and separates them into two sections: &lt;code&gt;appRoleAssignments&lt;/code&gt; for application permissions, and &lt;code&gt;oauth2PermissionGrants&lt;/code&gt; for delegated permissions. This is important to understand when reviewing permissions through the Graph API.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these differences and hidden behaviors make application permissions notoriously difficult to audit in practice. In a real tenant with hundreds or thousands of applications, understanding who actually has access to what, and through which chain, is far from straightforward.&lt;/p&gt;
&lt;h2 id="auditing-permissions-in-entra-id-a-real-challenge_1"&gt;Auditing permissions in Entra ID: a real challenge&lt;/h2&gt;
&lt;h3 id="entra-id-roles-technically-visible-practically-painful"&gt;Entra ID roles: technically visible, practically painful&lt;/h3&gt;
&lt;p&gt;Checking which Entra ID roles are assigned to a given entity through the web portal is technically possible, but far from efficient. To check the roles of a single Service Principal, you need to navigate to the Entra ID admin center or the Entra ID section of the Azure portal, find the right entity, and click through several menus before reaching the role assignments. You can also list all the existing roles and click through each of them to see which entities are assigned to them.&lt;/p&gt;
&lt;p&gt;There is no consolidated view showing all role assignments across all entities at once. For a tenant with dozens of Service Principals and users, doing this manually quickly becomes unrealistic.&lt;/p&gt;
&lt;p&gt;Not to mention the fact that when using Microsoft Entra Privileged Identity Management (PIM) to manage privileged roles, the actual role assignments are not visible in the portal at all, as they are only granted on demand when users activate them. This is a good security practice, but it also means that auditing effective permissions requires either having access to PIM logs or asking users to activate their roles one by one during the review process, which is impractical.&lt;/p&gt;
&lt;p&gt;On top of that, the portal does not show transitive role assignments. Roles that are effectively granted to an entity through group membership are not visible directly. An entity can inherit Global Administrator rights through a group it belongs to, and nothing unusual will appear when you look at that entity's role assignments directly.&lt;/p&gt;
&lt;h3 id="application-permissions-and-delegated-permissions-close-to-invisible"&gt;Application permissions and delegated permissions: close to invisible&lt;/h3&gt;
&lt;p&gt;If Entra ID roles are already painful to audit, application roles and delegated permissions are close to invisible without dedicated tooling. There is no built-in consolidated view in the portal that shows which app roles have been granted to a given Service Principal, or which users have delegated which permissions to which applications. Fragments of this information are scattered across different pages (the Enterprise Applications section, individual API permissions pages), but assembling a complete picture requires navigating through multiple screens for each entity individually.&lt;/p&gt;
&lt;h3 id="the-deeper-problem-permissions-are-transitive"&gt;The deeper problem: permissions are transitive&lt;/h3&gt;
&lt;p&gt;Even if you could instantly see all direct permissions assigned to every entity, that would still only be half the picture. Permissions in Entra ID propagate transitively through relationships between entities, and the chains can be long, indirect, and non-obvious. Below are some examples of inheritance paths that make this hard to reason about.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- Direct ownership.&lt;/strong&gt; The most intuitive one: an entity that owns another entity gains control over it. A user who owns an AppRegistration can manage it and its associated Service Principal, including rotating credentials and modifying permissions. Ownership is explicit and auditable in principle, but it tends to accumulate over time and rarely gets cleaned up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- Group membership.&lt;/strong&gt; Users, Service Principals, and other groups can be members of an Entra ID security group, and a group can itself hold Entra ID role assignments and application role assignments. Any member of such a group transitively inherits everything the group has been granted, and nested groups extend the chain further. Group membership often produces the longest and least-obvious privilege chains in real tenants: a user inherits a role granted to a group, which is itself nested in another group holding additional permissions, and so on. The portal, of course, does not surface these chains in a single view.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- ServicePrincipal of an AppRegistration.&lt;/strong&gt; Whoever controls an AppRegistration, through ownership or by obtaining its credentials, effectively controls the corresponding Service Principal and all the permissions granted to it. Since AppRegistration credentials are valid across every tenant where the application is deployed, this relationship is particularly sensitive: gaining access to the AppRegistration is not just gaining access to one Service Principal, but potentially to many.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- Federated Identity Credentials.&lt;/strong&gt; An application can be configured to trust an external identity via a Federated Identity Credential (FIC). When a Managed Identity is set as the FIC subject for an AppRegistration, that Managed Identity can request tokens for the application without any secret or certificate; the trust is configured at the application level, not through a stored credential.&lt;/p&gt;
&lt;p&gt;Concretely: if Managed Identity A is the FIC subject of Application B, and Application B's Service Principal holds significant permissions, then anything that can obtain a token for Managed Identity A can impersonate the application and exercise those permissions. An Azure VM with a system-assigned Managed Identity is a common example. Compromising that VM is enough; no credential to extract, nothing to rotate. The same scenario can occurs with a Kubernetes Service Account, which can be configured as a FIC subject to allow a pod to impersonate an application. This is a common scenario in real tenants, and it is often overlooked during permission reviews.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- Entra ID roles.&lt;/strong&gt; Privileged Entra ID roles such as Global Administrator, Application Administrator, or Cloud Application Administrator give their holder control over all entities within a defined scope: all applications and Service Principals, or all entities including users. Any entity that can reach a node holding one of these roles through an inheritance chain transitively inherits that scope.&lt;/p&gt;
&lt;p&gt;Example: User A owns AppRegistration B, whose Service Principal C has been assigned Global Administrator. User A has no privileged role assigned to them directly, and a quick portal check on their account looks clean. But through the ownership chain, User A effectively controls everything a Global Administrator can control.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- Microsoft Graph application roles.&lt;/strong&gt; Some Microsoft Graph application roles are as dangerous as, or more dangerous than, Entra ID administrative roles, and considerably easier to overlook. &lt;code&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt; allows its holder to assign any Entra ID role to any entity, including Global Administrator, which makes it a direct path to full tenant takeover. &lt;code&gt;AppRoleAssignment.ReadWrite.All&lt;/code&gt; allows assigning any application role to any entity without administrator approval, which can be used to first assign &lt;code&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt; to a controlled entity and then escalate from there. &lt;code&gt;Application.ReadWrite.All&lt;/code&gt; is similarly dangerous: it lets its holder add credentials (client secrets, certificates) to any application or Service Principal in the tenant, including ones already holding privileged roles, effectively turning it into an immediate impersonation primitive against any application of interest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- Bonus example, Azure Resource Manager (ARM) roles.&lt;/strong&gt; While ARM permissions are not technically part of Entra ID, an entity that has an ARM role allowing it to fully manage a resource with a Managed Identity attached (such as a Virtual Machine or an Azure Web App) can indirectly obtain an access token for that Managed Identity and impersonate it to access any resource or API the associated Service Principal has permissions for. This is a common scenario in real-world tenants.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And many more.&lt;/strong&gt; The combinations and variations of relationships can create very long and unexpected inheritance chains. For example, an entity might have read permissions on a specific entry in an Azure Key Vault that contains secrets for an AppRegistration. However, this is probably where we draw the line of what can reasonably be audited using regular tools.&lt;/p&gt;
&lt;h3 id="state-of-the-art-commercial-and-open-source-tools"&gt;State of the art: commercial and open-source tools&lt;/h3&gt;
&lt;p&gt;Commercial tools exist to address part of this. Microsoft Defender for Cloud Apps, Grip Security, and PingCastle for Entra ID, for example, all provide more or less governance-oriented visibility into application permissions. However, these tools are mostly designed for compliance and governance teams: they help detect policy violations, enforce least-privilege policies, and generate non-technical audit reports for management. They are often not built for security auditors or red teamers who need to answer the question "if I compromise this entity, what can I do from there?". They do not trace permission chains the way an attacker would follow them, they do not model inheritance, and they generally require a commercial license that a pentester or a bug hunter will not have.&lt;/p&gt;
&lt;p&gt;Open-source tools also exist, such as &lt;a href="https://azuread.github.io/MSIdentityTools/commands/Export-MsIdAppConsentGrantReport/"&gt;MSIdentityTools&lt;/a&gt; or the famous &lt;a href="https://gist.github.com/psignoret/41793f8c6211d2df5051d77ca3728c09"&gt;Get-AzureADPSPermissions.ps1&lt;/a&gt; script, which is even referenced in the &lt;a href="https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-app-consent"&gt;Microsoft documentation&lt;/a&gt; for investigating application consent grant attacks. It would also be unfair not to mention &lt;a href="https://github.com/specterops/bloodhound"&gt;BloodHound&lt;/a&gt; and &lt;a href="https://github.com/SpecterOps/AzureHound"&gt;AzureHound&lt;/a&gt; from SpecterOps, which are the reference tools for attack path mapping in Active Directory and Entra ID environments. BloodHound excels at surfacing reachability paths across a broad set of relationships (ownership, group membership, role assignments) and visualizing them interactively. Its primary design goal is attack path discovery rather than effective permission aggregation per entity, and extracting specific permission-related findings generally requires writing Cypher queries, which can be a barrier for practitioners less familiar with graph databases.&lt;/p&gt;
&lt;p&gt;Tracing these chains manually across a real tenant, mapping each entity's direct permissions, then following each ownership relationship, each FIC, and each privileged role assignment to see what they ultimately reach, is not a realistic operation at scale. The combinations grow quickly, the relevant data is split across multiple API endpoints and portal sections, and there is no unified view anywhere. No tool, commercial or open-source, gives you the exhaustive picture of permissions and an idea of the effective permissions of each entity through inheritance, in a way that is easy to read and actionable for security assessments.&lt;/p&gt;
&lt;p&gt;This is the gap QAZPT was built to fill.&lt;/p&gt;
&lt;h2 id="quarkslab-azure-permission-tracker-qazpt-mapping-the-full-picture_1"&gt;Quarkslab Azure Permission Tracker (QAZPT): Mapping the full picture&lt;/h2&gt;
&lt;h3 id="what-is-qazpt"&gt;What is QAZPT?&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/quarkslab/QAZPT"&gt;QAZPT (Quarkslab Azure Permission Tracker)&lt;/a&gt; is an open-source CLI tool designed to analyze and visualize permission inheritance in Microsoft Entra ID environments. It aims to provide what other tools do not: effective permissions computation for each entity, and the most complete view possible of how permissions propagate through a tenant. It covers most of the inheritance paths described above (ownership, Service Principal of an AppRegistration, Federated Identity Credentials, privileged Entra ID roles, and privileged Microsoft Graph application roles), with partial support for group membership.&lt;/p&gt;
&lt;p&gt;Beyond computing effective permissions through inheritance, QAZPT also exposes the direct permission state of each entity in its structured outputs: their own Entra ID role assignments, their own application role assignments, and the delegated permissions that have been granted to them. This gives practitioners a single, consolidated view of both "what this entity directly holds" and "what it can effectively reach through inheritance" in one pass.&lt;/p&gt;
&lt;p&gt;Most importantly, as it is written in Python, QAZPT does not require PowerShell or Windows-dependent PowerShell modules.&lt;/p&gt;
&lt;h3 id="how-it-works"&gt;How it works&lt;/h3&gt;
&lt;p&gt;QAZPT authenticates against the Microsoft Graph API using standard Azure credentials, supporting the full Azure identity chain (Azure CLI session, environment variables, or Managed Identity). From there, it proceeds in four main stages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Data collection.&lt;/strong&gt; A set of resource-specific &lt;em&gt;brokers&lt;/em&gt;, one per entity type (users, applications, Service Principals, role definitions), pulls all relevant entities and their attributes from the Graph API. Responses are cached locally as JSON, so subsequent runs against the same tenant can skip the network calls and run offline. This makes iterative exploration and re-running queries cheap, even on large tenants, and limits the risk of triggering throttling or detection logic during an assessment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Graph construction.&lt;/strong&gt; Each entity is wrapped into a typed &lt;code&gt;Node&lt;/code&gt; carrying its own direct permission set: Entra ID role assignments, application role assignments, and delegated permissions. Direct relationships (ownership links, AppRegistration to Service Principal pairing, federated identity subjects, role assignments) are then materialized as edges between these nodes, giving the inheritance computation a concrete starting point per entity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Inheritance resolution.&lt;/strong&gt; Five independent resolvers walk the graph and add inheritance edges, one per inheritance path described earlier in this post: direct ownership, Service Principal of an AppRegistration, federated identity credentials of an AppRegistration and Service Principal, privileged Entra ID roles, and privileged Microsoft Graph application roles. Resolvers are decoupled from each other, which means adding a new privilege escalation path to QAZPT only requires writing a new resolver and registering it; no other component needs to change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Transitive computation.&lt;/strong&gt; Once the inheritance graph is built, QAZPT computes effective permissions for every entity. Cycles, which are not unusual in real tenants (for example, two Service Principals that mutually own each other, which is, very unusual but a very simple example), are handled by collapsing strongly connected components into single super-nodes using Tarjan's algorithm. The resulting structure is a directed acyclic graph, on which permissions are aggregated bottom-up and then redistributed back to each individual node, with the original source of every inherited permission preserved so practitioners can trace where each permission ultimately came from.&lt;/p&gt;
&lt;p&gt;The result is a fully resolved view of the tenant's effective permissions, ready to be exported in any of the supported output formats.&lt;/p&gt;
&lt;h3 id="key-features"&gt;Key features&lt;/h3&gt;
&lt;p&gt;The key features of QAZPT include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Analyzing and visualizing inheritance between Entra ID entities (Users, AppRegistrations, ServicePrincipals), uncovering complex relationships and potential permission escalation paths.&lt;/li&gt;
&lt;li&gt;Identifying and visualizing gained permissions through inheritance, which helps detect over-privileged entities.&lt;/li&gt;
&lt;li&gt;Exporting detailed reports in CSV and JSON formats for further analysis, containing entities with their own permissions and their inherited permissions.&lt;/li&gt;
&lt;li&gt;Exporting the full graph to a Neo4j database for interactive exploration and advanced Cypher queries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="use-cases"&gt;Use cases&lt;/h3&gt;
&lt;p&gt;QAZPT covers both sides of a security assessment.&lt;/p&gt;
&lt;p&gt;On the offensive side, it helps quickly identify privilege escalation paths: which entities, if compromised, would give an attacker access to privileged roles or sensitive permissions, and through which chain. Finding that a VM's Managed Identity is one FIC away from a Global Administrator role is exactly the kind of finding that takes hours to trace manually and seconds to spot in a QAZPT output.&lt;/p&gt;
&lt;p&gt;On the defensive side, it helps auditors verify that no entity has accumulated unintended permissions through indirect relationships, that ownership assignments are justified, and that no critical application permission has been silently granted somewhere deep in an inheritance chain.&lt;/p&gt;
&lt;h3 id="outputs"&gt;Outputs&lt;/h3&gt;
&lt;p&gt;QAZPT produces three types of output across four formats.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inheritance tree&lt;/strong&gt; (console): an ASCII tree showing, for each entity, which entities it inherits permissions from, and through which relationship type. Useful for a quick read during an assessment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qazpt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inheritance&lt;/span&gt;
&lt;span class="err"&gt;###&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;###&lt;/span&gt;

&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="nv"&gt;@contoso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;xxxxxxxx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xxxx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xxxx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xxxx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;xxxxxxxxxxxx&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;ownership&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppRegistration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;general_admin_app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000001&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;sp_of_app&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;general_admin_app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000002&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HAS PRIVILEGED ROLE&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;Entraid_Role (Global Administrator)&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;All&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppRegistration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;###&lt;/span&gt;

&lt;span class="n"&gt;AppRegistration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;general_admin_app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000001&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;sp_of_app&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;general_admin_app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;19907881&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;667&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4959&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b41d&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="n"&gt;d85c71a844&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HAS PRIVILEGED ROLE&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;Entraid_Role (Global Administrator)&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;All&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;

&lt;span class="n"&gt;AppRegistration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;graph_role_readwrite_app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000003&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;sp_of_app&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;graph_role_readwrite_app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000004&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;Graph_App_Role (AppRoleAssignment.ReadWrite.All)&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;All&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;

&lt;span class="n"&gt;AppRegistration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_cycle_app2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000005&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;sp_of_app&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_cycle_app2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000006&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;ownership&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_cycle_app1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000007&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;&amp;uarr;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Cycle&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;ownership&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_cycle_app2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000006&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Managed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Principals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;###&lt;/span&gt;

&lt;span class="n"&gt;ManagedIdentityServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_managed_identity_2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000008&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;federated_identity&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ManagedIdentityServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_managed_identity_1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000009&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;federated_identity&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppRegistration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;another_app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000010&lt;/span&gt;
&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;--[&lt;/span&gt;&lt;span class="n"&gt;sp_of_app&lt;/span&gt;&lt;span class="o"&gt;]--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;another_app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000011&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Orphan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Principals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;###&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;Any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;section&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reviewed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;external&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;appRegistrations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;An&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;External&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;00000000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0000&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;000000000099&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HAS PRIVILEGED ROLE&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;--[Entraid_Role (Cloud Application Administrator)]--&amp;gt; Entities of type ServicePrincipal, AppRegistration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;JSON and CSV&lt;/strong&gt;: structured exports containing, for each entity, its direct Entra ID roles, its direct app role assignments, its direct delegated permissions, and all inherited permissions, with their origin traced back to the source entity. These can be fed into report templates or processed further with any standard tooling.&lt;/p&gt;
&lt;p&gt;Below is an example of the JSON output for a user inheriting permissions through ownership of an AppRegistration whose Service Principal has the Global Administrator role assigned to it. The output includes both the direct permissions (empty in this case) and the inherited permissions, along with the direct origin (the entity from which the permission was inherited, which might not be the ultimate source) of each inherited permission:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;qazpt&lt;span class="w"&gt; &lt;/span&gt;inheritance&lt;span class="w"&gt; &lt;/span&gt;--direct-origin&lt;span class="w"&gt; &lt;/span&gt;--as-json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;jq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"Entity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"EntraID Roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"Application Roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"Delegated Permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"Inherited EntraID Roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"Role(s)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"Billing Administrator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"Cloud Application Administrator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"Global Administrator"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"Origin entity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppRegistration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"general_admin_app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00000000-0000-0000-0000-000000000001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"App ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00000000-0000-0000-0000-000000000050"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"Inherited Application Roles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"AppRole(s)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft Graph/AppRoleAssignment.ReadWrite.All"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft Graph/User.Read.All"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft Graph/Application.ReadUpdate.All"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;"Origin entity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppRegistration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"general_admin_app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00000000-0000-0000-0000-000000000001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;"App ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00000000-0000-0000-0000-000000000050"&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"Inherited Delegated Permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Neo4j export&lt;/strong&gt;: exports the full graph to a Neo4j database for interactive exploration using Cypher queries. This is the most useful format for large tenants, for identifying non-obvious privilege escalation paths visually, or for preparing graph-based evidence for a report.&lt;/p&gt;
&lt;p&gt;Below is an example of a Cypher query, and its result, that finds all entities which have inherited an Entra ID role, along with the inheritance path:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-[&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;HAS_ENTRAID_ROLE&lt;/span&gt;&lt;span class="o"&gt;]-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;EntraIDRole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-[&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;HAS_ENTRAID_ROLE&lt;/span&gt;&lt;span class="o"&gt;]-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allShortestPaths&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-[&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;CONTROLS&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="o"&gt;]-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NONE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-[&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;HAS_ENTRAID_ROLE&lt;/span&gt;&lt;span class="o"&gt;]-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;RETURN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img alt="Neo4j paths to entities with inherited Entra ID roles" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/neo4j_path_entraidrole.png" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;Below is another example of a Cypher query that finds all entities controlling all entities (&lt;code&gt;scope_level: "ALL"&lt;/code&gt;), and the path that leads to them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;scope_level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ALL"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-[&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;HAS_APP_ROLE&lt;/span&gt;&lt;span class="p"&gt;|:&lt;/span&gt;&lt;span class="n"&gt;HAS_ENTRAID_ROLE&lt;/span&gt;&lt;span class="o"&gt;]-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope_level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ALL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;

&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allShortestPaths&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-[&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;CONTROLS&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="o"&gt;]-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NONE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope_level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ALL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;RETURN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img alt="Neo4j paths to entities with highest privileges" class="align-center" src="resources/2026-04-30_ms-azure-permissions-inheritance/neo4j_path_scope_all.png" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;Note that a depth limit of 6 is set in the previous queries, but it can be adjusted depending on the size and complexity of the tenant. In practice, most privilege escalation paths are relatively short, and a limit of 6 is usually sufficient to capture them without overwhelming the output with excessively long and unlikely chains.&lt;/p&gt;
&lt;h3 id="limitations-and-caveats"&gt;Limitations and caveats&lt;/h3&gt;
&lt;p&gt;QAZPT is a proof of concept and has known gaps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Azure Resource Manager (ARM) permissions are not covered: Managed Identities with ARM RBAC roles are not analyzed and so are Kubernetes roles.&lt;/li&gt;
&lt;li&gt;Group membership is partially handled: transitive Entra ID roles granted through groups are included, but application role assignments via group membership are not.&lt;/li&gt;
&lt;li&gt;Privileged Identity Management (PIM) is not supported: roles that are only activated on-demand via PIM do not appear.&lt;/li&gt;
&lt;li&gt;The list of Entra ID roles and Graph application roles treated as privilege escalation paths is not exhaustive, and covers mainly the most commonly seen ones in assessments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion_1"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Auditing application permissions in Microsoft Entra ID is hard, and most of the difficulty comes from things that are not visible by default: hidden Service Principal credentials, undocumented or asymmetric API behaviors, and especially the transitive nature of permissions. An entity rarely accumulates risk by holding a single dangerous role; in practice, the worst exposures are the result of long chains where ownership, group membership, federated identity, and privileged role assignments stack on top of each other. Looking only at direct assignments gives you a fraction of the picture, and that fraction is often the least interesting one.&lt;/p&gt;
&lt;p&gt;This is exactly where most existing tooling stops, and where QAZPT focuses. By computing and aggregating effective permissions per entity, with each inherited permission traced back to its origin, it gives security assessors and auditors a starting point that the Azure Portal and most commercial tools do not provide. It is not a complete solution; ARM RBAC, full group inheritance, and PIM are still on the roadmap. However, it covers a large enough share of the real-world inheritance surface to be useful in actual assessments, both offensive and defensive. The source code is available on &lt;a href="https://github.com/quarkslab/QAZPT"&gt;GitHub&lt;/a&gt;, and contributions, issues, and feedback are welcome.&lt;/p&gt;</content><category term="Cloud"></category><category term="2026"></category><category term="cloud"></category><category term="azure"></category><category term="entra-id"></category><category term="tool"></category><category term="microsoft"></category><category term="pentest"></category><category term="oauth"></category></entry></feed>