Safeguard
Supply Chain Security•

kaniko CVE-2026-105769: Azure Token Leak to Lookalike ACR Hosts

kaniko before 1.25.20 can send an Azure access token to any host that contains .azurecr.io. Upgrade, keep Azure identity out of builds, check your logs.

Safeguard Security Team
37 min read

CVE-2026-105769 is a critical flaw in kaniko, the container image builder, before version 1.25.20. A Dockerfile that names a lookalike registry such as x.azurecr.io.attacker.example can make kaniko send the build's Microsoft Entra ID access token for Azure Resource Manager to that host. Upgrade to 1.25.20, keep Azure identities away from untrusted builds, and review logs.

The root cause is not in kaniko's own code. It sits in a small, unmaintained Go module, github.com/chrismellard/docker-credential-acr-env, that kaniko links in to log in to Azure Container Registry (ACR). That module decides whether a hostname belongs to ACR with a regular expression that is not anchored, so any hostname that merely contains .azurecr.io passes. The module has no fixed release, and its last commit is from March 2023. kaniko fixed it by carrying its own patched copy. Anyone else who uses the helper has to do the same, switch to a maintained fork, or remove it.

This is not breaking news. The helper flaw has been public since 13 August 2026, the kaniko fix shipped on 27 October, and the CVE and repository advisory followed on 2 November, with GitHub's reviewed advisory on 5 November. It is a case of a critical CI credential leak in a dependency nobody maintains, which you have to fix yourself. This post lists the exact versions and identifiers, and shows how to find kaniko and the helper, rebuild, cut the Azure identity out of build pods, and check your logs.

The short version

  • What it is: kaniko asks an ACR credential helper for credentials before it pulls a base image. The helper treats any host that contains .azurecr.io, .azurecr.cn, .azurecr.de or .azurecr.us as ACR, gets an Entra ID access token for Azure Resource Manager, and posts it to https://<host>/oauth2/exchange on that host. A lookalike host owned by an attacker receives a live token.
  • What triggers it: a FROM line or a COPY --from image reference that points at a lookalike host. According to the advisory, the build needs no RUN instruction, never has to push, and the attacker's registry does not need to ask for authentication.
  • Identifiers: CVE-2026-105769 and GHSA-5q7h-w9jf-cx97 for kaniko. The Go vulnerability database tracks the underlying helper flaw as GO-2026-6225.
  • Severity: critical, CVSS 3.1 base score 9.3, set by Chainguard as the CVE numbering authority.
  • Affected: github.com/chainguard-dev/kaniko below 1.25.20, when it builds Dockerfiles or build arguments you do not fully control and an Azure identity is reachable from the build: service principal variables, a federated workload identity token, or a managed identity through the Instance Metadata Service.
  • Fixed: kaniko 1.25.20, released 27 October 2026. The project publishes source only, so you rebuild the executor, the warmer and the helper binary from that tag yourself.
  • The helper itself: github.com/chrismellard/docker-credential-acr-env has no fixed version. Go projects that import it directly must patch it, move to a maintained fork, or replace it.
  • Exploitation: we found no report of exploitation in the wild in the sources listed below as of 6 November 2026. The CVE is not in CISA's Known Exploited Vulnerabilities catalog (version 2026.11.04, checked 6 November 2026), and CISA's SSVC enrichment lists exploitation as "none".

What happened

kaniko builds container images from a Dockerfile inside a container or Kubernetes pod, without a Docker daemon, which makes it common in CI systems that run builds as pods.

Google created kaniko as GoogleContainerTools/kaniko, now archived. The README of chainguard-forks/kaniko (Go module path github.com/chainguard-dev/kaniko) says the original was archived in June 2025 and forked by Chainguard "to continue upkeep with security patches and maintenance". That fork is where this fix landed. It publishes tags and source only, and its README says the old gcr.io/kaniko-project/executor and gcr.io/kaniko-project/warmer images are unmaintained and no longer updated.

To pull from private registries, kaniko chains several keychains: the Docker config file, then Google, Amazon ECR, Azure and GitLab helpers. The Azure step is docker-credential-acr-env, written by Chris Mellard, which gets an Entra ID token from the environment and exchanges it at the registry for a registry refresh token. GitHub shows no push to its repository since 24 March 2023.

On 13 August 2026, Martin Zihlmann opened issue 21 on the helper's repository. He showed that the hostname pattern is not anchored, so evil.azurecr.io.attacker.com counts as ACR, and that the helper then sends the Entra ID access token to that host twice. The issue pointed to a new fork of the helper, osscontainertools/docker-credential-acr, released with the fix as v0.8.0 the same day. On 18 August the Go vulnerability database published GO-2026-6225.

