azure/azure-policy-advisor
Assess ARM template resources for Azure Policy compliance. Analyse the template, query existing subscription assignments via `az policy assignment list`, identify unassigned built-in and custom policies (CIS, NIST, FedRAMP), and emit a two-part report: template-fixable gaps (Part 1) and subscription-level policy assignments (Part 2). USE FOR: recommending Azure Policy assignments for an ARM template, auditing a subscription against CIS/NIST/general best practices, deciding which initiatives to assign at sub or management-group scope, distinguishing template-fixable vs platform-level governance gaps. DO NOT USE FOR: per-resource security configuration assessment (use azure-security-analyzer), RBAC role recommendations (use azure-role-selector), CAF naming abbreviations (use azure-naming-research), or pricing estimates (use azure-cost-estimator). INVOKES: az policy assignment list, az policy set-definition list, microsoft_docs_search, microsoft_docs_fetch.
npx skills add https://github.com/Azure/git-ape --skill azure-policy-advisor
Recommend Azure Policy assignments for ARM template resources by combining three sources of truth: existing Azure subscription policy state (assignments + definitions), Microsoft Learn built-in recommendations, and ARM template configuration analysis. The skill analyses each resource, queries the live subscription, classifies gaps, and produces per-resource recommendations with severity ratings, built-in/custom definition IDs, and ready-to-use implementation options.
azure-security-analyzerazure-role-selectorazure-naming-researchazure-cost-estimatorazure-resource-availabilityazure-drift-detectorAzure Template Generator agentScope: This skill assesses (a) ARM template resources you supply and (b) the existing policy state in the target Azure subscription (active assignments + unassigned custom definitions). It does not enumerate the live configuration of deployed resources — for that, use azure-drift-detector.
| Tool | Purpose |
|---|---|
| microsoft_docs_search | Search Microsoft Learn for built-in policy definitions per resource type |
| microsoft_docs_fetch | Retrieve full Microsoft Learn pages (built-in policies index, framework initiatives) |
| Requirement | How |
|---|---|
| Azure CLI logged in | az login (skill degrades gracefully — see Troubleshooting if az is unavailable) |
| MCP server microsoft.docs.mcp available | Configure in your MCP client; used by Step 4 to look up built-in policy IDs |
| ARM template OR resource type list | Provided by the user or generated by Azure Template Generator |
Read compliance preferences from the ## Compliance & Azure Policy section in copilot-instructions.md (available automatically in conversation context). Extract:
If no compliance section exists in copilot-instructions.md, ask the user:
Which compliance approach should I assess against?
1. General Azure best practices (recommended)
2. CIS Azure Foundations v3.0
3. NIST SP 800-53 Rev 5
4. Custom — tell me what to check
Then parse the ARM template (if provided) to extract all resource types:
Extract for each resource:
- Resource type (e.g., Microsoft.Storage/storageAccounts)
- Resource name
- Current security-relevant properties (cross-reference with security-analyzer output if available)
If {subscription-id} is not provided, discover it:
az account show --query id -o tsv — current default subscriptionaz account list --query "[].{id:id, name:name}" -o table — all subscriptions the user has access toIf {mg-name} is needed (for management-group-scoped queries) and not provided, list available management groups:
az account management-group list --query "[].{name:name, displayName:displayName}" -o tableIf multiple subscriptions or management groups exist, ask the user which one to assess — do not guess.
Before recommending new policies, discover what is already enforced in the target subscription and what custom definitions exist but are unassigned. This prevents redundant recommendations, surfaces enforcement gaps, and lets you prefer unassigned custom definitions over equivalent built-ins.
Run the discovery script:
bash .github/skills/azure-policy-advisor/scripts/discover_policy_state.sh \
--subscription "{subscription-id}" \
[--management-group "{mg-name}"] \
--output /tmp/policy-state.json
The script wraps az policy assignment list, az policy definition list, and az policy set-definition list at both subscription and (optional) management-group scope, then normalises the output into a single JSON document:
{
"subscription_id": "...", "management_group": "..." | null,
"assigned_policies": [ { policy_definition_id, enforcement_mode, scope, source: "subscription" | "management-group-inherited", ... } ],
"assigned_initiatives": [ { policy_set_definition_id, ... } ],
"unassigned_custom_policies": [ { definition_id, display_name, category, target_resource_type, effect, source: "subscription" | "management-group" } ],
"unassigned_custom_initiatives": [ { definition_id, display_name, ... } ],
"errors": [ "..." ] // non-fatal; partial results still usable
}
Use .assigned_policies keyed by policy_definition_id for the "already-assigned" lookup in Step 5. Use .unassigned_custom_policies filtered by target_resource_type matching ARM template resources to surface unassigned custom definitions that the platform team should consider assigning.
If az is not available or az login has not been run, the script exits non-zero and prints an error to stderr. Skip Step 5's assigned-vs-unassigned classification and note in the report:
⚠️ Could not query Azure subscription — existing assignments and custom
definitions unknown. Recommendations are based on Microsoft Learn and
template analysis only. Run `az login` and re-run for subscription-aware
assessment.
> Always verify policy and initiative definition IDs from Microsoft Learn (microsoft_docs_search / microsoft_docs_fetch) or by calling az policy set-definition list --query "[?contains(displayName, 'CIS')]" -o table and az policy definition list before recommending them for assignment. Definition IDs and display names change over time and across Microsoft cloud regions (Public, Government, China). Do not rely on memorized IDs from training data — emit only IDs you have verified live in this run.
For EACH resource type, query Microsoft Learn for current built-in policy definitions:
Tool: microsoft_docs_search
Query: "Azure Policy built-in {resource-type-category}"
Examples:
- "Azure Policy built-in Storage Accounts"
- "Azure Policy built-in App Service Function Apps"
- "Azure Policy built-in SQL Server database"
- "Azure Policy built-in Key Vault"
- "Azure Policy built-in Kubernetes AKS"
- "Azure Policy built-in virtual machines compute"
- "Azure Policy built-in network security"
- "Azure Policy built-in monitoring diagnostic settings"
For compliance frameworks, also query:
Tool: microsoft_docs_search
Query: "Azure Policy built-in initiative {framework-name}"
Examples:
- "Azure Policy built-in initiative CIS Azure Foundations"
- "Azure Policy built-in initiative NIST SP 800-53"
- "Azure Policy regulatory compliance initiative"
When search results reference a high-value page, use microsoft_docs_fetch to retrieve the full content:
Tool: microsoft_docs_fetch
URL: https://learn.microsoft.com/azure/governance/policy/samples/built-in-policies
Use this to get the complete list of built-in policies organized by category (Storage, App Service, SQL, Key Vault, Network, Monitoring, etc.)
Key Microsoft Learn reference pages: Read references/ms-learn-policy-pages.md when you need a specific Microsoft Learn URL (canonical built-in policies list, framework-specific pages for CIS/NIST/FedRAMP/PCI-DSS, ARM assignment syntax) — it lists the high-value entry points with guidance on which to fetch when.
For each recommended policy, assign a severity tier, classify its current status against the ARM template and Steps 2–3 inventory, and resolve precedence when multiple sources cover the same control. Read references/classification-rules.yaml for the full rule set — it defines the four severity tiers (Critical/High/Medium/Low with Audit/Deny effects), the five status classifications (✅ Already assigned, 🔵 Compliant via template, 🟣 Unassigned custom available, ⚠️ Gap, 🔄 Complementary), and the three-rule precedence ladder (assigned_wins → custom_over_builtin → builtin_fallback).
Per resource type, prioritize policy categories ranked by severity. Read references/per-resource-policy-priorities.md when classifying recommendations for any of these resource types: Storage Accounts, App Service / Function Apps, SQL Servers / Databases, Key Vault, Compute / VMs, AKS / Kubernetes, Networking, or general cross-cutting controls. For resource types not in that list, fall back to the microsoft_docs_search query template in Step 4.
Present findings split into two clear action tracks: template improvements (changes to the ARM template) and subscription-level actions (policy/initiative assignments). This separation clarifies who needs to act and where.
Report outline — Read references/policy-assessment-template.md before producing the final markdown report. The template includes the canonical heading order, table column definitions for each section, and example rows. Skim the headings here, then fetch the full template:
## Azure Policy Compliance Assessment
**Scope** · **Deployment** · **Compliance Framework** · **Enforcement Mode** · **Subscription Policy State**
### Summary — table of {Recommended, Already Assigned, Template Compliant, Template Fixable, Subscription-Level Gap} per category
## Part 1: Template Improvements (developer acts; edit the ARM template)
### Gaps Fixable in the Template — table of {Resource Type, Gap, Compliance Control, Fix}
### Already Compliant in Template — table of {Resource Type, Property, Template Value, Compliance Control}
## Part 2: Subscription-Level Actions (platform team acts; assign at sub/mg scope)
### Existing Policy Assignments — table of {Policy/Initiative, Scope, Enforcement, Type, Relevant?}
### Unassigned Custom Policies — table of {Policy, Category, Target Resource, Effect, Scope}
### Recommended Built-in Assignments — table of {Policy, Effect, Severity, Definition ID, Category, Source}
### Recommended Compliance Initiative — table of {Initiative, Policies, Built-in ID, Status}
Split implementation guidance into both tracks:
Track 1: Template Changes (for gaps in Part 1)
For each gap listed in Part 1, provide the exact ARM template JSON to add. Include:
dependsOn referencesPresent as ready-to-copy code blocks that the developer can insert into the nested template's resources array. Group related resources together (e.g., Log Analytics workspace + all diagnostic settings that depend on it).
Track 2: Subscription-Level Assignments (for gaps in Part 2)
Option A: Azure CLI (quickest for individual policies)
# Assign unassigned custom policies (prioritize — they already exist)
az policy assignment create \
--name "{custom-policy-short-name}" \
--display-name "{custom policy display name}" \
--policy "{full-custom-definition-id-with-mg-scope}" \
--scope "/subscriptions/{subscription-id}" \
--enforcement-mode DoNotEnforce
# Assign a built-in policy at subscription scope
az policy assignment create \
--name "{policy-short-name}" \
--display-name "{policy display name}" \
--policy "{built-in-definition-id}" \
--scope "/subscriptions/{subscription-id}" \
--params '{}' \
--enforcement-mode Default
# Assign a compliance initiative
az policy assignment create \
--name "{initiative-short-name}" \
--display-name "{initiative display name}" \
--policy-set-definition "{initiative-id}" \
--scope "/subscriptions/{subscription-id}"
Option B: ARM Template (for IaC-managed policies)
{
"type": "Microsoft.Authorization/policyAssignments",
"apiVersion": "2024-04-01",
"name": "{assignment-name}",
"properties": {
"displayName": "{display name}",
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/{built-in-id}",
"scope": "/subscriptions/{subscription-id}",
"enforcementMode": "Default",
"parameters": {}
}
}
Enforcement mode guidance:
| Mode | When to Use |
|------|-------------|
| Default | Active enforcement — new non-compliant resources are denied or audited |
| DoNotEnforce | Audit-only — evaluates compliance without blocking. Recommended for initial rollout |
The policy gate is advisory — it surfaces findings without blocking deployment.
### Policy Gate: ADVISORY
**Part 1 — Template Compliance:**
🔵 {T} of {R} checks pass via template configuration
🔧 {F} gaps can be fixed by updating the template (see Part 1)
**Part 2 — Subscription Enforcement:**
✅ {A} policies already assigned in subscription
🟣 {C} custom policies available but unassigned — assign for immediate coverage
⚠️ {S} subscription-level gaps — recommend assigning built-in policies or initiative
Enforcement coverage: {percentage}% of recommended policies actively assigned
**Action items:**
_Template (developer):_
1. 🔧 {list of template changes from Part 1, e.g. "Add blob soft delete", "Add diagnostic settings"}
_Subscription (platform team):_
1. 🟣 Assign existing custom policies: {list — these already exist, just need assignment}
2. ⚠️ Assign built-in policies: {list of built-in policies to assign}
3. {initiative to assign if framework selected}
When invoked during a deployment workflow, save results to the deployment directory:
| File | Format | Content |
|------|--------|---------|
| policy-assessment.md | Markdown | Full assessment report (Section 4 output) |
| policy-recommendations.json | JSON | Structured policy data for automation |
JSON structure for policy-recommendations.json — Read references/policy-recommendations-schema.json when emitting the JSON sidecar. The reference includes complete field definitions, status/actionTrack enum values, and a fully-worked example. Skeleton:
{
"assessedAt": "...", "deploymentId": "...", "framework": "...",
"enforcementMode": "Audit|Deny", "subscriptionState": "queried|unavailable",
"summary": { "totalRecommended": 16, "alreadyAssigned": 2, "templateCompliant": 8,
"templateFixable": 3, "customAvailable": 1, "subscriptionGaps": 3 },
"existingAssignments": [ /* from Step 2 .assigned_policies */ ],
"unassignedCustomDefinitions": [ /* from Step 2 .unassigned_custom_policies */ ],
"templateImprovements": [ /* Part 1 gaps */ ],
"policies": [ /* full recommendation list with status + actionTrack */ ],
"initiative": { /* compliance-framework initiative if selected */ }
}
Status values for the status field:
| Value | Meaning | Action Track |
|-------|---------|-------------|
| already-assigned | Policy actively assigned in subscription (✅) | none |
| template-compliant | ARM template satisfies the policy, not assigned (🔵) | subscription (assign to prevent drift) |
| template-fixable | Gap that can be closed by modifying the template (🔧) | template |
| custom-available | Custom definition exists but not assigned (🟣) | subscription |
| gap | Not covered — recommend built-in policy assignment (⚠️) | subscription |
| complementary | Would add enforcement on top of existing config (🔄) | subscription |
Action track values for the actionTrack field:
| Value | Meaning |
|-------|---------|
| template | Fix by modifying the ARM template (Part 1 — developer) |
| subscription | Fix by assigning a policy at subscription scope (Part 2 — platform team) |
| none | No action needed (already assigned or compliant) |
/azure-security-analyzer, optionally invoke /azure-policy-advisor to recommend subscription-level policies that complement the templatecopilot-instructions.md — this skill reads them automaticallyExample 1 — Post-template recommendation (general best practices)
> "I just generated an ARM template with a Storage Account, Function App, and Key Vault for production. What Azure Policies should we enforce? Separate template fixes from subscription-level assignments."
Skill activates → Step 1 reads "General best practices" + Audit from copilot-instructions → Step 2 runs discover_policy_state.sh → finds SecurityCenterBuiltIn (MCSB) initiative covers 13 of 17 recommendations → Step 6 emits Part 1 (4 template fixes: blob soft delete + 3 diagnostic settings) and Part 2 (3 monitoring built-ins to assign).
Example 2 — Compliance framework audit
> "Audit our subscription resources against CIS Azure Foundations v3.0 — what's covered and what's missing?"
Skill activates → Step 1 captures framework: CIS Azure Foundations v3.0 → Step 2 discovers existing assignments → Step 4 fetches the CIS Azure Foundations Benchmark v3.0.0 built-in initiative ID from Microsoft Learn → Step 3 verifies the ID via az policy set-definition list --query "[?contains(displayName, 'CIS')]" → Step 6 emits per-control coverage + recommended initiative assignment.
| Symptom | Cause | Resolution |
|---|---|---|
| discover_policy_state.sh exits non-zero, errors about az login | Azure CLI not authenticated | Run az login and retry. If unauthenticated by design (e.g., template-only review), the skill still produces Part 1 + recommended built-ins from Microsoft Learn, with subscriptionState: "unavailable" in the JSON sidecar |
| Recommended definition ID returns "not found" from az policy definition show | Display-name drift or wrong cloud (Public vs Government vs China) | Re-verify via Step 3: az policy set-definition list --query "[?contains(displayName, '<keyword>')]" -o table. Definition IDs are stable; display names evolve (e.g., "purge protection" → "deletion protection") |
| Skill reports ⚠️ for a policy already enforced via initiative | assigned_policies lookup keyed on individual policy IDs; initiative members aren't expanded | Cross-check assigned_initiatives in the policy-state JSON — if MCSB (1f3afdf9-d0c9-4c3d-847f-89da613e70a8) is assigned, 200+ individual policies are covered indirectly. Filed as a known limitation; expansion via az policy set-definition show --query "policyDefinitions[].policyDefinitionId" is on the roadmap |
| microsoft_docs_search returns stale or empty results | MCP server not configured, or Microsoft Learn page moved | Fall back to microsoft_docs_fetch with the canonical URL from references/ms-learn-policy-pages.md (built-in policies index) |
Take azure/azure-policy-advisor from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.