What it is
An Azure Policy exemption whose expiresOn is a few days away and has no owner watching it. Exemptions are the sanctioned way to carve a resource, resource group, subscription, or management group out of a policy assignment, and the expiry date is what makes the carve-out temporary. When the date arrives, one of two things happens, and both are bad: the exempted scope silently falls back under a deny or audit effect and deployments or compliance break overnight, or someone reflexively pushes the date out to stop the bleeding and the "temporary" waiver turns into a permanent hole. Expired exemptions also stay behind as objects, so audits keep finding carve-outs that no longer do anything.
Why it happens
Azure treats expiresOn as a static property, not an event. There is no built-in notification when an exemption is about to lapse, no event when it does, and no Advisor or Defender recommendation about it. The person who approved "30 days for the migration" is never told the 30 days are up, and the exemption object itself is a small resource at an obscure scope that nobody browses.
Two edges of the same footgun make it worse. First, expiresOn is optional: many exemptions are created without one, especially from the portal under time pressure, which turns a Waiver (Azure's own definition: "the non-compliance state of the resource is temporarily accepted") into an indefinite one. Second, when the date is set and does pass, Microsoft documents that the exemption is not deleted; the object is preserved for record-keeping and simply stops being honored. So the Policy blade and az policy exemption list keep showing exemptions that are in force next to ones that expired a year ago, with nothing but a date column to tell them apart.
The security angle is the sharpest. A Waiver on a deny policy (no public storage endpoints, no VMs without disk encryption, required private endpoints on Key Vault) is a deliberate gap in a guardrail. A Mitigated exemption claims the intent is met another way; a Waiver admits it is not. Either way the reviewer accepted the gap for a bounded window, and the window closing with no review means the next decision about that gap is made by whoever is on call when a pipeline fails.
What it costs / blast radius
The direct cost is nothing; exemptions are free and so is letting them lapse. The blast radius is operational and governance harm, and it depends on which way the lapse breaks. (Azure behavior; authored assessment.)
If the exemption expires unnoticed, a deny policy re-arms against a scope that still depends on the carve-out: redeploys fail with a policy violation, the next deployment triggers DeployIfNotExists effects, and compliance scores drop across every dashboard at once. The scope can be a whole management group, which means every subscription under it inherits the surprise.
If the exemption is extended unreviewed, the guardrail stays open for another cycle with the original justification long stale. That is how a three-month waiver for a vendor appliance becomes a permanent exception that survives the appliance itself, and how an auditor ends up asking why a "temporary" exemption on an encryption policy is on its fourth year.
Either way the expired-but-present objects accumulate, and an exemption inventory that mixes live, lapsed, and permanent entries is one that no reviewer trusts.
See it
// Exemptions live in the policyresources table, not resources.
// properties.expiresOn is optional and null for permanent exemptions.
policyresources
| where type == 'microsoft.authorization/policyexemptions'
| extend expiresOn = todatetime(properties.expiresOn)
| extend status = case(
isnull(expiresOn), 'no expiry (permanent)',
expiresOn <= now(), 'EXPIRED, object still present',
expiresOn <= now(30d), 'expires within 30 days',
'later')
| where status != 'later'
| project name, displayName = tostring(properties.displayName),
category = tostring(properties.exemptionCategory),
status, expiresOn,
scope = substring(id, 0, indexof(tolower(id), '/providers/microsoft.authorization/policyexemptions')),
assignment = tostring(properties.policyAssignmentId)
| order by expiresOn asc# Option A: the carve-out is still needed. Extend it with a date someone
# can defend, and write the review into the description so the next
# reviewer sees why, not just when.
az policy exemption update --name waiver-legacy-storage-tls \
--scope /subscriptions/<subscriptionId>/resourceGroups/rg-legacy \
--expires-on 2027-01-31T00:00:00Z \
--description "Reviewed 2026-10-28: TLS 1.0 clients retire Jan 2027 (OPS-4412)"
# Option B: the need has passed (or it already expired). Remove the object
# so the policy applies again and the inventory stops lying.
az policy exemption delete --name waiver-legacy-storage-tls \
--scope /subscriptions/<subscriptionId>/resourceGroups/rg-legacyHow StratoLens helps
StratoLens tracks every policy exemption across every subscription and management group in your tenant, buckets them by time to expiry (expired, 30, 60, 90 days, no expiry), and sends a Policy Exemption Expiration alert when one enters your warning window, once per exemption and deadline, re-armed if the date is extended. The reviewer who approved the carve-out hears about it before the guardrail re-arms, not after the pipeline fails.