That entry had no CVE or GitHub advisory alias. kaniko's fix pull request notes this meant no Dependabot alert fired for it. The vulnerable helper sat in kaniko's dependency list until a pull request opened on 23 October 2026 replaced it with a patched copy inside kaniko's own module. The fix merged on 26 October and shipped in kaniko 1.25.20 on 27 October. Chainguard published the kaniko advisory and CVE-2026-105769 on 2 November, and GitHub reviewed it into the global advisory database on 5 November.

Why the fix is a copy, not an upgrade

Normally a fix like this is a version bump. Here there was nothing to bump to: the Go vulnerability database lists every version of the helper as affected, with no fix. kaniko's maintainers chose to vendor the helper as ordinary packages under third_party/docker-credential-acr-env in the kaniko module, fix the host check there, and drop the upstream module from go.mod, go.sum, vendor/ and the build info of every binary.

The pull request explains why. A first attempt wrapped the helper inside pkg/creds but left the standalone binary in the image untouched. A second used a go.mod replace directive, which fixed the code but left binary scanners reporting GO-2026-6225, because the helper's module path still said github.com/chrismellard. The final version puts the copy under kaniko's own module path, so the vulnerable module disappears from build info.

Timeline

All times are UTC, taken from the source listed for each row.

When (UTC)WhatSource
24 Mar 2023Last push to chrismellard/docker-credential-acr-envGitHub repository data
June 2025GoogleContainerTools/kaniko archived; Chainguard forks itChainguard fork README
13 Aug 2026, 19:44osscontainertools/docker-credential-acr v0.8.0 released with an anchored host patternfork release notes
13 Aug 2026, 21:33Issue 21 opened on the original helper: "ACR hostname pattern is unanchored"helper issue 21
18 Aug 2026, 18:05GO-2026-6225 published in the Go vulnerability databasevuln.go.dev
5 Oct 2026, 19:21CVE-2026-105769 reserved by Chainguard as CNACVE record
23 Oct 2026, 08:41kaniko PR 495 opened: carry a patched helper inside the kaniko modulePR 495
26 Oct 2026, 20:35PR 495 merged as commit 08de01fePR 495, commit
27 Oct 2026, 19:05kaniko v1.25.20 release publishedrelease page
2 Nov 2026, 16:32Repository advisory GHSA-5q7h-w9jf-cx97 publishedGitHub API
2 Nov 2026, 16:49CVE-2026-105769 publishedCVE record
2 Nov 2026, 17:11CISA ADP adds SSVC: exploitation none, automatable no, technical impact totalCVE record
2 Nov 2026, 17:17NVD publishes the CVEGitHub advisory data
5 Nov 2026, 17:48GitHub reviews the advisory into the global advisory databaseGitHub advisory
6 Nov 2026KEV catalog version 2026.11.04 does not list the CVECISA KEV feed

Two dates in this story are easy to mix up. The kaniko CVE became public on 2 November. GitHub's reviewed global advisory, which is what most dependency alerts key off, appeared on 5 November. The underlying helper flaw, under its Go ID, had been public since mid August.

Technical details

Identifiers and scores

FieldValueSource
CVECVE-2026-105769CVE record
GitHub advisoryGHSA-5q7h-w9jf-cx97 (reviewed, critical)GitHub advisory
Go vulnerability ID (helper)GO-2026-6225vuln.go.dev
CNAChainguardCVE record
CVSS 3.19.3, AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:NCVE record, GitHub advisory
CWECWE-346 Origin Validation Error; CWE-522 Insufficiently Protected CredentialsGitHub advisory
SSVC (CISA ADP)Exploitation none; Automatable no; Technical Impact totalCVE record
EPSS0.196% (about the 9th percentile), as shown in GitHub's advisory data on 6 November 2026GitHub advisory API
KEVNot listed (catalog 2026.11.04)CISA KEV feed
Affected packagegithub.com/chainguard-dev/kaniko, all versions below 1.25.20CVE record, GitHub advisory
First patched version1.25.20GitHub advisory
FixPR 495, merge commit 08de01fe7fffaeb6bc030efa9eae723ea2dd4405, plus 58915b22dbeca4d936d6bb51c0c889baf6ebbc48GitHub advisory
Helper modulegithub.com/chrismellard/docker-credential-acr-env, all versions, no fixvuln.go.dev
Helper symbolsACRCredHelper.Get, isACRRegistry in pkg/credhelpervuln.go.dev

Credits, per the advisory: reported by Martin Zihlmann of osscontainertools (GitHub handle mzihlmann), with the fix developed by Will Cooke (8none1) and reviewed by Sergio Durigan Junior (sergiodj), the names shown on their GitHub profiles.

