How quarkslab/qazpt works
1. What this repository is
QAZPT is a Python CLI tool for analyzing Entra ID permission inheritance. Its architecture is centered on building an in-memory identity/permission graph, resolving control relationships, then computing effective permissions and exporting results in several formats.
2. Primary entrypoints
The runtime entrypoints are qazpt/__main__.py and qazpt/main.py, which expose the qazpt command and its inheritance subcommand. The CLI is the orchestration layer: it parses flags, loads data, selects analysis mode, and routes output to tree/CSV/JSON/Neo4j exporters.
3. Boundary between data acquisition and analysis
The code is split into a “fetch/auth” side and an “analysis” side. qazpt/az/auth.py and the qazpt/az/ad/* package model Entra ID entities and permissions, while qazpt/core/* contains the permission-inheritance logic that operates on those models. This is an important architectural boundary: Microsoft Graph access and identity modeling are kept separate from graph algorithms.
4. Azure/Entra ID domain model
qazpt/az/ad/user.py, application/*, credentials.py, and permissions/* define the domain objects for users, app registrations, service principals, roles, and permission grants. These classes represent the raw security inventory that the analyzer consumes, including app roles, delegated permissions, and Entra ID role membership.
5. Core graph representation
qazpt/core/graph/ is the central internal data structure. graph.py, node.py, and condensation.py suggest a graph abstraction with explicit support for strongly connected components and condensation into a DAG. This graph is where ownership, service-principal linkage, FIC relationships, and role-based control edges are materialized.
6. Inheritance resolvers as graph builders
The qazpt/core/graph/resolvers/ package is the main “relationship discovery” layer. Each resolver contributes one inheritance path:
direct ownership, service principal of an app registration, federated identity credentials, Entra ID roles, and Microsoft Graph application roles. These resolvers are what turn raw directory objects into control edges in the graph.
7. Permission computation pipeline
qazpt/core/analysis.py and qazpt/core/linker.py likely orchestrate the end-to-end analysis: resolve links, build the graph, then compute transitive permission propagation. The README shows two analysis strategies: a legacy DFS approach and a Tarjan-based condensation approach that collapses cycles before bottom-up propagation.
8. Important algorithmic boundary: cyclic vs condensed traversal
The repository explicitly supports two traversal modes because inheritance graphs can contain cycles. The condensed DAG path is the default and is the more complete approach; the legacy DFS path exists for compatibility or troubleshooting. This affects diagramming because graph construction and graph evaluation are not the same phase.
9. Permission sources and effective permissions
qazpt/core/permissions/sets.py and sources.py appear to define the vocabulary of permission sets and their origins. The output distinguishes direct permissions from inherited ones, and also tracks “highly privileged” entities that stop transitive expansion because they already control the directory.
10. Output and visualization layer
qazpt/ui/visualization.py and qazpt/ui/colors.py handle human-readable rendering of the inheritance tree and gained permissions. qazpt/ui/neo4j_export.py is a separate export path that writes the analyzed graph into Neo4j, including nodes, labels, and relationships described in the README. This is a distinct sink from the CLI text/CSV/JSON outputs.
11. Caching and repeated runs
qazpt/utils/cache.py plus the --local-cache-dir CLI option indicate a local persistence layer for fetched directory data. Cache use is an architectural optimization boundary: it reduces repeated Microsoft Graph calls and lets analysis run on previously collected snapshots.
12. External dependencies and runtime shape
The project is a Python package built with pyproject.toml, tested with pytest, and distributed with container support via Dockerfile and docker-compose.yml. Neo4j is the only explicitly managed external service in the repo; Microsoft Graph/Entra ID is the data source implied by the auth and permission model.
13. Test architecture
The tests/data/* fixtures are structured as captured directory snapshots for different entity types and permissions scenarios. The tests appear to validate inheritance output against those fixtures, which means the test suite is focused on graph correctness and output stability rather than live integration with Microsoft Graph.
14. Diagramming summary
A good architecture diagram should show: CLI entrypoint → auth/data retrieval/cache → domain entity modeling → resolver-driven graph construction → condensation/transitive permission analysis → output/export paths (terminal tree, CSV/JSON, Neo4j). The key conceptual boundaries are source data vs analysis graph, and direct relationships vs computed effective permissions.
Main entry points: auth.py Auth · main.py Main CLI · base.py Resolvers · analysis.py Analysis
Components 27
CLI
Command entrypoints and orchestration
Azure data
Authentication, requests, cache, and Entra ID domain models
- Auth · Microsoft Graph authentication and data loading boundary
qazpt/az/auth.py - Requests · Graph request helpers and transport
qazpt/core/requests.py - Cache · Snapshot persistence for repeated analysis runs
qazpt/utils/cache.py - Abstraction · Common AD entity abstractions
qazpt/az/ad/base/abstraction.py - Users · User entity model
qazpt/az/ad/user.py - Applications · App registration and service principal model
qazpt/az/ad/application/application.py - App perms · Application permission and grant model
qazpt/az/ad/application/permissions.py - Credentials · Secrets, certificates, and related identity credentials
qazpt/az/ad/credentials.py - Dir perms · Entra ID permission and role model
qazpt/az/ad/permissions/permissions.py - Broker · Coordinates collected data before analysis
qazpt/core/broker.py
Core engine
Graph construction and permission analysis
- Analysis · End-to-end inheritance computation
qazpt/core/analysis.py - Linker · Connects modeled entities into control relationships
qazpt/core/linker.py - Graph · In-memory identity and permission graph
qazpt/core/graph/graph.py - Node · Vertex representation for identities and permissions
qazpt/core/graph/node.py - Condense · Strongly connected component condensation into DAG
qazpt/core/graph/condensation.py - Resolvers · Relationship discovery and graph building
qazpt/core/graph/resolvers/base.py - Ownership · Direct ownership inheritance edges
qazpt/core/graph/resolvers/ownership.py - App SP · Application to service principal linkage
qazpt/core/graph/resolvers/app_sp.py - FIC · Federated identity credential inheritance
qazpt/core/graph/resolvers/fic.py - App roles · Microsoft Graph application role inheritance
qazpt/core/graph/resolvers/role_based/admin_app_roles.py - Entra roles · Directory role inheritance
qazpt/core/graph/resolvers/role_based/admin_entra_id_roles.py
Exports
Human-readable and Neo4j output sinks
- Tree view · Terminal-friendly inheritance visualization
qazpt/ui/visualization.py - Neo4j export · Writes analyzed graph to Neo4j
qazpt/ui/neo4j_export.py
Connections 31
- qazpt → Main CLI: starts · Module entrypoint invokes CLI dispatch
- Main CLI → Auth: loads · CLI requests authenticated directory data
- Main CLI → Broker: orchestrates · CLI hands off collection and analysis coordination
- Main CLI → Analysis: runs · CLI triggers inheritance analysis
- Main CLI → Tree view: renders · CLI can print tree-style results
- Main CLI → Neo4j export: exports · CLI can export the analyzed graph
- Auth → Requests: calls · Auth layer uses Graph request helpers
- Auth → Cache: reads/writes · Fetched snapshots can be cached locally
- Auth → Abstraction: models · Auth layer populates common AD abstractions
- Auth → Users: models · User objects are part of the source inventory
- Auth → Applications: models · Application and service principal objects are collected
- Auth → Credentials: models · Credential artifacts are collected
- Auth → Dir perms: models · Directory roles and grants are collected
- Broker → Auth: uses · Broker coordinates authenticated data access
- Analysis → Linker: delegates · Analysis starts by building relationships
- Linker → Resolvers: invokes · Linker applies resolver framework
- Resolvers → Ownership: includes · Ownership resolver contributes control edges
- Resolvers → App SP: includes · Service-principal resolver contributes control edges
- Resolvers → FIC: includes · Federated identity resolver contributes control edges
- Resolvers → App roles: includes · App role resolver contributes control edges
- Resolvers → Entra roles: includes · Entra role resolver contributes control edges
- Linker → Graph: builds · Linker materializes the internal control graph
- Graph → Node: contains · Graph is composed of typed nodes
- Graph → Condense: condenses · Cycle handling collapses SCCs into a DAG
- Condense → Perm sets: annotates · Condensation feeds effective permission computation
- Analysis → Sources: tracks · Analysis records where permissions came from
- Analysis → Tree view: produces · Analysis results can be rendered as text trees
- Analysis → Neo4j export: produces · Analysis results can be exported to Neo4j
- Perm sets → Sources: classifies · Permission sets are tied to their origins
- Tree view → Perm sets: shows · Rendered output highlights direct and inherited permissions
- Neo4j export → Graph: writes · Neo4j export serializes the analyzed graph