What it is
A service principal granted Owner, Contributor, or User Access Administrator directly at subscription scope. It's usually the identity behind a Terraform pipeline, a DevOps service connection, or a vendor integration, and it holds the entire subscription around the clock. Unlike a human admin, a service principal can't do MFA, gets far narrower conditional-access coverage than an interactive user, and can't activate through PIM: its power is standing by design. Whoever holds its client secret holds the subscription.
Why it happens
Subscription-scope Owner or Contributor is the path of least resistance when wiring up automation. The pipeline needs to create resource groups, so someone grants Contributor "on the subscription for now", and the deployment works, so nothing ever prompts a revisit. Vendor onboarding guides and quickstart docs routinely say "grant the app Contributor on your subscription" because it's the one instruction that works for every customer.
The identity also doesn't age the way a person does. A service principal never leaves the company, never changes teams, never triggers an offboarding checklist. Its secret, meanwhile, lives wherever automation secrets live: pipeline variables, config files, a developer's shell history. Access reviews tend to focus on people; the non-human identities holding the biggest grants are exactly the ones nobody re-examines.
Note the distinction from a role that's merely over-privileged for what it does (see AZF-0050, which is about activity not matching the role, for any principal). This footgun is static and specific: a service principal, a privileged built-in role, subscription scope. Even a busy, legitimate pipeline with this grant is carrying more standing power than it should.
What it costs / blast radius
There's no direct cost; the blast radius is the subscription. A leaked secret for a Contributor-holding SP lets an attacker create, modify, or delete every resource in the subscription: exfiltrate storage, spin up cryptomining fleets, delete backups. Owner and User Access Administrator are worse: they can grant further access, so the attacker mints their own persistence before you rotate the secret. (Azure behavior; authored assessment.) And because service-principal sign-ins are non-interactive, there's no MFA prompt, no unusual-login challenge, and often no human who'd notice: the abuse looks like automation doing automation things.
See it
authorizationresources
| where type =~ 'microsoft.authorization/roleassignments'
| extend principalType = tostring(properties.principalType),
scope = tostring(properties.scope),
roleId = tolower(tostring(properties.roleDefinitionId))
| where principalType == 'ServicePrincipal'
| where scope matches regex @'^/subscriptions/[^/]+$' // subscription scope exactly
| where roleId endswith '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' // Owner
or roleId endswith 'b24988ac-6180-42a0-ab88-20f7382dd24c' // Contributor
or roleId endswith '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' // User Access Administrator
| project principalId = tostring(properties.principalId), scope, roleId# Drop the subscription-wide grant, re-grant at the narrowest scope that works
az role assignment delete --assignee <appId> \
--role Owner --scope /subscriptions/<subscriptionId>
az role assignment create --assignee <appId> \
--role Contributor \
--scope /subscriptions/<subscriptionId>/resourceGroups/rg-workload
# Better still: remove the secret class entirely. Use a managed identity for
# Azure-hosted workloads, or federated (workload identity) credentials for
# CI/CD, so there is no client secret to leak in the first place.How StratoLens helps
StratoLens flags every privileged service-principal assignment at subscription scope, continuously, across all your subscriptions, from inside your own tenant. The Terraform SP that got Owner during setup two years ago shows up as a finding with the role, principal, and scope attached, instead of waiting to show up in an incident report.