The vulnerable check

The original helper's pkg/credhelper/helper.go defines the pattern like this:

var acrRE = regexp.MustCompile(`.*\.azurecr\.io|.*\.azurecr\.cn|.*\.azurecr\.de|.*\.azurecr\.us`)

isACRRegistry parses the input as a URL, accepts mcr.microsoft.com directly, and otherwise runs acrRE.FindStringSubmatch on the hostname. Go's regexp finds a match anywhere in the string unless the pattern is anchored with ^ and $. So x.azurecr.io.attacker.example contains a match for .*\.azurecr\.io and passes.

The issue makes two subtle points worth repeating. First, each alternative needs its own anchors. Putting ^ on the first alternative and $ on the last only looks anchored, because alternation binds loosely. Second, the leading .*\. is there for a reason: ACR serves blobs from data endpoints such as <registry>.<region>.data.azurecr.io, so a correct check still has to allow several labels before azurecr.io. The issue also notes that a bare azurecr.io.attacker.com does not match, because the pattern needs a literal dot before azurecr.

What the helper does after the check passes

When isACRRegistry returns true, ACRCredHelper.Get calls token.GetServicePrincipalTokenFromEnvironment() to get an Entra ID token, then registry.GetRegistryRefreshTokenFromAADExchange with the host that passed the check. According to the issue and the advisory, that sends a POST to https://<host>/oauth2/exchange with the token in both the Authorization: Bearer header and the access_token form field.

The token is an Entra ID access token for Azure Resource Manager, not an ACR scoped token. The issue states this directly: what leaks "stays usable against Azure beyond the registry". The advisory spells out the consequences:

  • Until it expires, the attacker can call Azure Resource Manager with every role assignment the identity holds.
  • They can do what the helper does and exchange the token at the /oauth2/exchange endpoint of any real ACR registry the identity can access, which gives them registry credentials.
  • If the identity has AcrPush or higher on a registry, they can push images into it.

The advisory also notes that rotating the identity's credentials does not revoke an access token that has already been issued. It stays valid until it expires.

Where the token comes from

The advisory lists three ways the helper can get an identity:

Identity sourceWhat the helper reads
Service principalAZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID in the environment
Federated workload identityAZURE_FEDERATED_TOKEN or AZURE_FEDERATED_TOKEN_FILE, with AZURE_CLIENT_ID and AZURE_TENANT_ID, as AKS workload identity injects
Managed identityThe Azure Instance Metadata Service, for example on an AKS node or Azure VM, or MSI_ENDPOINT

Microsoft's workload identity documentation describes the second case: when the AKS mutating admission webhook injects the projected service account token into a pod, it also sets AZURE_FEDERATED_TOKEN_FILE to the token's path. Pods opt in with the label azure.workload.identity/use: "true". The third case needs nothing in the pod at all. Microsoft documents the Instance Metadata Service at the non routable address 169.254.169.254, reachable from inside the VM. On an AKS node with a managed identity, a pod that can reach that address may be able to get a token for the node's identity unless something blocks it.

Which code paths consult the helper

You do not have to use ACR, or configure the helper, to be exposed. The advisory explains that kaniko consults its built in helper for any registry that has no credentials in your Docker config and is not a Google or Amazon ECR registry. Every lookalike host falls into that set. That covers base images in FROM lines and images named in COPY --from=<image>. kaniko asks for credentials before it contacts the registry, so the attacker's server does not need to challenge for authentication.

There is a second path. kaniko's image ships the helper as a standalone binary at /kaniko/docker-credential-acr-env. If your Docker config.json sets "credsStore": "acr-env", Docker style clients call that binary for every registry. The advisory says this setting is uncommon, because before 1.25.20 it broke pulls from registries that are not ACR.

The patched check in 1.25.20

kaniko 1.25.20 replaces the pattern with a function that is strict about its input. According to the advisory and the pull request:

  • The input must be exactly host[:port]. The code parses it as a URL and rejects it unless the parsed Host equals the raw input, which rules out userinfo, paths, queries, fragments and percent encoded tricks.
  • An explicit port must be between 1 and 65535.
  • The hostname must be mcr.microsoft.com, or match an anchored pattern of well formed DNS labels ending in azurecr.io, azurecr.cn, azurecr.de or azurecr.us. Trailing dots and malformed labels fail.
  • Matching stays case sensitive, as upstream was.
  • For any rejected host, Get returns the standard "credentials not found" error instead of a generic error. Clients then treat the host as anonymous rather than failing the whole credential lookup.

