Why an agent image is Tier 0

Picture a Tier 0 deployment pipeline. A job starts, the platform hands it a service connection token, and the job runs az, terraform or kubectl against the identity control plane. Every one of those steps executes inside a container image or on an agent image. Whoever controls that image controls what the job does with the token.

Microsoft calls this the clean source principle: every security dependency must be as trustworthy as the object it secures. An image that runs Tier 0 jobs is such a dependency. That settles the most common question in one line: agent and tooling images are built inside Tier 0 from pinned, verified inputs, and never promoted from Tier 2. No promotion gate can make a Tier 2 build system trustworthy after the fact.

Application images are different. They run on Tier 0 infrastructure but do not hold pipeline tokens, so Tier 2 can build them as long as Tier 0 decides what gets in. The rest of this article follows those two routes and the controls on each.

Four eyes and a PAW do different jobs

Teams with a strict two-person rule sometimes ask whether they still need PAWs. They do, because the two controls answer different questions. Four eyes asks: does a second person agree with this change? The PAW asks: is this session really that person, on a clean device?

Four eyes only guards the pipeline path. A Tier 0 administrator’s activated roles also work directly in the portal, the CLI and Graph, where no pipeline approval applies. And if an approver’s session is stolen from a daily-driver laptop, the attacker becomes the second pair of eyes.

SituationFour eyes in Azure DevOpsPAWControls
One engineer merges a malicious change aloneStops itDoes not helpC2 C3
An approver’s browser session is stolen on a daily-driver laptopThe attacker approves as that personTier 0 accounts cannot sign in outside a PAWC1
An admin uses an activated role directly in the portal to change the registryNo pipeline involvedProtects the session, not the intent; add PIM approval and alertsC12 C13
Malware on the engineer’s device edits the PR after approvalOnly if votes reset on new pushes and the last pusher cannot approveClean deviceC1 C2
A pipeline job abuses an agent VM’s managed identityRuns after approvalNot a human sessionC3 C4 C7
An Azure DevOps admin turns the checks offAdmins edit the checksOnly if admins are Tier 0 accounts on PAWsC12 C13

There is a corollary for the PAW itself: do not build images or install packages on it. Package installs run third-party code at install time. The Shai-Hulud npm worm ran from postinstall and preinstall scripts and harvested cloud credentials, including the Azure CLI profile. On a PAW, that code would run next to activated Tier 0 tokens. The PAW authors code and approves runs; builds move to fresh, network-isolated Tier 0 agents, and everyday development stays on the normal workstation with a non-privileged account.

Decide the route first

Before choosing tools, classify the image. The test is simple: is it referenced by a container step on a Tier 0 job, is it the image of a Tier 0 agent pool, or do Tier 0 pipeline steps call tools inside it? Then it runs Tier 0 jobs. If it only runs on the Tier 0 cluster as an application, it is a workload image, unless that application can obtain Tier 0 credentials.

Decision flow

Does this image run Tier 0 pipeline jobs?

Treat it as Tier 0 control plane

Build it inside Tier 0 from pinned, verified inputs and never promote it from Tier 2.

Recommended

Managed DevOps Pools

Stateless pools give every job a fresh VM in your Tier 0 virtual network. No agent image to build, sign or patch, no registration secret, no Docker-in-Docker.

C4C5C6C7

Only with four fixes

Containerized agents

The Docker sample downloads the agent at start, keeps its token on disk and mounts the node’s Docker socket. Fix all four first.

A1A2A3A4

Agent images are built in Tier 0. Application images come from Tier 2 through a gate. Both routes keep the two-person rule and PAW-only sign-in.

When in doubt, treat it as Tier 0. Rebuilding inside Tier 0 is always safe; promoting is safe only for workloads.

The target state, step by step

Here is the whole path from a pull request on the PAW to a signed image running on Tier 0 AKS. Press Play flow to walk the nine steps, select any box to see what it does, or select a dark tag (C1 to C14) to see the control behind it and how to verify it.

Nine steps from a PR on the PAW to a signed image running on Tier 0 AKS.

