This check flags Azure custom role definitions that grant wildcard (*) actions, which hand out far more permission than anyone needs. Replace the wildcards with the specific actions the role actually requires, and gate role definitions in CI so broad grants never reach production.
Azure RBAC gives you a clean way to scope permissions, but custom roles make it just as easy to throw that away. A single "*" in the actions array of a role definition turns a carefully named role like StorageOperator into something that can do almost anything in its assigned scope. This check catches those role definitions before an attacker, or a well meaning automation script, finds them first.
What this check detects
The authorization_wildcardactions check inspects every custom role definition in your Azure tenant and flags any that use a wildcard in the permissions block. Specifically, it looks at the permissions object inside a role definition and raises a finding when:
- The
actionsarray contains"*" - The
dataActionsarray contains"*" - A scoped wildcard like
"Microsoft.Compute/*"grants every operation on an entire resource provider (depending on your policy thresholds)
Here is what a problematic role definition looks like:
{
"properties": {
"roleName": "AppTeamOperator",
"description": "Lets the app team manage their resources",
"assignableScopes": [
"/subscriptions/00000000-0000-0000-0000-000000000000"
],
"permissions": [
{
"actions": [
"*"
],
"notActions": [],
"dataActions": [],
"notDataActions": []
}
]
}
}
That single "*" grants the assignee every management operation available across the entire subscription. The friendly role name hides how dangerous it actually is.
Note: Azure separates actions (management plane operations like creating a VM or reading a storage account's properties) from dataActions (data plane operations like reading blob contents or queue messages). A wildcard in either is a problem, but a dataActions wildcard is often overlooked because it does not show up in many RBAC reviews.
Why it matters
The whole point of a custom role is precision. If you needed everything, you would assign the built in Owner or Contributor role and move on. A custom role with a wildcard is the worst of both worlds: it looks scoped and intentional, but it behaves like a god mode grant.
The blast radius problem
When a role with "actions": ["*"] is assigned at a subscription scope, anyone holding that role can:
- Modify or delete production resources across every resource group
- Change network security group rules and open inbound access
- Read and rotate secrets in Key Vault if no
notActionsrestrict it - Create new role assignments and escalate privileges further
That last point is the dangerous one. If the wildcard role includes Microsoft.Authorization/roleAssignments/write (which "*" does), the holder can grant themselves or any compromised identity the Owner role. A medium severity service principal compromise becomes a full subscription takeover.
Danger: A wildcard custom role assigned to a service principal is a common privilege escalation path. If that service principal's credentials leak through a committed .env file, a misconfigured CI pipeline, or a phished developer, the attacker inherits everything the role can do, including the ability to create new role assignments and persist access.
The audit and compliance problem
Frameworks like CIS Azure Foundations, SOC 2, and ISO 27001 all expect least privilege to be enforced and demonstrable. A wildcard role makes that impossible. You cannot tell an auditor what an identity can do when the answer is "everything," and you cannot reason about the impact of a compromise.
How to fix it
The fix is conceptually simple: replace the wildcard with the explicit actions the role actually needs. The work is in figuring out what those actions are.
Step 1: Find your wildcard roles
List custom roles and inspect their permissions with the Azure CLI:
# List all custom role definitions in the current subscription
az role definition list --custom-role-only true \
--query "[].{name:roleName, id:name, actions:permissions[0].actions}" \
--output table
To pull the full definition of a specific role so you can edit it:
az role definition list --name "AppTeamOperator" --output json
Step 2: Determine what the role actually needs
The safest way to scope a role is to base it on real usage. Activity logs tell you which operations the assignees have actually performed:
# Pull recent write/action operations performed in the subscription
az monitor activity-log list \
--start-time 2024-01-01T00:00:00Z \
--query "[].{op:operationName.value, caller:caller, status:status.value}" \
--output table
Tip: If you do not have enough activity log history, start from a relevant built in role instead of a blank slate. Run az role definition list --name "Storage Account Contributor" and copy its actions array as your baseline, then trim what you do not need.
Step 3: Rewrite the role with explicit actions
Replace the wildcard with the specific operations. Here is the corrected version of the earlier example, scoped to what an app team operator genuinely needs:
{
"properties": {
"roleName": "AppTeamOperator",
"description": "Manage app team compute and storage resources, no role assignment rights",
"assignableScopes": [
"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/app-team-rg"
],
"permissions": [
{
"actions": [
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/restart/action",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Storage/storageAccounts/read",
"Microsoft.Storage/storageAccounts/listKeys/action",
"Microsoft.Resources/subscriptions/resourceGroups/read"
],
"notActions": [],
"dataActions": [],
"notDataActions": []
}
]
}
}
Note two other improvements here: the assignableScopes is narrowed to a single resource group rather than the whole subscription, and the description states the security intent explicitly.
Step 4: Apply the update
Warning: Tightening a role that is already assigned can break running workloads or automation. Before you apply, confirm which identities hold the role with az role assignment list --role "AppTeamOperator" and validate the new action set against their actual usage. Roll out to a non production subscription first if you can.
# Update the existing custom role from a definition file
az role definition update --role-definition app-team-operator.json
If you decide the role should not exist at all, remove it after confirming there are no active assignments:
Danger: Deleting a role that still has active assignments will strip those identities of their permissions immediately. Check and remove assignments first, then delete the role definition.
# Check for active assignments first
az role assignment list --role "AppTeamOperator" --all --output table
# Only then delete the definition
az role definition delete --name "AppTeamOperator"
Fixing it in infrastructure as code
If you manage roles with Terraform, the same principle applies. Avoid actions = ["*"] and list explicit operations:
resource "azurerm_role_definition" "app_team_operator" {
name = "AppTeamOperator"
scope = data.azurerm_resource_group.app_team.id
description = "Manage app team compute and storage, no role assignment rights"
permissions {
actions = [
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/restart/action",
"Microsoft.Storage/storageAccounts/read",
"Microsoft.Storage/storageAccounts/listKeys/action",
]
not_actions = []
}
assignable_scopes = [
data.azurerm_resource_group.app_team.id
]
}
The Bicep equivalent follows the same shape, with each action spelled out in the actions array rather than collapsed into a wildcard.
How to prevent it from happening again
Manual review does not scale. The reliable way to keep wildcards out is to block them before deployment and continuously scan what is already live.
Gate role definitions in CI/CD
Scan Terraform and Bicep for wildcard actions with a policy as code tool. With OPA / Conftest, a Rego rule like this fails the build:
deny[msg] {
resource := input.resource.azurerm_role_definition[name]
action := resource.permissions[_].actions[_]
action == "*"
msg := sprintf("Role '%s' uses wildcard action '*'. List explicit actions instead.", [name])
}
Run it as a required check in your pipeline:
terraform show -json plan.tfplan | conftest test --policy ./policies -
Enforce with Azure Policy
Azure Policy can audit or deny custom role creation at the management group level, giving you a backstop that catches roles created outside your IaC pipeline (for example, by someone clicking through the portal).
Note: Azure does not ship a built in policy that inspects custom role actions directly, but you can restrict who is allowed to create or modify role definitions at all by limiting Microsoft.Authorization/roleDefinitions/write to a small set of trusted identities. Combine that with a deny rule in CI, and the portal path closes too.
Continuous scanning
Roles drift. Someone always finds a reason to create a quick wildcard role "just for testing" that never gets cleaned up. Lensix runs the authorization_wildcardactions check on a schedule so a wildcard role that slips past your gates still surfaces as a finding within hours rather than at your next audit.
Best practices
- Prefer built in roles. Azure ships hundreds of granular built in roles. Reach for a custom role only when none of them fit, and start from the closest built in role's action set.
- Scope tightly. Set
assignableScopesto the narrowest resource group or resource that makes sense, not the whole subscription, and never the management group unless the role genuinely needs to span subscriptions. - Use
notActionsdeliberately, not as a fix for wildcards. Granting"*"and then carving out a fewnotActionsis still a wildcard role. Anything you forget to exclude is granted. List what you allow, not what you deny. - Separate role assignment rights. Keep
Microsoft.Authorization/*/writeout of operational roles entirely. Privilege to grant privilege belongs to a small, audited set of administrators. - Review assignments regularly. Use Azure AD access reviews or Privileged Identity Management to re-certify who holds powerful roles, and remove stale assignments.
- Name roles for their intent. A clear, specific name and description makes over scoped roles obvious in review and reminds the next engineer what the role is for.
A custom role should answer one question precisely: what can this identity do, and nothing more. Every wildcard you remove makes that answer something you can actually trust.

