A mid-size Azure tenant with a few hundred subscriptions routinely surfaces 5,000 to 15,000 open recommendations in Microsoft Defender for Cloud. Almost none of that volume gets triaged one recommendation at a time. The teams that keep the backlog under control do three things differently: they stop optimizing for the secure score number, they route findings to the people who actually own the resource, and they measure whether things get fixed rather than whether a percentage moved. The rest is API plumbing.
How secure score is calculated and why chasing it misranks real risk
Secure score is a weighted average, not a risk score. Each security control (for example, “Enable MFA” or “Encrypt data in transit”) has a maximum point value assigned by Microsoft, and a control’s current contribution is calculated as:
max points x (healthy resources / total applicable resources)
Take a control worth 8 points with 40 applicable resources, 10 of which are currently healthy. The control contributes 8 x (10/40) = 2 points today. Fixing 8 more low-effort resources in that same control raises the contribution toward the full 8 points. Fixing one severe, internet-facing finding on a resource that already counts as partially covered by a different control can move the score by a fraction of a point, or not at all.
This is why teams that manage toward the score end up working the wrong queue. A control covering hundreds of low-privilege storage accounts with a one-line policy fix is scoring gold; a single unpatched VM carrying a two-year-old actively exploited CVE and an open management port is scoring noise. Score is a portfolio-level metric useful for reporting to leadership or benchmarking against the Microsoft benchmark baseline. It is not built to answer “what do I fix first.” For that, pull recommendation severity together with exploitability data and, where you have Defender CSPM enabled, attack path analysis, which weighs a finding by whether it sits on a reachable path to a sensitive resource rather than by how many resources share its control. Some platforms take that logic a step further and score findings purely on exploitability and exposure, independent of any compliance percentage; that is the approach an autonomous vulnerability management platform such as SITEY applies to its own risk ranking, deliberately decoupled from a coverage-based score.
Grouping thousands of recommendations by resource owner instead of severity
Sorting the backlog by severity produces a list nobody can act on, because severity says nothing about who has the access or context to fix the underlying resource. The more useful first cut is ownership. If your tagging policy is enforced (an owner or cost-center tag required at resource creation via Azure Policy), you can pull ownership directly with Resource Graph:
securityresources | where type == “microsoft.security/assessments” | where properties.status.code == “Unhealthy” | join kind=leftouter (resourcecontainers | where type==”microsoft.resources/subscriptions/resourcegroups” | project resourceGroup=name, owner=tostring(tags[“owner”])) on resourceGroup | summarize count() by owner, severity=tostring(properties.metadata.severity)
That single query turns one flat backlog into N owner-scoped backlogs, each small enough for a team to actually work through. Defender for Cloud’s governance rules feature operationalizes this: you define a rule matching recommendation severity, resource type, and an owner source (a specific tag key, or a fallback owner), attach a grace period in days before the item is flagged overdue, and set a notification email. Recommendations that miss their grace period escalate automatically instead of sitting invisible in a shared queue. Route grace periods by severity, not uniformly. A reasonable starting point is 7 days for high severity, 30 for medium, 90 for low, adjusted once you have a few months of actual closure data to check against. This ownership-first routing is the same problem that team workflow tooling solves for findings coming from other scanners: a finding is only actionable once it has a person and a clock attached to it.
Exemptions done right: scope, expiry, and an audit trail that survives review
An exemption is not a way to make a recommendation disappear. It is a documented, time-boxed decision that a specific resource will not be remediated, with a reason an auditor can read later without asking anyone. Defender for Cloud implements exemptions through Azure Policy exemption resources, and three fields separate a defensible exemption from a liability:
| Field | Purpose | Example |
|---|---|---|
| exemptionCategory | States whether the risk is accepted (Waiver) or addressed by a compensating control (Mitigated) | Mitigated, because a WAF blocks the exposed path |
| expiresOn | Forces a review date; without it the exemption is permanent by default | 2026-12-31 |
| scope | Limits blast radius to the specific resource or resource group, never the whole subscription | /subscriptions/xxx/resourceGroups/legacy-app/providers/… |
A working CLI example:
az policy exemption create –name “legacy-app-tls-waiver” –policy-assignment “/providers/Microsoft.Authorization/policyAssignments/ASC-Default” –exemption-category Mitigated –expiration-date 2026-12-31 –scope “/subscriptions/xxx/resourceGroups/legacy-app” –display-name “TLS 1.0 required by vendor, WAF terminates TLS 1.2 upstream” –metadata ticket=SEC-4821
Put the ticket number in metadata or the description field, not in a separate spreadsheet that nobody updates. Then run a standing Resource Graph query against Microsoft.Authorization/policyExemptions filtered on expiresOn in the next 30 days, on a monthly schedule, and route the output through the same approval gate that signed off on the original exemption. If nobody can produce the ticket, the approver, and the expiry date for a given exemption during a review, treat it as an open finding again rather than grandfathering it in.
Exporting recommendations to a ticketing system with stable identifiers
The most common cause of duplicate tickets is using resourceId or the recommendation’s display name as the external key. Resource IDs change when a resource is recreated (a common outcome of infrastructure-as-code redeploys), and display names are not guaranteed unique across assessment types. The stable identifier is the combination of the assessment key, a GUID defined in Microsoft.Security/assessmentMetadata that is fixed per recommendation type, together with the resource ID and subscription ID at the time of export. Use that triple as the external ID field in Jira or ServiceNow and perform an upsert rather than a create on every sync run.
For the export mechanism itself, prefer scheduled polling of the assessments REST API (GET /subscriptions/{id}/providers/Microsoft.Security/assessments?$filter=status/code eq ‘Unhealthy’, paginated via nextLink) over continuous export to an Event Hub for this use case. Continuous export streams every state change, including transient flapping, and will flood a ticketing queue unless you build your own deduplication layer in front of it. A 6-hour polling cadence with an upsert keyed on the assessment triple gets you fresh enough data without that extra layer. Platforms that ingest findings from many scanners at once run into the identical identity problem: an autonomous vulnerability management platform such as SITEY normalizes each finding to a fingerprint built from asset, rule, and location before it reaches a ticket queue, so a resource rename or a rescan does not spawn a second ticket for the same underlying issue. Once tickets exist with a stable key, the actual work items belong in task management rather than in the security team’s own tracker, so the resource owner sees the fix alongside their other work.
Handling hybrid and multi-cloud resources connected into Defender
Non-Azure resources only get assessed once they are actually onboarded through a connector, and the failure mode is silent: a resource shows up in inventory but sits at “Not applicable” for most recommendations because the underlying agent was never provisioned. For Arc-enabled servers (on-prem or other-cloud VMs), verify agent-based auto-provisioning is on with az security auto-provisioning-setting list, and confirm the Arc agent is actually reporting with az connectedmachine list –query “[].{name:name,status:status}” rather than assuming the connector wizard finished the job. For AWS, the connector deploys a CloudFormation stack that grants a cross-account role; when Microsoft updates the required IAM permissions for new features, that stack needs to be re-applied, and skipping the update quietly drops coverage for the new checks rather than throwing an error anywhere visible. GCP connectors follow the same pattern through a service account and enabled APIs at the project level.
Ownership routing also breaks quietly across clouds if tagging conventions differ. AWS tag keys are case-sensitive and commonly use different casing than an Azure tagging policy assumes (Owner versus owner), so a governance rule built against one convention will simply fail to match resources tagged under the other, and those findings fall back to an unassigned bucket without any error. Align tag key casing and allowed values across clouds before connecting AWS or GCP accounts, not after the first quarterly review turns up an unassigned pile with real owners behind it.
Measuring closure instead of score movement
Track three numbers on a cadence independent of the secure score dashboard: mean time to remediate per severity band, the 30-day rolling resolve rate, and the count of items past their governance-rule grace period, broken out by owner. A rising secure score alongside a flat or worsening MTTR is a specific and detectable failure pattern, it means the team is closing easy multi-resource controls while high-severity individual findings age in place. Build the query against Resource Graph on a weekly schedule and keep the output in a dashboard the security team owns, separate from whatever executive view tracks the score percentage.
Closure itself deserves the same skepticism you’d apply to a patch job’s reported exit code. A recommendation moving to “Healthy” in the Defender for Cloud UI generally reflects the next assessment cycle re-evaluating the resource, but for anything remediated through a manual change or a script, don’t consider the finding closed until a fresh scan confirms it. The same principle holds outside Defender for Cloud: an autonomous vulnerability management platform such as SITEY re-tests the specific finding after a remediation runs, rather than closing the ticket because the patch command returned success, since a config drift or a rollback can leave the underlying condition exactly where it started. Once closure is tracked this way, the resulting evidence, timestamps, exemption records, and re-test results, maps cleanly onto ISO 27001 or PCI DSS control evidence instead of requiring a separate screenshot exercise at audit time through retest and closure tracking built for that purpose.
About SITEY
SITEY is an autonomous vulnerability management platform. It discovers, validates, prioritizes, remediates and re-tests vulnerabilities through an eight-phase automated pipeline, unifying output from 17 integrated scanners. SITEY is self-hosted: it runs in your own infrastructure and your findings are stored there. Outbound connections are limited to licence activation and the optional services you enable, such as an AI provider, CVE enrichment and patch catalogues. Pricing is 599 USD per month or 5,999 USD for a perpetual lifetime license. See pricing or how the platform works.