Microsoft Defender for Cloud -- How-To Guide
Audience: Team lead, team members, and future reference Subscription: HISD (
9639039c-86c6-4859-b014-d28688863cd5) Jira Project: DEVOPS Last updated: 2026-02-18
Table of Contents
- Overview
- Understanding Severity
- Jira Structure
- Monthly Intake Workflow
- Triage Checklist
- Wave Prioritization
- Closing a Finding
- Common JQL Queries
- az CLI Quick Reference
1. Overview
What Is Defender for Cloud?
Microsoft Defender for Cloud is Azure’s built-in cloud security posture management (CSPM) and workload protection platform. It continuously evaluates your Azure resources against security best practices and regulatory standards, then surfaces recommendations — actionable findings that tell you what to fix and why.
For eshars, Defender monitors everything in the HISD subscription: App Services, Azure SQL databases, Key Vaults, storage accounts, virtual machines, networking, and identity configuration.
Why We Track It
- Security score: Defender assigns a score (0-100%) to the subscription. Each unresolved recommendation lowers the score. Resolving findings raises it. The score is visible to auditors, leadership, and Microsoft.
- Compliance: As a SHARS/Medicaid platform handling student health data, we operate under HIPAA and state compliance requirements. Defender findings often map directly to compliance gaps.
- Attack surface: Unresolved findings represent real risk. Defender’s attack path analysis connects findings to show how an attacker could chain vulnerabilities (e.g., internet-exposed VM + unpatched CVE = remote code execution path).
- Auditability: Tracking findings in Jira creates a documented trail of what was found, when, what was done about it, and who approved any risk acceptance decisions.
Security Score Impact
Each recommendation carries a max score increase — the number of points the security score gains when the finding is fully resolved across all affected resources. Partially resolving a finding (e.g., fixing 3 of 5 affected resources) gives proportional credit.
Key points:
- Resolving a single high-impact recommendation can raise the score by several percentage points.
- Findings affecting many resources (e.g., “Storage accounts should restrict network access” across 12 accounts) have outsized score impact because every affected resource contributes to the total.
- The score only updates when Defender’s next assessment cycle runs (typically within 24 hours of a change).
2. Understanding Severity
Defender for Cloud presents findings through two different lenses. We track both.
Recommendation Severity (Static)
Every Defender recommendation has a fixed severity assigned by Microsoft:
| Severity | Meaning |
|---|---|
| High | Significant security weakness. Exploitation could lead to data breach, privilege escalation, or compliance violation. |
| Medium | Moderate security gap. Increases attack surface but typically requires additional conditions to exploit. |
| Low | Best-practice hardening. Defense-in-depth measure or configuration improvement. |
This severity is static — it is a property of the recommendation itself and does not change based on your environment. “Storage accounts should prevent shared key access” is always High, regardless of which storage account it applies to.
This is how our Jira Epics are organized. Each Epic corresponds to a recommendation severity level.
Risk-Based View (Dynamic)
Defender also provides a risk-based prioritization that overlays context on top of recommendation severity:
| Risk Level | Meaning |
|---|---|
| Critical | Recommendation + exposure factors (internet-facing, attack path exists, sensitive data involved) |
| High | Recommendation + moderate exposure factors |
| Medium | Standard risk, no aggravating factors |
| Low | Minimal exposure, low exploitability |
This view is dynamic — it changes as your environment changes. A High-severity recommendation on an internet-exposed VM with known CVEs might be elevated to Critical risk. The same recommendation on an isolated dev VM might stay at High or Medium risk.
Risk-based factors include:
- Attack paths: Defender maps chains of findings that an attacker could traverse (e.g., internet exposure + vulnerability + privileged identity = attack path to data).
- Internet exposure: Is the affected resource reachable from the public internet?
- Sensitive data: Does the resource hold or process sensitive information?
- Lateral movement potential: Could compromising this resource lead to others?
How We Track Both
| Dimension | Where We Track It | Changes Over Time? |
|---|---|---|
| Recommendation severity (H/M/L) | Jira Epics (DEVOPS-82 through DEVOPS-86) | No — static per recommendation |
| Risk-based level (Critical/High/Medium/Low) | Jira labels (risk-critical, risk-high) | Yes — re-evaluate periodically |
| Attack path involvement | Jira label (attack-path) | Yes — Defender updates this dynamically |
| Environment | Jira labels (env-prod, env-nonprod) | No — resource is either prod or not |
This dual-tracking lets us answer two different questions:
- “What kind of problem is this?” — Look at the Epic (recommendation severity).
- “How urgent is it right now?” — Look at the risk/environment labels.
3. Jira Structure
Severity Epics
| Epic Key | Name | Recommendation Severity | Priority | SLA | Use For |
|---|---|---|---|---|---|
| DEVOPS-82 | Defender - High | High | Highest | 1 week | High-severity Defender recommendations |
| DEVOPS-83 | Defender - Medium | Medium | Medium | 2 weeks | Medium-severity Defender recommendations |
| DEVOPS-84 | Defender - Low | Low | Low | 1-2 sprints | Low-severity / best-practice recommendations |
| DEVOPS-85 | Defender - Non-Issue | N/A | Lowest | Document and close | False positives, not applicable, compensating control exists |
| DEVOPS-86 | Defender - Won’t Do | N/A | Low | Awaiting approval | Accepted risk — team lead must approve before closing |
Label Taxonomy
Every Defender finding in Jira must have the Defender label. Additional labels provide filtering dimensions:
| Label | Category | Purpose |
|---|---|---|
Defender | Required | Identifies all Defender-sourced findings |
defender-high | Severity | Matches High recommendation severity (Epic DEVOPS-82) |
defender-medium | Severity | Matches Medium recommendation severity (Epic DEVOPS-83) |
defender-low | Severity | Matches Low recommendation severity (Epic DEVOPS-84) |
env-prod | Environment | Finding affects at least one production resource |
env-nonprod | Environment | Finding affects at least one non-production resource |
risk-critical | Risk | Risk-based view rates this as Critical |
risk-high | Risk | Risk-based view rates this as High |
attack-path | Risk | Defender identified this in an attack path analysis |
Notes:
- A finding can have both
env-prodandenv-nonprodif the same recommendation applies to resources in both environments. - Risk labels (
risk-critical,risk-high) are added based on the dynamic risk-based view and should be re-evaluated periodically. - The
attack-pathlabel is for findings that Defender has connected into an exploitable chain. These deserve elevated attention regardless of recommendation severity.
Priority Mapping
| Epic | Jira Priority | Rationale |
|---|---|---|
| DEVOPS-82 (High) | Highest | Direct security weakness with potential for breach |
| DEVOPS-83 (Medium) | Medium | Security gap requiring remediation within sprint cycle |
| DEVOPS-84 (Low) | Low | Best-practice improvement, backlog-appropriate |
| DEVOPS-85 (Non-Issue) | Lowest | Document rationale, then close |
| DEVOPS-86 (Won’t Do) | Low | Awaiting team lead approval for risk acceptance |
Ticket Creation Checklist
When creating a new Jira Task for a Defender finding:
- Issue Type: Task
- Epic Link: Set to the matching severity Epic
- Priority: Set to match the Epic (see table above)
- Labels:
Defender+ severity label + environment label(s) + risk label (if applicable) - Summary: Use the Defender recommendation title verbatim (e.g., “Storage accounts should restrict network access using VNet rules”)
- Description: Include:
- Defender recommendation name
- Affected resource(s) — name, resource group, environment
- Defender’s remediation guidance
- Link to Defender recommendation in Azure Portal (if available)
- Any notes on impact to eshars applications
JQL Quick Examples
# All Defender findingsproject = DEVOPS AND labels = Defender ORDER BY priority DESC
# Open findings onlyproject = DEVOPS AND labels = Defender AND status != Done ORDER BY priority DESC
# High severity, productionproject = DEVOPS AND labels = Defender AND labels = defender-high AND labels = env-prod AND status != Done
# Everything with an attack pathproject = DEVOPS AND labels = Defender AND labels = attack-path AND status != Done4. Monthly Intake Workflow
Perform this workflow at least monthly to pull new Defender findings and reconcile with Jira.
Step 1: Set the subscription context
az account set --subscription 9639039c-86c6-4859-b014-d28688863cd5Verify:
az account show --query "{name:name, id:id}" -o tableStep 2: Pull assessment metadata (recommendation definitions)
az security assessment-metadata list -o json > metadata.jsonThis file contains the full catalog of Defender recommendations, including their displayName, severity, description, and remediationDescription. It provides the static severity (High/Medium/Low) for each recommendation.
Step 3: Pull unhealthy assessments (active findings)
az security assessment list \ --query "[?status.code=='Unhealthy']" \ -o json > assessments.jsonThis file contains every recommendation that currently has at least one non-compliant resource. Each entry includes:
displayName: The recommendation titlestatus.code:Unhealthy(filtered above)resourceDetails: Which specific resource is affectedname: The assessment ID (used to join with metadata)
Step 4: Parse and join the data
python security/parse_defender.py metadata.json assessments.json > findings.jsonThe script (security/parse_defender.py) joins the two files on assessment ID to produce a unified output with:
- Each unique recommendation title
- Its Defender severity
- Count of affected resources
- List of affected resource names, resource groups, and resource IDs
Output goes to stdout as JSON. Progress messages go to stderr.
Step 5: Review the parsed output
Open findings.json and review:
# Quick summary -- how many recommendations by severity?python -c "import jsonwith open('findings.json') as f: data = json.load(f)print(f'Total unhealthy: {data[\"total_unhealthy\"]}')print(f'Unique recommendations: {data[\"unique_recommendations\"]}')for sev, count in data['by_severity'].items(): print(f' {sev}: {count}')"Step 6: Cross-reference against register and Jira
For each recommendation in findings.json:
- Check the register (
security/defender-register.md) — is this recommendation already tracked? - Check Jira — run the JQL:
project = DEVOPS AND labels = Defender AND summary ~ "recommendation title" - Classify each result:
| Situation | Action |
|---|---|
| New finding, not in register or Jira | Create new Jira ticket + add to register |
| In register and Jira, still open | No action needed (already tracked) |
| In register and Jira, marked Done, but still Unhealthy | Reopen the Jira ticket — the fix was incomplete or has regressed |
| In register but not in Jira | Create the Jira ticket (register was not kept in sync) |
| In Jira but not in findings | Good news — finding may have been resolved. Verify in Azure Portal before closing. |
Step 7: Create new tickets and update the register
For each new finding:
- Create a Jira Task following the Ticket Creation Checklist
- Classify the affected resources as prod or non-prod using naming conventions:
- Prod: names containing
prod,prodwest,sharsprod - Non-prod: names containing
nonprod,dev,uat,qa,training,regression - Ambiguous: flag for team lead confirmation
- Prod: names containing
- Add the appropriate environment labels (
env-prod,env-nonprod, or both) - Add a row to the register under the correct severity section
- Update the register summary counts
Step 8: Archive the raw data
Move the raw files to a dated archive:
mv metadata.json security/metadata-$(date +%Y-%m-%d).jsonmv assessments.json security/assessments-$(date +%Y-%m-%d).jsonmv findings.json security/findings-$(date +%Y-%m-%d).jsonConsider adding raw JSON files to .gitignore if they are too large to commit. The register and Jira are the sources of truth.
5. Triage Checklist
Use this checklist when evaluating a new Defender finding. Work through each question and document the answers in the Jira ticket description or a comment.
Assessment Questions
| # | Question | Why It Matters |
|---|---|---|
| 1 | What is the Defender recommendation severity? (High/Medium/Low) | Determines which Epic the ticket belongs to |
| 2 | Which resources are affected? List names and resource groups. | Scoping: How many resources? Which environments? |
| 3 | Are any affected resources in production? | Production findings get higher wave priority |
| 4 | Is there an attack path involving this finding? Check Defender’s attack path analysis. | Attack paths indicate chained exploitability |
| 5 | What is the risk-based severity? (Critical/High/Medium/Low in Defender’s risk view) | May elevate priority beyond the static recommendation severity |
| 6 | Is the resource internet-exposed? | Internet exposure significantly increases risk |
| 7 | Does this affect data at rest or in transit? (PII, PHI, credentials) | Compliance implications for HIPAA/Medicaid |
| 8 | Is there a compensating control already in place? | If yes, document it — may qualify as Non-Issue |
| 9 | What does Defender recommend for remediation? | Guides the implementation work |
| 10 | What is the blast radius? Could the fix break application functionality? | Determines QA requirements and rollout caution |
Decision Outcomes
After working through the checklist, assign one of these dispositions:
| Disposition | Action |
|---|---|
| Remediate | Assign to severity Epic, add to sprint backlog per wave priority |
| Non-Issue | Move to DEVOPS-85, document justification (compensating control, false positive, not applicable) |
| Won’t Do | Move to DEVOPS-86, document justification + residual risk. Requires team lead approval (see below) |
| Needs Investigation | Keep in severity Epic, add comment noting what needs to be determined before remediation can begin |
”Won’t Do” Approval Process
No finding may be closed as “Won’t Do” without team lead sign-off:
- Engineer adds a comment to the ticket with:
- Justification: Why the recommendation will not be implemented
- Compensating controls: What mitigations exist instead
- Residual risk: What risk remains and why it is acceptable
- Engineer moves the ticket to Epic DEVOPS-86 (Won’t Do — Pending Approval)
- Team lead reviews and adds a comment:
[APPROVED - Won't Do] - Only after approval can the ticket be transitioned to Done
- If the team lead disagrees, they reassign back with revised instructions
6. Wave Prioritization
Remediation work is organized into waves that balance severity with environment impact. Production findings of the same severity always come before non-production.
Wave Definitions
| Wave | Scope | Target Timeline | JQL Filter |
|---|---|---|---|
| Wave 1 | High + prod | Current sprint | project = DEVOPS AND labels = Defender AND labels = defender-high AND labels = env-prod AND status != Done |
| Wave 2 | High + non-prod, Medium + prod | Next sprint | project = DEVOPS AND labels = Defender AND ((labels = defender-high AND labels = env-nonprod) OR (labels = defender-medium AND labels = env-prod)) AND status != Done |
| Wave 3 | Medium + non-prod | Following sprint | project = DEVOPS AND labels = Defender AND labels = defender-medium AND labels = env-nonprod AND status != Done |
| Wave 4 | Low + prod | Backlog | project = DEVOPS AND labels = Defender AND labels = defender-low AND labels = env-prod AND status != Done |
| Wave 5 | Low + non-prod | Backlog | project = DEVOPS AND labels = Defender AND labels = defender-low AND labels = env-nonprod AND status != Done |
Wave Escalation
Findings with these characteristics should be escalated to an earlier wave regardless of recommendation severity:
attack-pathlabel present — Defender has identified an exploitable chainrisk-criticallabel present — Risk-based view rates the finding as Critical- Finding involves credentials, secrets, or identity (PII/PHI exposure potential)
- Finding has been open for longer than the SLA for its severity tier
Sprint Planning Integration
During sprint planning:
- Run the Wave 1 JQL to identify must-do items for the current sprint
- Run the Wave 2 JQL to identify candidates for the sprint if capacity allows
- Check for any escalated items from lower waves
- Review attack-path items separately:
project = DEVOPS AND labels = attack-path AND status != Done
7. Closing a Finding
Closing a Defender finding requires verification across multiple systems. Do not skip steps.
Step 1: Verify in Defender
Confirm the finding is resolved in Azure:
# Check status of a specific recommendation by titleaz security assessment list \ --query "[?displayName=='<RECOMMENDATION TITLE>' && status.code=='Unhealthy']" \ -o table- If the query returns no results, the finding is resolved (Healthy).
- If it returns results, the finding is still active. Investigate further before closing.
Note: Defender assessments can take up to 24 hours to reflect changes. If you just applied a fix, wait and re-check.
You can also verify in the Azure Portal:
- Navigate to Defender for Cloud > Recommendations
- Search for the recommendation by name
- Confirm all previously affected resources now show as Healthy
Step 2: Update the Jira ticket
- Add a closing comment with:
- Confirmation that the finding shows as Healthy in Defender
- Summary of what was done to resolve it
- Date verified
- Any follow-up actions (e.g., monitoring for regression)
- Transition the ticket to Done
Step 3: Update the register
In security/defender-register.md:
- Move the finding row from the Open section to the Completed section under the appropriate severity
- Update the Status column to
Done - Update the Summary counts table
Step 4: Write release notes
For each batch of resolved findings:
- Copy the template from
security/release-notes/TEMPLATE.md - Create a new file:
security/release-notes/YYYY-MM-DD-batch-N.md - Fill in each section per the template:
- What happened: The Defender finding and why it matters
- Solution: Step-by-step remediation actions taken
- QA considerations: Testing done to verify no application impact
- Deployment considerations: Any restarts, downtime, or deployment steps
- Commit to the repository
Step 5: Handle “Won’t Do” closures
If closing as Won’t Do (rather than resolved):
- Ensure team lead has added
[APPROVED - Won't Do]comment on the Jira ticket - Move the ticket to Epic DEVOPS-86
- In the register, add the finding to a “Won’t Do” section with the justification
- No release notes needed — the approval comment and register entry serve as the audit trail
- The finding will remain Unhealthy in Defender. This is expected and accepted.
8. Common JQL Queries
Copy-paste these into Jira’s search bar or save as filters.
By Status
-- All Defender findings (any status)project = DEVOPS AND labels = Defender ORDER BY priority DESC
-- Open findings onlyproject = DEVOPS AND labels = Defender AND status != Done ORDER BY priority DESC
-- Completed findingsproject = DEVOPS AND labels = Defender AND status = Done ORDER BY resolved DESCBy Severity Epic
-- High severityproject = DEVOPS AND "Epic Link" = DEVOPS-82 AND status != Done ORDER BY priority DESC
-- Medium severityproject = DEVOPS AND "Epic Link" = DEVOPS-83 AND status != Done ORDER BY priority DESC
-- Low severityproject = DEVOPS AND "Epic Link" = DEVOPS-84 AND status != Done ORDER BY priority DESC
-- Non-Issueproject = DEVOPS AND "Epic Link" = DEVOPS-85
-- Won't Do (pending approval)project = DEVOPS AND "Epic Link" = DEVOPS-86 AND status != DoneBy Environment
-- All open production findingsproject = DEVOPS AND labels = Defender AND labels = env-prod AND status != Done ORDER BY priority DESC
-- All open non-production findingsproject = DEVOPS AND labels = Defender AND labels = env-nonprod AND status != Done ORDER BY priority DESC
-- Findings affecting both prod and non-prodproject = DEVOPS AND labels = Defender AND labels = env-prod AND labels = env-nonprod AND status != DoneBy Risk / Attack Path
-- Critical risk findingsproject = DEVOPS AND labels = Defender AND labels = risk-critical AND status != Done
-- High risk findingsproject = DEVOPS AND labels = Defender AND labels = risk-high AND status != Done
-- Attack path findingsproject = DEVOPS AND labels = Defender AND labels = attack-path AND status != Done
-- Critical risk OR attack path (most urgent)project = DEVOPS AND labels = Defender AND (labels = risk-critical OR labels = attack-path) AND status != DoneWave Queries
-- Wave 1: High + prodproject = DEVOPS AND labels = Defender AND labels = defender-high AND labels = env-prod AND status != Done
-- Wave 2: High + non-prod OR Medium + prodproject = DEVOPS AND labels = Defender AND ((labels = defender-high AND labels = env-nonprod) OR (labels = defender-medium AND labels = env-prod)) AND status != Done
-- Wave 3: Medium + non-prodproject = DEVOPS AND labels = Defender AND labels = defender-medium AND labels = env-nonprod AND status != Done
-- Wave 4: Low + prodproject = DEVOPS AND labels = Defender AND labels = defender-low AND labels = env-prod AND status != Done
-- Wave 5: Low + non-prodproject = DEVOPS AND labels = Defender AND labels = defender-low AND labels = env-nonprod AND status != DoneOperational
-- Overdue items (created more than 30 days ago, still open)project = DEVOPS AND labels = Defender AND status != Done AND created <= -30d ORDER BY created ASC
-- Recently resolved (last 14 days)project = DEVOPS AND labels = Defender AND status = Done AND resolved >= -14d ORDER BY resolved DESC
-- Needs triage (To Do, no severity label)project = DEVOPS AND labels = Defender AND status = "To Do" AND labels NOT IN (defender-high, defender-medium, defender-low)9. az CLI Quick Reference
All commands assume the subscription is already set:
az account set --subscription 9639039c-86c6-4859-b014-d28688863cd5Security Assessments
# List all unhealthy assessments (summary table)az security assessment list \ --query "[?status.code=='Unhealthy'].{name:displayName, severity:metadata.severity, status:status.code}" \ -o table
# Export all unhealthy assessments to JSONaz security assessment list \ --query "[?status.code=='Unhealthy']" \ -o json > assessments.json
# Check a specific recommendation by nameaz security assessment list \ --query "[?displayName=='Storage accounts should restrict network access using VNet rules']" \ -o table
# Count unhealthy findingsaz security assessment list \ --query "[?status.code=='Unhealthy'] | length(@)"Assessment Metadata
# Export full recommendation catalog (severity, description, remediation)az security assessment-metadata list -o json > metadata.json
# Look up a specific recommendation's detailsaz security assessment-metadata list \ --query "[?displayName=='Storage accounts should restrict network access using VNet rules'].{name:displayName, severity:severity, description:description}" \ -o tableSecurity Score
# Get current secure scoreaz security secure-score list -o table
# Get score controls (which categories contribute to score)az security secure-score-controls list -o table
# Get score controls with detail on recommendationsaz security secure-score-control-definitions list -o tableDefender Plans
# List active Defender plans (which resource types have protection enabled)az security pricing list -o table
# Check a specific planaz security pricing show --name VirtualMachines -o tableAlerts
# List active security alertsaz security alert list \ --query "[?status=='Active'].{name:alertDisplayName, severity:severity, time:timeGeneratedUtc}" \ -o table
# Dismiss an alert (after investigation)az security alert update --name <ALERT_NAME> --status Dismissed --location <LOCATION>Useful Filters
# Unhealthy findings for a specific resource groupaz security assessment list \ --query "[?status.code=='Unhealthy' && resourceGroup=='eshars-prod-rg']" \ -o table
# Unhealthy findings by severityaz security assessment list \ --query "[?status.code=='Unhealthy' && metadata.severity=='High']" \ -o table
# Resources affected by a specific recommendationaz security assessment list \ --query "[?displayName=='<RECOMMENDATION>' && status.code=='Unhealthy'].resourceDetails.Id" \ -o tsvParse Script
# Full intake pipeline (pull, parse, output)az security assessment-metadata list -o json > metadata.jsonaz security assessment list --query "[?status.code=='Unhealthy']" -o json > assessments.jsonpython security/parse_defender.py metadata.json assessments.json > findings.jsonAppendix: Key File Locations
| File | Purpose |
|---|---|
security/defender-process.md | Triage workflow, categorization criteria, approval process |
security/defender-register.md | Master register mapping Defender findings to Jira tickets |
security/defender-guide.md | This file — comprehensive how-to guide |
security/parse_defender.py | Script to join metadata + assessments into structured findings |
security/release-notes/TEMPLATE.md | Template for documenting resolved findings |
security/release-notes/ | Batch release notes for resolved findings |
security/findings-YYYY-MM-DD.json | Archived raw findings output from parse script |
Appendix: Cadence Summary
| Frequency | Activity | Owner |
|---|---|---|
| Immediately | High-severity + prod findings go into current sprint | Team lead |
| Monthly | Run intake workflow (Steps 1-8 above), create new tickets, update register | Assigned engineer |
| Sprint planning | Include Defender items per wave priority | Team |
| Sprint close | Write release notes for resolved findings | Assigned engineer |
| Quarterly | Full review of all open findings with team lead; re-evaluate risk labels | Team lead + team |
| Quarterly | Review “Won’t Do” items — are compensating controls still valid? | Team lead |