The anchored pattern in the kaniko tree at v1.25.20 is:

var acrHostRE = regexp.MustCompile(`^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+azurecr\.(?:io|cn|de|us)$`)

The pull request also adds unit tests for lookalike hosts and a guard in deploy/Dockerfile that fails the image build unless go version -m on the helper shows the path github.com/chainguard-dev/kaniko/third_party/docker-credential-acr-env and no github.com/chrismellard module.

The archived Google kaniko

The CVE and the advisory name only github.com/chainguard-dev/kaniko. They say nothing about the archived GoogleContainerTools/kaniko repository. Its source, though, shows the same dependency: its go.mod requires github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589, the same commit kaniko's fix starts from, and its pkg/creds/creds.go wires credhelper.NewACRCredentialsHelper() into the same keychain chain. Based on that source, old gcr.io/kaniko-project/executor images appear to carry the same code path. No fix will come for them, because the repository is archived and the images are no longer updated. Treat them as affected and move off them.

Other software that used the helper

The helper is not only a kaniko dependency. The issue's author writes that pkg/authn/k8schain in go-containerregistry before v0.21.6 wrapped this helper into a keychain and consulted it for whatever registry was being pulled. The current go.mod of that k8schain module requires a different ACR helper module. If you run anything built on go-containerregistry's Kubernetes keychain, check which version it uses. Docker or Podman configured with "credsStore": "acr-env" rather than per host credHelpers is also exposed whenever it runs the original binary.

Who is affected, and who is not

SituationExposed?Why
kaniko below 1.25.20 builds Dockerfiles from forks, merge requests or other untrusted sources, and the pod has workload identity, service principal variables or IMDS accessYesThe attacker controls the image reference and an identity is reachable
kaniko below 1.25.20 builds only Dockerfiles your team controls, with an Azure identityLower, not zeroBuild arguments, templated FROM lines and compromised base image references can still steer the host
kaniko below 1.25.20 with no Azure identity reachable from the podNot for this token leakThe helper fails before it gets a token; patch anyway
kaniko 1.25.20 or later, built from that tagNoThe patched host check rejects lookalikes
Old gcr.io/kaniko-project/executor imagesTreat as yesSame helper at the same commit, per the archived source; no updates
A Go project importing github.com/chrismellard/docker-credential-acr-envYes, if it can be given arbitrary hostsNo fixed version exists
Docker or Podman with "credsStore": "acr-env" using the original binaryYesThe binary is consulted for every registry
Docker with per host credHelpers entries for your own ACR names onlyNot through this pathThe helper only runs for the hosts you listed
BuildKit, Buildah or Docker builds that never load this helperNoThe bug is in this helper, not in ACR or Entra ID

"We only build our own Dockerfiles" is less comforting when a pipeline passes --build-arg BASE_IMAGE=... from a merge request variable. Anyone who can change that value can choose the host.

What to do now

1. Find every kaniko you run

Start with the places kaniko usually hides: CI templates, Kubernetes manifests, Helm values and Tekton or Argo task definitions.

# Image references in repos and CI templates
grep -rnE "kaniko-project/(executor|warmer)|kaniko" \
  --include="*.yml" --include="*.yaml" --include="Dockerfile*" --include="*.json" .

# Running pods that use a kaniko image
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}' \
  | grep -i kaniko

Remember self built images. Many teams copy /kaniko/executor into their own runner image, which then carries a name with no "kaniko" in it. Search Dockerfiles for COPY --from= lines that pull from a kaniko image.

2. Read the version from the binary, not the tag

Tags such as latest or debug tell you nothing. Go binaries embed their module build info, and go version -m reads it without running the binary.

# Copy the executor out of an image without running it
cid=$(docker create gcr.io/kaniko-project/executor:latest)
docker cp "$cid":/kaniko/executor ./executor
docker cp "$cid":/kaniko/docker-credential-acr-env ./docker-credential-acr-env
docker rm "$cid"

# Which kaniko module and which helper are inside?
go version -m ./executor | grep -E "GoogleContainerTools/kaniko|chainguard-dev/kaniko|chrismellard"
go version -m ./docker-credential-acr-env | grep -E "^\s*(path|mod)|chrismellard"

How to read the result:

  • mod github.com/GoogleContainerTools/kaniko means the archived project. Replace it.
  • mod github.com/chainguard-dev/kaniko with a version below v1.25.20 means affected. Rebuild from v1.25.20.
  • Any line with github.com/chrismellard/docker-credential-acr-env means the unpatched helper is linked in.
  • For the standalone helper, a fixed build shows path github.com/chainguard-dev/kaniko/third_party/docker-credential-acr-env and no chrismellard module.