InternetPAWC1Azure DevOpsC12Tier 0 subscriptionManaged DevOps PoolsC4Acquire subnetBuild subnetRegistry endpointsRuntime subnetTier 2pinned onlyapproves runfresh VMsigned pushNo agent imagesMCR imagesVendor toolsEngineerApproverTier 0 repoC2C6RequiredtemplateRun checksC3WIFconnectionC7Azure FirewallC14Sentinel logsC13Acquire jobC5C6Build jobPublish jobC7C9Promote jobC11Catalog ACRKey VaultTier 0 ACRC8Staging ACRAgent pods(diagram 2)Tier 0 AKSC10Tier 2 pipelineStaging ACR123456789

Select any box or a dark control tag to see what it does, which controls apply and how to verify them.

Controls

Three ideas carry most of the weight. First, jobs run only on stateless Managed DevOps Pools: each job gets a fresh VM that is discarded afterwards, and no managed identity is attached to any pool, so a job cannot pick up credentials from the VM. Second, internet egress and write credentials never share a job: acquire can reach pinned vendor endpoints but only writes the catalog, build has neither internet nor write access, and publish holds the push token but no internet. Third, the only identity that can push is reached through workload identity federation, so there is no secret to steal and only runs that pass the checks can use it.

Promoting application images from Tier 2

Only application images take this route, and the direction matters: Tier 0 pulls, Tier 2 never pushes.

  1. Tier 2 builds the application image, signs it with the Tier 2 Notation key, attaches the SBOM and provenance, and pushes it to a staging registry with public access off.
  2. The staging registry gets a second private endpoint in the Tier 0 registry endpoint subnet. Tier 2 and Tier 0 are never peered. The Tier 0 promote identity has pull-only rights on staging.
  3. The promote job verifies the Tier 2 signature and the provenance (repository, branch, pipeline) against a Notation trust policy.
  4. It scans the image against an internally mirrored vulnerability database.
  5. It copies the image by digest into the Tier 0 registry, including referrers such as the signature and SBOM.
  6. It signs the same digest with the Tier 0 key held in Key Vault.
  7. Tier 0 AKS admits only digests signed by the Tier 0 key.

Why not az acr import? Import needs the Container Registry Data Importer and Data Reader role on both registries, and every network-restricted registry involved must allow trusted services to bypass its network rules. That reopens exactly the paths the design closes. With export disabled on the Tier 0 registry, import out of it is blocked, which is intended.

One DNS detail is easy to miss. If privatelink.azurecr.io is a shared, central zone, the staging registry’s second endpoint registers a record that collides with its Tier 2 record. Give Tier 0 its own zone and resolver path.

A registry no human can push to

Only two managed identities hold data-plane roles: AcrPush for the orchestration identity through workload identity federation, and AcrPull for the AKS kubelet identity. No human has a role assignment and the admin user is off. On the network side, public access is off and the registry is reachable only through its private endpoint.

SettingValueWhy
SKUPremiumRequired for private endpoints, export policy and network rules.
Public network accessDisabledClients outside the virtual network cannot reach the registry endpoints.
Admin userDisabledNo shared username and password; every push and pull is an Entra ID identity.
Export policyDisabledBlocks import and transfer pipelines out of the registry. Requires public access to be disabled.
Trusted-services bypassNoneAzure services use the private endpoint like everyone else.
Network rule setDefault deny, no IP rulesDefence in depth if someone re-enables public access.
Anonymous pullDisabledNo unauthenticated reads.
DNSPrivate DNS zone groupWrites both records the registry needs: the login server and the regional data endpoint.
Endpoint subnet NSG443 from runtime and build subnets onlyNeeds private endpoint network policies enabled on the subnet.
Data-plane rolesAcrPush (orchestration, via WIF), AcrPull (AKS)ABAC-enabled registries ignore AcrPull and AcrPush; do not switch modes without replacing both.
IntegrityDeploy by digest, admit by signatureTag locks only stop accidents. Docker Content Trust is being removed from ACR; use Notation.
Delete lockCanNotDeleteBlocks control-plane deletion; image operations are unaffected.

Governance closes the last gap. Anyone who can grant roles at the registry’s scope, such as Owner or User Access Administrator, can grant themselves AcrPush. Those roles are therefore Tier 0 too: held through PIM, used from PAWs, and every role assignment change on the registry raises an alert. Built-in Azure Policy definitions can deny the common regressions at the Tier 0 management group, such as re-enabling export, the local admin account or anonymous pull.

