Audits Microsoft Entra ID app registrations and service principals for the risk patterns that actually get exploited: overprivileged permissions, expired credentials, ownerless apps, and stale workload identities.
Most Entra tenants accumulate app registrations faster than they audit them. Each one is a workload identity that can hold permissions, own credentials, and act on the tenant's behalf. When someone leaves, when a project ends, when a POC gets forgotten, the app registration stays. Its permissions stay. Its secrets stay. Nobody owns it.
Existing tools either require a paid CSPM suite or are fragmented PowerShell scripts that each solve one piece. This scans all of it in one pass and emits a report you can read at a glance.
| Rule | Severity | What it means |
|---|---|---|
tier0-application-permission |
critical | App holds a permission that can escalate to full tenant control (Application.ReadWrite.All, RoleManagement.ReadWrite.Directory, etc.) |
broad-data-permission |
high | App holds tenant-wide access to mail, files, or user data |
no-owner (with credentials) |
high | Ownerless app that has active credentials — nobody accountable for rotation |
no-owner (no credentials) |
medium | Ownerless app registration with no defined accountable party |
never-signed-in |
high | Active credentials but no sign-in ever recorded |
stale-workload-identity |
high | Active credentials, no sign-in in the last 90 days |
expired-credential |
medium | Credential expired and still attached |
long-lived-credential |
medium | Credential minted with a lifetime over one year |
expiring-credential |
low | Credential expires within 30 days |
multi-tenant-app |
low | signInAudience allows external tenants to consent |
Run against a 72-app tenant seeded with intentional risk patterns via entra-lab-seed:
69 findings across 4 severity levels. Every finding on the page is a real, intentional risk pattern surfaced by the scanner.
Async collection via Microsoft Graph:
/applicationsfor app registrations, credentials, requested permissions/servicePrincipalsfor service principals, tags, and permissions actually held/servicePrincipals/{id}/appRoleAssignmentsfor granted application permissions (the one unavoidable fan-out)/oauth2PermissionGrantsfor delegated grants/reports/servicePrincipalSignInActivities(beta) for sign-in history- Server-side ownership filtering via
$filter=owners/$count eq 0withConsistencyLevel: eventual
Rules run over the collected snapshot and produce explainable findings — every finding carries the fact that produced it, not an opaque score.
Requires Python 3.10+ and an Entra ID tenant.
git clone https://github.com/Dfrank77/entra-workload-identity-scanner.git
cd entra-workload-identity-scanner
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtRegister an app in your Entra tenant with these Microsoft Graph application permissions:
Application.Read.AllDirectory.Read.AllAuditLog.Read.All
Grant admin consent. Create a client secret. Copy .env.example to .env and fill in the values.
cp .env.example .env
# edit .env with your TENANT_ID, CLIENT_ID, CLIENT_SECRETpython scan.pyPrints a CLI summary and writes scan-report.html next to the script. The HTML is self-contained: no CDN, no external CSS, no JS.
- The beta sign-in activity endpoint has documented lag on multi-tenant apps. The
stale-workload-identityrule degrades gracefully if the endpoint is unavailable. - The
no-ownerrule flags Microsoft first-party service principals as ownerless in some tenants. Filter them from your review, or extend the rule with anappOwnerOrganizationIdexclusion for Microsoft's tenant ID. - Recently created delegated grants may not appear in
/oauth2PermissionGrantsuntil Graph replication catches up.
MIT