You can also ask govulncheck, which reads the same build info and reports Go vulnerability IDs:

go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck -mode=binary ./executor
govulncheck -mode=binary ./docker-credential-acr-env

Look for GO-2026-6225 in the output. kaniko's pull request reports that govulncheck stopped reporting it for both binaries after the fix.

3. Check Go modules that import kaniko or the helper

If you build your own tooling on kaniko as a library, or use the helper directly:

go list -m all | grep -E "chainguard-dev/kaniko|GoogleContainerTools/kaniko|chrismellard/docker-credential-acr-env"
go mod why -m github.com/chrismellard/docker-credential-acr-env
govulncheck ./...

go mod why tells you which of your packages pulls the helper in, which matters when you decide whether to upgrade a parent or replace the helper yourself.

4. Upgrade kaniko to 1.25.20 and rebuild

The advisory is explicit that the repository publishes no binaries or images. Rebuild the executor, the warmer and docker-credential-acr-env from the v1.25.20 tag or later, following the project's development guide.

git clone --branch v1.25.20 --depth 1 https://github.com/chainguard-forks/kaniko.git
cd kaniko
docker build -f deploy/Dockerfile --target kaniko-executor -t registry.example.com/build/kaniko-executor:v1.25.20 .
docker build -f deploy/Dockerfile --target kaniko-warmer   -t registry.example.com/build/kaniko-warmer:v1.25.20 .

At v1.25.20 the Dockerfile defines kaniko-executor, kaniko-warmer and kaniko-debug stages, and its builder stage refuses to produce a helper binary that is not the patched copy. Pin the result by digest in your CI templates, not by tag:

image: registry.example.com/build/kaniko-executor@sha256:<digest-from-your-build>

If you import kaniko as a Go module:

go get github.com/chainguard-dev/kaniko@v1.25.20
go mod tidy
go list -m all | grep chrismellard   # should print nothing

5. If you cannot upgrade today, cut the identity off

The advisory gives two workarounds. Both work by making sure no token can be fetched.

Remove Azure identity from build pods. Take AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN, AZURE_FEDERATED_TOKEN_FILE, MSI_ENDPOINT and MSI_SECRET out of the build environment. Remove the azure.workload.identity/use: "true" label from build pods unless the build really needs it. Then block the metadata address from build pods. A Kubernetes NetworkPolicy works if your cluster enforces network policy:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: build-pods-no-imds
  namespace: ci-builds
spec:
  podSelector:
    matchLabels:
      app: kaniko-build
  policyTypes: ["Egress"]
  egress:
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 169.254.169.254/32

Check that it is enforced. Kubernetes only applies NetworkPolicy when the cluster's network plugin supports it, so confirm your cluster has a network policy engine turned on.

Disable the helper's Azure lookup. If you do not rely on kaniko's built in ACR login, the advisory suggests setting AZURE_ENVIRONMENT to a value that names no Azure cloud, such as none, in the kaniko container. The helper then fails before it requests any token. The side effect is that kaniko no longer logs in to ACR through the helper, so give it ACR credentials another way, such as a Docker config with per host entries.

env:
  - name: AZURE_ENVIRONMENT
    value: "none"

Fix credsStore if you use it. If any Docker config sets "credsStore": "acr-env", replace it with credHelpers entries that name only your own registries:

{
  "credHelpers": {
    "myregistry.azurecr.io": "acr-env"
  }
}

The advisory notes that this change alone closes only the standalone binary path. The built in helper inside the executor still needs the upgrade or one of the workarounds above.

6. Check your Docker configs and identities

# What credential settings does each build image or runner use?
jq '{credsStore, credHelpers}' /kaniko/.docker/config.json 2>/dev/null
jq '{credsStore, credHelpers}' ~/.docker/config.json 2>/dev/null

# Which pods opt in to workload identity?
kubectl get pods -A -l azure.workload.identity/use=true \
  -o custom-columns=NS:.metadata.namespace,POD:.metadata.name,SA:.spec.serviceAccountName

# Which client ID does each service account map to?
kubectl get sa -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.metadata.annotations.azure\.workload\.identity/client-id}{"\n"}{end}' \
  | awk -F'\t' '$3 != ""'

For each identity that a build pod can reach, list its role assignments:

az role assignment list --assignee <client-or-object-id> --all \
  --query "[].{role:roleDefinitionName, scope:scope}" -o table

That list is the blast radius of a leaked token for that identity. A build identity with AcrPull on one registry is a small problem. One with Contributor on a subscription is a large one.

7. If untrusted builds ran with an identity, investigate