Microsoft’s documentation differs on whether Defender for Cloud needs the trusted-services bypass to scan a private registry. Keep the bypass off and confirm that scan results still appear before relaxing anything.

If agents stay in containers

Managed DevOps Pools remove the agent image, the registration secret and Docker-in-Docker in one step, which is why they are the recommended route. Some teams still run Azure DevOps agents as pods on AKS. Microsoft’s own Docker sample is a reasonable starting point for a lab, but it downloads the agent at start, keeps its registration token on disk and, on AKS, mounts the node’s Docker socket. All four of the fixes below are needed before it runs Tier 0 jobs.

Tier 0 AKSAgent pod · one jobAzure DevOpsManaged DevOps Poolone jobbuildspod exitsKEDAScaledJobRegister(init)SharedvolumeAgentcontainerCleanupcontrollerEgresslockdownTier 0 poolImage builds

Select any box or a dark control tag to see what it does, which controls apply and how to verify them.

Controls
A1Bake a pinned agent into the imageContainerized agents · documented capability + design recommendation

Microsoft’s sample start.sh downloads the latest agent package every time the container starts, so the signed image is not the code that runs. Download and SHA256-verify a pinned agent version at image build. Automatic agent updates are on by default, so turn them off for the pool and rebuild the image when a task needs a newer agent.

How to verify

The image contains the agent binaries. start.sh makes no download call. Pool settings show automatic updates off.

A2Keep the registration credential out of the job containerContainerized agents · documented capability + design recommendation

The sample writes the token to a file and keeps it for cleanup at shutdown, so any job on that agent can read it and register a rogue agent. Register in an init container and deregister from a separate controller. With workload identity, list the agent container in azure.workload.identity/skip-containers so only the init container gets the token.

How to verify

kubectl exec into the agent container finds no token file or token environment variable.

A3No Docker socket, no privileged podsContainerized agents · documented capability

Microsoft’s AKS example mounts the node’s Docker socket, and Microsoft warns that this lets code in the container run as root on the host. Docker-based tasks also do not work on AKS 1.19 or later. Build images on the Managed DevOps Pool instead.

How to verify

Pod Security Admission runs in restricted mode on the agent namespace. No pod has hostPath volumes.

A4One job per pod, short-lived credentialsContainerized agents · documented capability + design recommendation

The sample defaults to a PAT. A service principal’s client secret is used only during registration (agent 3.227.1 and later). Run with --once so each pod serves one job, and deregister as soon as the pod exits.

How to verify

A KEDA ScaledJob creates one pod per queued job. Agent registrations disappear within minutes of pod exit.

For daemonless builds, run BuildKit on the Managed DevOps Pool VM rather than inside agent pods. Kaniko’s original repository was archived in June 2025, so do not plan new work around it.

The control set

Fourteen controls make up the target state, plus the four agent fixes above if containerized agents stay. Each is tagged C for a documented Microsoft capability or L for a design recommendation inferred from documented behaviour, and each carries a check you can run to prove it is in place.

C1Tier 0 accounts sign in only from PAWsPeople and devices · design recommendation

Conditional Access limits Tier 0 accounts to PAW sessions. The PAW authors code and approves runs; it never builds images or installs packages.

How to verify

A Conditional Access policy targets the Tier 0 accounts and requires the PAW device filter or a compliant PAW. Sign-in logs show no Tier 0 sign-ins from other devices.

C2Branch policy on main enforces a second reviewerPipeline · documented capability

Require a minimum number of reviewers, turn off “Allow requestors to approve their own changes”, turn on “Prohibit the most recent pusher from approving their own changes”, and reset all approval votes when new changes are pushed.

How to verify

The main branch policies show those four settings. A test PR cannot be completed by its author.

C3Checks on Tier 0 pools and the service connectionPipeline · documented capability

Branch control allows only refs/heads/main, a required template check forces the approved pipeline shape, and an approval check stops approvers from approving their own runs. Pipeline permissions are granted per pipeline, not to all pipelines.

How to verify

Approvals and checks on each Tier 0 agent pool, the WIF service connection and the Tier 0 environment list all three checks. A run from a feature branch is blocked.

C4Managed DevOps Pools: stateless, VNet-injected, no pool identityBuild · documented capability + design recommendation

“Fresh agent every time” gives each job a new VM that is discarded afterwards. Each pool has its own delegated subnet. No managed identity is attached to any pool, so a job cannot pick up credentials through IMDS.

