Cloud services

Blog post

Why cache invalidation became its own service

Letting every site clear its own hostname sounds narrow, but Azure's permission model cannot express that boundary on a shared Front Door endpoint. The enforceable boundary therefore lives in a trusted workflow instead.

A guarded platform workflow checks repository and hostname tokens before clearing one site's cards from a shared global edge

The gap in the permission model

Azure grants Microsoft.Cdn/profiles/afdendpoints/Purge on the endpoint or profile. The --domains value is merely a field inside the request. That means any identity allowed to purge our shared endpoint could name a neighboring site's hostname. Storage's path conditions never see that Front Door request.

We do not hide this limitation or hope tenants behave. Tenant uploaders receive no Front Door permission. They call a reusable workflow owned by the platform, and Entra will issue the purge token only while that trusted file is executing.

The route-backed allowlist

  1. Call trusted code The tenant deploy invokes our purge workflow at the same library tag.
  2. Mint a narrow token The stack application trusts the organization id, immutable caller subject, and exact reusable-workflow reference.
  3. Look up one leaf PROJECT_ID selects Front Door route inventory owned by the platform; it does not authorize anything by itself.
  4. Prove membership Each purge invocation accepts exactly one FQDN. The caller repository name and that hostname must be attached custom domains whose routes have the exact origin path /<PROJECT_ID>/<fqdn>/public. Batched hostnames or any mismatch stop before a purge command runs.

Storage folder names are deliberately not evidence: the uploader may create names inside its own $web/<PROJECT_ID>/* boundary. A leaked neighboring project id is still useless because the attacker’s repository name is not provisioned on that leaf’s Front Door routes. Once the caller is proven, production and provisioned environments are peers.

Hosts are authorized; queued paths are payload

Before any public-origin mutation, deploy reads every existing queue, unions pending paths with this deploy’s durable paths and dropped unhashed paths, and coalesces them into one state/to-purge-YYYYMMDD.txt. The original unfinished date remains in the filename. Failure to write and byte-verify that queue stops the deploy before public files change.

The queue body contains only FQDN-relative Front Door paths such as /, /index.html, and /blog/*. It never contains a host or storage prefix. If the union exceeds Front Door’s 100-path maximum, the workflow rolls up the deepest, smallest overlapping groups one directory at a time, keeping a separate /dir/ key where needed. /* is the deterministic last resort. It never truncates to the first 100: exact paths or covering wildcards represent the entire set in one purge invocation, so no remainder waits for a second pass.

Ordinary deploys keep fingerprints out of the queue and avoid /* whenever a narrower representation fits. Redirect activation and the tenant’s manual Purge all are explicit full-tree operations. Any purge or verification failure leaves the queue in place; successful purge plus public verification is the only path that clears it.

Verification sweeps all remaining durable URLs in parallel. A current URL confirms only on 200 plus matching staged and fetched SHA-1; a dropped durable URL confirms on 404. The explicit index.html name and its trailing-slash alias are separate cache keys and both travel through purge and verification. One matching endpoint response is enough to complete a check, so success is accompanied by the reminder that the full global rollout may still be proceeding.

Fingerprinted files are not pruned, purged, or polled during deploy. Their old origin copies are expected to remain until the separately planned storage lifecycle cleanup.

What each party proves

GitHub supplies the caller repository and signs the OIDC token. Entra proves that our trusted workflow is executing. Azure proves that the stack application may purge the endpoint. Our allowlist accepts one FQDN and proves that it and the caller share provisioned route inventory for the project. No one layer answers all four questions, so the service composes them explicitly.

When an operator needs the whole host

The tenant repository exposes a manual GitHub Purge all dispatch. The operator types one provisioned FQDN; the workflow authorizes it, durably queues /*, performs the platform purge, and verifies every current durable origin object through that hostname before clearing the queue.

For the proof-style version and federated credential details, see the operations deep dive. Continue with how dropped pages and folders complete that transaction or why upload and purge use different applications.