The advisory says to treat any access token issued to that identity during those builds as compromised. Focus on what it could have done while valid, and on longer lived secrets it could read.

  1. Find the builds. List builds from forks, external merge requests or user supplied build arguments that ran on affected kaniko with an identity attached. Pull their Dockerfiles and resolved build arguments, and look for FROM or COPY --from hosts that contain azurecr. but do not end in your registry names.
  2. Entra ID sign in logs. In the Entra admin center, review service principal and managed identity sign ins for the identity's client ID over that period. Look for activity from unfamiliar IP addresses.
  3. Azure activity log. Filter by the identity as caller and look for role assignment writes, key and secret listings (listKeys and listSecrets style operations), deployments and resource changes.
az monitor activity-log list --caller <client-or-object-id> \
  --start-time 2026-08-13T00:00:00Z --end-time 2026-11-06T00:00:00Z \
  --query "[].{time:eventTimestamp, op:operationName.value, status:status.value, ip:httpRequest.clientIpAddress}" -o table
  1. ACR push history. If the identity had AcrPush or higher, check registry logs for pushes and token exchanges you cannot match to a known build. If you send ACR diagnostics to Log Analytics, the repository and login event tables are the place to look.
  2. Rotate what the identity could read. The advisory calls out secrets reachable through Azure Resource Manager, such as storage account keys. Rotate those, and rotate the service principal secret if one was in the environment.

What changed since first reports

  • Scope widened from the helper to kaniko. The August issue and GO-2026-6225 described a flaw in one Go module. The November advisory adds the kaniko specific facts: that the built in helper runs for any registry without other credentials, that COPY --from is a trigger as well as FROM, and that the standalone binary is a second path.
  • Identity sources and workarounds were spelled out. The advisory adds the exact environment variables, MSI_ENDPOINT, AZURE_ENVIRONMENT=none and the credsStore change.
  • The scanner story changed. Before 1.25.20, scanners that read Go build info reported GO-2026-6225 for kaniko binaries. From 1.25.20 the upstream module is gone from build info, so they stop, which is a quick sign the upgrade worked.

Open questions

  • Other forks and downstream builds. The osscontainertools helper fork shipped an anchored pattern in August, and its release notes say the helper is used inside that project's kaniko executor image. We have not verified which versions of every kaniko fork or downstream package carry which helper. Check build info rather than assuming.
  • The archived Google project. No advisory covers it. The source shows the same dependency, but nobody has published a formal statement for those images.
  • Other importers of the helper. The Go vulnerability database lists the module but not every program that imports it. Search your own go.mod files and binaries.
  • Token lifetime. Entra ID sets the lifetime of tokens issued for Microsoft APIs, and the advisory gives no figure. Use the exp claim of a sample token from your own environment if you need a number for an incident window.

What would have helped

Practices any team can use

Never give a build that runs untrusted input a cloud identity. This is the lesson that outlives the bug. Split pipelines into an untrusted build stage with no cloud identity and no metadata access, and a trusted push stage that runs only after review, with an identity scoped to the one registry it pushes to.

Scope build identities to the registry, not the subscription. Give the build identity AcrPull on the registries it pulls from and nothing else. Give push jobs AcrPush on the one target registry. Microsoft documents the ACR built in roles for this purpose.

Block the metadata address from build pods by default. IMDS is how this bug reaches a managed identity with nothing in the pod's spec. Deny egress to 169.254.169.254 in build namespaces by default.

Allowlist registries in image references. A pre build check that rejects FROM and COPY --from references outside an approved list of registries stops lookalike hosts before kaniko sees them. Resolve build arguments first, or reject FROM lines that use a variable for the registry part. A starting point for listing what a Dockerfile pulls:

grep -iE '^\s*(FROM|COPY\s+--from=)' Dockerfile

Track Go vulnerability IDs, not only CVEs. GO-2026-6225 sat in the Go vulnerability database for about ten weeks before kaniko's fix, with no CVE or GitHub alias. kaniko's own pull request notes that no Dependabot alert fired. Running govulncheck in CI against source and built binaries catches entries that only exist under a Go ID.

Watch for unmaintained dependencies on security paths. A credential helper with no commits since 2023, handling cloud tokens, deserved attention before any CVE existed.

Where Safeguard fits

Safeguard did not discover this bug. It was reported by Martin Zihlmann of osscontainertools, and the kaniko fix was developed by Will Cooke and reviewed by Sergio Durigan Junior, according to the advisory. Here is what Safeguard does that is relevant, and where it stops.