How to verify

Pool agent state is Stateless. Pool identity is None. Each pool’s subnet is delegated to Microsoft.DevOpsInfrastructure/pools and used by no other pool.

C5Internet egress and write credentials never share a jobBuild · design recommendation

Acquire has internet egress and may write only the catalog. Build has no internet and no write credentials. Publish has no internet and holds the WIF token for the Tier 0 registry. Each job runs on its own fresh VM.

How to verify

The required template defines the three jobs and their pools. Only the publish and promote jobs reference the WIF service connection.

C6Every input pinned in reviewed source and verifiedBuild · design recommendation

Base images by digest, downloads by SHA256 (plus GPG or vendor signature where published), the agent version and its SHA256. Inputs are verified before they enter the catalog, and builds use catalog inputs only.

How to verify

The repo holds a lock file of digests and hashes. A changed hash fails the acquire job. Build logs show only catalog registry pulls.

C7Push only through workload identity federationIdentity · documented capability + design recommendation

The orchestration identity holds AcrPush on the Tier 0 registry only and is reachable only through a WIF service connection, with no secret. Only the publish and promote jobs use that connection.

How to verify

The identity has exactly one federated credential (the service connection) and one role assignment (AcrPush on the registry). It is not attached to any VM.

C8Tier 0 registry locked to its private endpointRegistry · documented capability + design recommendation

Premium SKU, public network access off, admin user off, export policy disabled, private endpoint only, DNS through a zone group, an NSG allowing 443 only from the runtime and build subnets. Trusted-services bypass off.

How to verify

az acr show reports publicNetworkAccess Disabled, adminUserEnabled false, exportPolicy disabled and networkRuleBypassOptions None. Both A records exist in privatelink.azurecr.io.

C9Sign with a Tier 0 key; deploy by digestIntegrity · documented capability + design recommendation

The publish job signs the pushed digest with a Tier 0 key held in Key Vault (Notation Key Vault plugin) and attaches the SBOM. Deployments reference image@sha256 digests, never tags.

How to verify

notation ls lists a Tier 0 signature for each deployed digest. Kubernetes manifests contain digests.

C10Admission allows only Tier 0-signed digestsIntegrity · documented capability + design recommendation

Self-managed Ratify with Gatekeeper on Tier 0 AKS verifies the Tier 0 signature before admission. The AKS Image Integrity add-on is still in preview.

How to verify

Deploying an unsigned or Tier 2-only-signed image to the Tier 0 cluster is denied.

C11Promotion gate for Tier 2 application imagesIntegrity · documented capability + design recommendation

Verify the Tier 2 signature and provenance, scan, copy by digest including referrers, re-sign with the Tier 0 key. Do not use az acr import: it needs the Importer role on both registries and the trusted-services bypass on every network-restricted registry involved.

How to verify

Promote job logs show verify, scan, copy-by-digest and sign steps. No Tier 0 identity holds Container Registry Data Importer and Data Reader.

C12Azure DevOps admins are Tier 0 accountsPeople and devices · design recommendation

Organization, project, agent pool and service connection administrators can edit every check above, so they are dedicated Tier 0 accounts restricted to PAWs.

How to verify

Project Collection Administrators and the relevant admin groups contain only dedicated Tier 0 accounts. Membership changes raise alerts.

C13Registry and pipeline audit logs reach SentinelOperate · documented capability

ContainerRegistryRepositoryEvents and ContainerRegistryLoginEvents go to Log Analytics, alongside the Azure DevOps audit stream, firewall logs and pool logs.

How to verify

Both tables return rows in the workspace. Analytics rules for unexpected pushes, deletes and role assignment changes are enabled.

C14Per-subnet egress allowlistsOperate · documented capability + design recommendation

Managed DevOps Pools documents its required egress, including *.queue.core.windows.net and Ubuntu package endpoints. Allow only the documented Managed DevOps Pools and Azure DevOps endpoints, plus pinned vendor FQDNs for acquire. Never allow github.com or *.blob.core.windows.net broadly.

How to verify

Firewall rule collections exist per subnet. Denied-traffic logs from the build subnet stay at zero during normal runs.

Sources and further reading

Claims above are based on Microsoft’s published documentation, checked on October 13, 2026. Review the linked guidance for current product behaviour before you rely on it.