Inventory and matching. The Safeguard CLI scans repositories, container images and SBOM files (safeguard scan --dir ., safeguard scan --image <name>, safeguard scan --sbom <file>) and writes results as JSON under ./output. Scanning a repository that imports kaniko or the helper lists them as dependencies from go.mod. Scanning your runner and executor images produces an SBOM of what the scanner identifies in them. Vulnerability matching compares components against vulnerability databases, and the platform shows which projects contain a given component and version. That answers "where do we have kaniko below 1.25.20, or the helper module at all?" across your estate. For Go binaries, confirm what the SBOM lists against go version -m, since that build info is the ground truth.

Policies and gates. Safeguard policies and gates are evaluated in the platform and can run in CI with safeguard exec. A policy that fails a pipeline when a runner image or Go module contains kaniko below 1.25.20, or any version of github.com/chrismellard/docker-credential-acr-env, stops new builds from reintroducing it. A policy violation of that kind is what Safeguard calls a finding.

Prioritization. Reachability analysis ranks the vulnerable code paths your application actually calls, which helps for Go projects that import the helper. For kaniko itself every pull goes through the keychain, so identity exposure is the deciding factor.

Remediation. For Go projects that depend on kaniko as a module, autonomous remediation can open a policy gated pull request that moves github.com/chainguard-dev/kaniko to v1.25.20, and your own CI runs your tests before anyone merges. Auto-merge is off by default. For CI templates that pin a kaniko image, the Safeguard CLI's assistant can help you change the image reference and digest in a terminal session, asking before any command that changes something.

Where Safeguard does not help directly. Safeguard does not see which Azure identities your build pods can reach, does not read your Entra ID sign in logs or Azure activity log, and does not change your network policies or role assignments. Those checks are yours.

Zero-Day Discovery and Gold

Safeguard's Zero-Day Discovery looks for previously unknown flaws in open source packages before a CVE exists, in an agentic mode and a deterministic scanner mode, and issues what it finds as SGZ advisories. It had no part in this disclosure.

Gold Open Source at gold.safeguard.sh is a free, no login lookup for CVE intelligence: CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version. Search there for CVE-2026-105769 and check whether a record exists and what it says before you rely on it. For kaniko versions, the GitHub advisory and the release tag remain the authority.

The habit that helps most is pulling patched, signed components from a source you trust and rebuilding fast when a fix lands. Here the fix for kaniko exists, but the helper module it came from has none. The sections below cover that case. We make no claim that Gold has or had a patched build of kaniko or of this helper.

Common mistakes

Upgrading the executor and forgetting the warmer. The warmer pre pulls base images and links the same keychain. Rebuild both, and the debug image if you use it.

Fixing credsStore and stopping there. Replacing "credsStore": "acr-env" with per host credHelpers closes only the standalone binary path. The built in helper inside the executor still runs for unknown hosts until you upgrade.

Assuming "we don't use ACR" means safe. The built in helper runs for every registry that is not Google or ECR and has no other credentials. If an Azure identity is reachable, a lookalike host gets a token whether or not you ever log in to ACR.

Removing secrets but leaving IMDS open. Deleting AZURE_CLIENT_SECRET from the pod is not enough on a node with a managed identity. Block 169.254.169.254 too.

Rotating the secret and calling it done. A token issued before the rotation stays valid until it expires. Review what it could have done, and rotate the downstream secrets it could read.

FAQ

Do I need to use ACR to be affected?

No. According to the advisory, kaniko consults the built in helper for any registry that has no credentials in your Docker config and is not a Google or Amazon ECR registry. What you need for exposure is an Azure identity the helper can reach and an image reference that an outsider can influence.

Is it being exploited?

We found no report of exploitation in the wild in the sources below as of 6 November 2026. The CVE is not in CISA's KEV catalog (version 2026.11.04), and CISA's SSVC enrichment lists exploitation as "none". The helper flaw has been public since August, so do not read the absence of reports as proof nothing happened in your environment.

Which version fixes it?

kaniko 1.25.20, from the chainguard-forks/kaniko repository, Go module github.com/chainguard-dev/kaniko. You must build it yourself, because the project publishes no images.

We use the old gcr.io/kaniko-project/executor image. Are we affected?

The advisory does not cover that project. Its archived source requires the same helper at the same commit and wires it into the keychain the same way, so treat those images as affected. They are unmaintained and will not be updated. Move to a maintained build.

What exactly leaks?

An Entra ID access token for Azure Resource Manager, not a registry scoped token. The attacker can use it with all of the identity's role assignments until it expires, and can exchange it at real ACR registries for registry credentials. With AcrPush, that means pushing images.

Does rotating the service principal secret fix a leak?

No. The advisory notes that rotating credentials does not revoke an access token that was already issued. Rotate anyway, then review activity for the period and rotate the secrets the identity could read.

We import docker-credential-acr-env in our own Go code. What do we do?

There is no fixed version of that module. Patch it with an anchored host check, move to a maintained fork, or replace it with a different ACR login method. The section below covers each option.

Do GitHub Dependabot alerts cover this?

For kaniko, the reviewed global advisory GHSA-5q7h-w9jf-cx97 has existed since 5 November, so dependency alerts can key off it. For the helper module alone, kaniko's pull request notes that GO-2026-6225 had no CVE or GitHub advisory alias and so produced no Dependabot alert. govulncheck reports it by its Go ID.

When the helper is your own dependency: patch, fork or replace it

kaniko users have a fix. Anyone who imports github.com/chrismellard/docker-credential-acr-env directly does not, because the module has no fixed version. That leaves three options.

Option 1: patch it in your tree. Copy the helper into your repository as a package of your own module, the way kaniko did, and replace the pattern with an anchored one. The smallest correct change, from the original issue, gives each alternative its own anchors:

var acrRE = regexp.MustCompile(`^.*\.azurecr\.io$|^.*\.azurecr\.cn$|^.*\.azurecr\.de$|^.*\.azurecr\.us$`)

kaniko's version, described above, is the better model: exact host[:port] input, a validated port, well formed labels and "credentials not found" for rejected hosts. Keep the upstream Apache 2.0 license and headers and record your changes, as kaniko's PATCHES.md does. If you use a go.mod replace instead of moving the code under your own module path, expect binary scanners to keep reporting GO-2026-6225, and record why.

Option 2: move to a maintained fork. The osscontainertools project forked the helper as github.com/osscontainertools/docker-credential-acr and fixed the pattern in v0.8.0 on 13 August 2026, according to its release notes. It has since released newer versions and renamed the module and binary to docker-credential-acr. Review it as you would any new dependency before adopting it.

Option 3: replace it. If you log in to a fixed set of registries, per host credHelpers entries or a login step that names each registry explicitly avoids matching arbitrary hosts at all.

Whichever you choose, add a test that a lookalike host such as x.azurecr.io.example.invalid gets "credentials not found" without any network call, and a CI check that fails if github.com/chrismellard/docker-credential-acr-env reappears in go list -m all.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Griffin, Safeguard's security model, opens a policy gated pull request that moves the affected dependency to the safe version, and your own CI runs your tests on it before anyone merges. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Auto-merge is a separate switch and stays off by default, so your team decides what merges on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Safeguard Gold rebuilds patched versions of open-source packages and images from source, signs each build, attaches an SBOM, SLSA provenance and VEX, and serves them from registry.safeguard.sh to your organization. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would have helped

The first demo is a real Safeguard CLI session: you install the CLI, run safeguard and ask it to fix a lodash CVE in a repository. It reads the vulnerability and the advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. The demo shows an npm change, not a kaniko one. For a Go tool that imports kaniko, you would make the same kind of request, asking it to move github.com/chainguard-dev/kaniko to v1.25.20 and run go mod tidy, then review the diff and let your CI build and test it.

Click through it at your own pace, or open it full screen.

The second demo sets up the Safeguard runner on your own machine, approves it with its device code, then scans from the terminal. For a platform team with many runner and executor images, that is how you would run safeguard scan --image across the images in your own registry from your own infrastructure and collect which ones carry kaniko or the helper module.

Click through it at your own pace, or open it full screen.

The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask an assistant which of your projects contain a given package and version while you plan the rollout. Whether a given CVE is in your workspace's data depends on your account, so confirm fixed versions against the GitHub advisory.

Click through it at your own pace, or open it full screen.

To try it on your own images, download Safeguard or start with the CLI documentation. For background on finding affected products from SBOMs, see SBOM incident response: finding affected products fast, our incident response playbook for a compromised dependency and our review of container image supply chain incidents in 2026.

Quick check

Your CI runs kaniko 1.25.19 on AKS for merge requests from forks, with no Azure variables in the pod and no workload identity label. Are you safe?

Not necessarily. If the pod can reach the Instance Metadata Service at 169.254.169.254 on a node with a managed identity, the helper can get a token there. Upgrade to 1.25.20, and block that address from build pods.

You replaced "credsStore": "acr-env" with per host credHelpers entries. Is the executor fixed?

No. That closes only the standalone binary path. The helper linked into the executor and warmer still runs for unknown hosts until you rebuild from 1.25.20 or apply the advisory's other workarounds.

Sources

Last reviewed: 6 November 2026. This is a developing story; check the linked advisories for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

We email you a link to confirm, and only then add you to the list. Unsubscribe in one click. Privacy

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.