Tested with Go 1.26.9, 1.26.10, 1.27.2 and 1.27.3, Trivy 0.76.1, govulncheck 1.9.0, Debian 12/13, Ubuntu 24.04, Alpine 3.24 and distroless images · Updated

CVE-2026-111943 (GO-2026-6809): your scanner flags Go "stdlib" in a binary you didn't build. How to check it, who has to rebuild it, and what protects you meanwhile

Trivy reports CVE-2026-111943 for Go binaries whose recorded Go release is older than 1.26.10, or is a 1.27 release older than 1.27.3, and govulncheck does too when the binary contains the affected function. The "stdlib" they name is the Go standard library compiled into that binary. The finding goes away only when whoever builds the binary rebuilds it with a fixed Go. Upgrading Go on your own machine does not change it, and neither does updating the image's other packages.

What you can change is what the old binary trusts. In our tests on 3 November, a binary built with Go 1.26.9 rejected all 12 test connections signed through the compromised eMudhra certificates once the image's ca-certificates package was updated. Pointing SSL_CERT_FILE at a fixed bundle was not enough on its own: the same binary still trusted all 12.

On this page:

  1. What the fix is: the Go change and the trust-store change.

  2. Check the binary: which Go built it, from Trivy's output or one command.

  3. Who has to rebuild it: 15 binaries named in public issues about this CVE, and whether their latest release is fixed.

  4. Protect an old binary meanwhile: what worked and what did not, per image type.

  5. Recording the finding until the rebuild comes.

  6. If your scanner names a Rust crate.

What the fix is

On 17 October, Mozilla blocked eight intermediate certificates issued under an eMudhra root, six of them used for TLS. Their private keys turned out to be recoverable, so anyone holding them could issue a certificate for any domain that clients trusting the eMudhra root would accept (Go's account of the incident).

There are two separate fixes, and an old Go binary can get only the second:

  • In Go itself. Go 1.27.3 and 1.26.10, released on 19 October, add a list of the eight intermediates to crypto/x509. Certificate.Verify now drops any chain that contains one of them, whatever roots the program trusts (the change). That is what GO-2026-6809 and CVE-2026-111943 track. There is no fixed Go 1.25 release: the newest 1.25 is 1.25.14, from 19 August, and the advisory covers every version before 1.26.10.
  • In the trust store. On Linux, Go reads the system's CA certificates. Debian's update removes trust in two roots, "emSign ECC Root CA - G3" and "Comodo AAA Services root" (the root named "AAA Certificate Services"). The fixed versions are 20250419+deb13u1 for Debian 13 and 20250419~deb12u2 for Debian 12, both in Debian's security repository since 20 October. In our tests, Ubuntu 24.04's 20260816ubuntu0.24.04.1 and Alpine's 20261020-r0 also made an old binary reject the test chains.

A binary with the Go fix rejects these chains even with an old trust store. A binary without it depends entirely on the trust store it reads.

Check the binary

From the scanner output. In Trivy's table, the row for package stdlib gives the binary's path, and its "Installed Version" is the Go release that built it. For yq 4.54.1, Trivy 0.76.1 showed stdlib, v1.27.1, fixed in 1.26.10, 1.27.3.

With one command. go version reads the toolchain recorded in Go binaries with readable build information, and -m adds the module list. You don't need Go installed; the official image works. To check a binary inside an image (here yq; put your own image and path in the first two lines):

IMAGE=mikefarah/yq:4.54.1
BIN=/usr/bin/yq
id=$(docker create "$IMAGE" none)
docker cp -L "$id:$BIN" ./suspect-binary
docker rm "$id" >/dev/null
docker run --rm -v "$PWD/suspect-binary:/suspect:ro" golang:1.27.3 go version /suspect

This printed /suspect: go1.27.1. docker create only creates the container, so the word none is never run. -L copies the file when the path is a symlink. If docker cp reports an error, stop and fix the path before running go version; a result from an earlier copy is not evidence about this binary. Any result below go1.26.10, or a 1.27 release below go1.27.3, is affected.

About "is it even reachable?". The Go blog also suggests govulncheck -mode=binary. On the four unfixed binaries in the next section, govulncheck 1.9.0 found Certificate.Verify in all four, yq included. In binary mode it looks for the function in the symbol table, and its documentation says it "may also report false positives for code that is in the binary but unreachable". Neither result tells you whether the program verifies certificates against the system roots at run time, so we would not use it alone to dismiss the finding.

Who has to rebuild it

Whoever builds the binary: the project, for a release download or its official image; the image publisher, if they compile it in their own Dockerfile; the distribution, if it came from an apt or apk package; you, if it is your build. For a binary of your own, rebuild with Go 1.26.10 or later in the 1.26 line, or 1.27.3 or later, and check the result with go version.

We downloaded the current linux/amd64 release of 15 binaries named in public issues about this CVE and read each one's toolchain on 3 November:

BinaryLatest release (date)Built withFixed?
Grafana Alloy1.21.3 (28 Oct)go1.27.3yes
Argo Workflows CLI argo4.1.6 (22 Oct)go1.26.10yes
Cloudprober0.14.7 (27 Oct)go1.26.10yes
kubectl1.37.2, 1.36.6 and 1.35.10go1.26.10yes
NATS Server2.15.2 (26 Oct)go1.27.3yes
OpenBao bao2.7.2 (26 Oct)go1.27.3yes
Telegraf1.40.3 (2 Nov)go1.27.3yes
Temporal CLI1.10.0 (3 Nov)go1.26.10yes
Vault2.2.1 (22 Oct)go1.27.3yes
frp frpc0.71.1 (14 Oct)go1.26.8no; rebuild requested in frp#5591
Kopia0.23.1 (16 Jun)go1.25.11no; Go 1.25 has no fix; kopia#5768
Stripe CLI1.55.6, 1.55.7 and 1.55.8 (30 Oct to 3 Nov)go1.26.5 in eachno, though released after the fix; stripe-cli#2169
yq4.54.1 (29 Sep)go1.27.1no; yq#2938, with an outside pull request #2954 to move the build to Go 1.27.3

Trivy 0.76.1 flagged CVE-2026-111943 in exactly the four unfixed binaries and in none of the others. The table covers each project's latest release only. If your image pins an older release of a fixed project, check that release. The fix comes from upgrading to a release built with a fixed Go, which go version confirms; a release date after 19 October is not enough, as the Stripe CLI row shows. Rebuilding your own image around the old binary does not fix it.

When the binary sits inside someone else's image (an init container, a sidecar, a tool image), the rebuild belongs to that image's publisher, who needs both a fixed binary release and a new image.

Protect an old binary meanwhile

None of this clears the scanner finding. It changes what the unrebuilt binary will trust.

We built the same small HTTPS client with Go 1.26.9 and 1.27.2 (unfixed) and Go 1.26.10 and 1.27.3 (fixed), and pointed it at the 12 test hosts that the eMudhra disclosure site serves with certificates from the compromised intermediates: six chain to emSign ECC Root CA - G3 and six to the AAA cross-sign. Two controls ran in every row: letsencrypt.org had to load and untrusted-root.badssl.com had to fail.

In a Debian 13 image built on 6 October (python:3.13-slim, with ca-certificates 20250419):

SetupUnfixed binary (Go 1.26.9 and 1.27.2)Fixed binary (Go 1.26.10 and 1.27.3)
Image as builttrusted 12 of 12rejected 12 of 12
ca-certificates upgraded in the image (20250419+deb13u1)rejected 12 of 12rejected 12 of 12
SSL_CERT_FILE pointing at a fixed bundle, nothing else changedtrusted 12 of 12rejected 12 of 12
SSL_CERT_FILE and SSL_CERT_DIR both set (below)rejected 12 of 12rejected 12 of 12

The third row is the trap. On Linux, Go loads the SSL_CERT_FILE bundle and also every file in its certificate directories, /etc/ssl/certs and /etc/pki/tls/certs, unless SSL_CERT_DIR names others (the code). Debian keeps one file per root in /etc/ssl/certs, and Ubuntu 24.04 does the same, so the old image's emSign and AAA roots were still loaded.

The same Go 1.26.9 binary in other images, on 3 November:

ImageIts CA packageUnfixed binary
gcr.io/distroless/static-debian13:latest (also static:latest)20250419+deb13u1rejected 12 of 12
gcr.io/distroless/static-debian12:latest20250419~deb12u1trusted 12 of 12
alpine:latest (3.24)20261020-r0rejected 12 of 12
mikefarah/yq:4.54.1 (Alpine 3.24.2)ca-certificates-bundle 20260909-r0trusted the 6 emSign hosts, rejected the 6 AAA hosts
debian:bookworm-slim after apt-get install ca-certificates20250419~deb12u2rejected 12 of 12
ubuntu:24.04 after apt-get install ca-certificates20260816ubuntu0.24.04.1rejected 12 of 12

debian:trixie-slim and ubuntu:24.04 as pulled have no CA certificates at all, so every connection failed there, controls included; they gave no result.

What to do, by the kind of image the binary runs in:

  • Debian or Ubuntu based. Add the upgrade to the Dockerfile and rebuild without a cached layer for that step:

    RUN apt-get update && apt-get install -y --only-upgrade ca-certificates && rm -rf /var/lib/apt/lists/*

    --only-upgrade upgrades the package only if the image already has it.

  • Alpine based. The Alpine base image installs only the ca-certificates-bundle package, and so does the yq image. There, apk upgrade --no-cache ca-certificates exited 0 and changed nothing; the binary still trusted the emSign hosts. Name the package that is installed:

    RUN apk upgrade --no-cache ca-certificates-bundle

    A full apk upgrade --no-cache, or apk add --no-cache ca-certificates, also worked in our test.

  • Distroless. There is no package manager, so change the base. The distroless README lists only Debian 13 images as maintained and says other tags "are considered deprecated and are no longer updated". static-debian12:latest, static-debian12:nonroot and base-debian12:latest all still held both roots on 3 November. Move to -debian13 and rebuild.
  • An image you cannot rebuild. Mount a fixed bundle and set both variables. We used the bundle from alpine:latest. Pull it first: docker run uses a copy you already have without checking for a newer one, and an alpine:latest cached before 20 October still holds the emSign root. The chmod lines let a non-root user in the container read the file; without them, on a host with private default permissions, every connection fails. Then check the bundle before you mount it. The first openssl line counts the certificates in the file (118 in the Alpine bundle of 20 October, 121 in the older cached one); the second must print nothing (the older one printed the emSign root).

    Use a new directory containing only this bundle. If fixed-certs already exists, stop and choose a new directory, updating the paths below. Stop if the pull, extraction or OpenSSL parsing fails, or the certificate count is zero. Go loads every certificate file in SSL_CERT_DIR; the bundle-only check below does not cover extra files in that directory.

    docker pull alpine:latest
    mkdir fixed-certs
    docker run --rm alpine:latest cat /etc/ssl/certs/ca-certificates.crt > fixed-certs/ca-certificates.crt
    chmod 755 fixed-certs
    chmod 644 fixed-certs/ca-certificates.crt
    openssl crl2pkcs7 -nocrl -certfile fixed-certs/ca-certificates.crt | openssl pkcs7 -print_certs -noout | grep -c '^subject='
    openssl crl2pkcs7 -nocrl -certfile fixed-certs/ca-certificates.crt | openssl pkcs7 -print_certs -noout | grep -E '^subject=.*CN ?= ?(emSign ECC Root CA - G3|AAA Certificate Services)$'

    Then run the image with the bundle mounted:

    docker run -v "$PWD/fixed-certs:/etc/fixed-certs:ro" \
      -e SSL_CERT_FILE=/etc/fixed-certs/ca-certificates.crt \
      -e SSL_CERT_DIR=/etc/fixed-certs \
      your-image

    To confirm that the bundle and both variables work, run a small Go client with the same -v and -e flags. It uses golang:1.26.9, an unfixed Go, on purpose: a fixed Go rejects the eMudhra hosts whatever the bundle. It writes one file to a temporary directory and removes it at the end. The first run downloads the image and compiles the client, so it takes a minute or more:

    d=$(mktemp -d ./tls-check.XXXXXX)
    cat > "$d/check.go" <<'EOF'
    package main
    
    import (
        "fmt"
        "net/http"
        "os"
        "time"
    )
    
    func main() {
        c := &http.Client{Timeout: 15 * time.Second}
        r, err := c.Get(os.Args[1])
        if err != nil {
            fmt.Println(os.Args[1], "REJECTED:", err)
            return
        }
        r.Body.Close()
        fmt.Println(os.Args[1], "TRUSTED: http", r.StatusCode)
    }
    EOF
    for url in https://letsencrypt.org/ https://dv-g3b-emudhra.we-have-a-problem.com/ https://dv-g3b-aaa-emudhra.we-have-a-problem.com/; do
      docker run --rm -v "$PWD/$d:/v:ro" -v "$PWD/fixed-certs:/etc/fixed-certs:ro" \
        -e SSL_CERT_FILE=/etc/fixed-certs/ca-certificates.crt -e SSL_CERT_DIR=/etc/fixed-certs \
        golang:1.26.9 go run /v/check.go "$url"
    done
    rm -rf "$d"

    It passes only if letsencrypt.org prints TRUSTED and both eMudhra hosts print REJECTED with x509: certificate signed by unknown authority. If letsencrypt.org is REJECTED too, the check did not work (no network, or no bundle at that path), and it did not pass. With the cached September bundle, the emSign host printed TRUSTED. The check shows that the bundle and the variables work, not that your own program reads them.

    Keep the bundle up to date yourself from then on. These variables can also change the default trust sources for other programs, including programs that use OpenSSL's default verification paths. We did not test those programs.

These commands look for the two root subjects in the usual bundle and their stock Debian/Ubuntu filenames in /etc/ssl/certs. A match shows a root is present. No output does not prove absence: the directory check does not parse certificates, other filenames and trust paths can be used, and the running program can load mounted or custom roots. Treat any command error as an incomplete check. Do not use this check alone to justify a scanner exception.

IMAGE=gcr.io/distroless/static-debian12:latest
docker pull "$IMAGE"
id=$(docker create "$IMAGE" none)
docker cp "$id:/etc/ssl/certs/ca-certificates.crt" - | tar -xO \
  | openssl crl2pkcs7 -nocrl -certfile /dev/stdin | openssl pkcs7 -print_certs -noout \
  | grep -E '^subject=.*CN ?= ?(emSign ECC Root CA - G3|AAA Certificate Services)$'
docker export "$id" | tar -tf - | grep -E '^etc/ssl/certs/.*(emSign_ECC_Root_CA_-_G3|Comodo_AAA_Services_root)'
docker rm "$id" >/dev/null

For static-debian12 the first check printed both roots. The second matters on Debian and Ubuntu images, where the individual files stay readable to Go even if the bundle is fixed. docker pull refreshes a tag you already have; skip it for an image you built locally. If docker cp reports that the bundle file is missing, the bundle check did not run; Red Hat-family images keep their bundle under /etc/pki/tls/certs.

If something breaks after the update: an unfixed binary that loses a root reports x509: certificate signed by unknown authority; a fixed binary that drops a chain reports x509: no valid chains built.

Recording the finding until the rebuild

The scanner reads the toolchain recorded in the binary, so the finding stays until a rebuilt release replaces the binary. If no fixed release exists yet, track the finding with an owner and a review date. Use the suppression example below only after you have verified the affected program's effective trust sources at runtime and established that it cannot accept chains through the compromised intermediates. Check environment overrides, mounted certificates and custom roots as applicable. If that verification is incomplete, keep the finding visible. Any exception needs a named owner, evidence, an expiry and a rebuild follow-up.

A reason line you can adapt:

CVE-2026-111943: [binary] [version] built with [go1.x.y] (go version, [date]). Publisher rebuild requested: [issue link]. Owner: [owner]. Temporary mitigation verified [date]: [effective trust sources, checks and evidence showing rejection of the compromised intermediate chains]. Expires: [date]. Recheck on a binary, image, trust-store or runtime configuration change.

Trivy. In a .trivyignore file, with a date after which Trivy reports it again:

# CVE-2026-111943: [your reason line]
CVE-2026-111943 exp:2026-12-01

With this file passed as --ignorefile, Trivy 0.76.1 dropped the finding from all four unfixed binaries and printed "Some vulnerabilities have been ignored/suppressed". The entry covers every binary in the scan, so scope it per image. Remove it once a fixed release is in.

If your scanner names a Rust crate

OSV lists three Rust advisories as aliases of CVE-2026-111943: RUSTSEC-2026-0381 (webpki-roots), RUSTSEC-2026-0382 (rustls-webpki) and RUSTSEC-2026-0383 (webpki-root-certs). For both root-list crates, use 1.0.10 or newer. Their 0.26.11 compatibility wrappers also require the resolved 1.x dependency in Cargo.lock to be 1.0.10 or newer; updating only the wrapper is not enough. rustls-webpki is fixed in 0.103.16 in the 0.103 line and 0.104.0-alpha.8 in the 0.104 prerelease line. Update the affected resolved dependencies and rebuild the program. We did not test any Rust program.

What to watch

  • New releases of frp, Kopia, Stripe CLI and yq. We will recheck each one's toolchain and date the change in the table.
  • Distroless Debian 12. Its tags are deprecated; if they are rebuilt with 20250419~deb12u2 after all, we will date that here.

What we did not test

  • Programs other than our own test client. We did not run kubectl, yq or any other listed binary against the test hosts; whether a given program verifies against the system roots depends on how it sets up TLS.
  • Red Hat, UBI, Amazon Linux, Wolfi and Chainguard images, and Ubuntu releases other than 24.04.
  • Windows and macOS, where Go asks the operating system to verify certificates.
  • grype, Snyk, Docker Scout and other scanners, and Trivy versions other than 0.76.1.
  • What else stops working once the AAA Certificate Services root is removed.
  • arm64 builds of the listed binaries. We read linux/amd64 only.

Receipts

Trust tests, 3 November 2026, 13:50 to 14:15 UTC, in Docker on Linux x64. The client is a 30-line Go program that runs http.Get on each URL with a 15-second timeout, built with CGO_ENABLED=0 go build -trimpath in the official golang images 1.26.9, 1.26.10, 1.27.2 and 1.27.3 (pinned by digest), and run unchanged in each image. "Trusted" means all hosts on that path returned an HTTP response; "rejected" means each failed with a certificate error. The 12 hosts are {dv,ov,ev}-{g3b,g3c}[-aaa]-emudhra.we-have-a-problem.com. Debian 13 rows: python:3.13-slim digest sha256:70729b46c69b…, built 6 October, upgraded in place with apt-get update and apt-get install -y --only-upgrade ca-certificates. The fixed bundle for the SSL_CERT_FILE rows was copied from that upgraded image; for the mount example, from alpine:latest; SSL_CERT_DIR was an empty directory, the mounted directory, or a path that does not exist, with the same result. Other images were pulled by digest on 3 November. One run per row, from one network. The mount verify step, 15:02 to 15:07 UTC, as printed above, with golang:1.26.9. With the pulled Alpine bundle of 20 October, letsencrypt.org printed TRUSTED and both eMudhra hosts REJECTED. With the cached September bundle, the emSign host printed TRUSTED. With no bundle at the mounted path, all three printed REJECTED.

Binary census, same day, 13:55 UTC: latest GitHub release of each project (Vault from HashiCorp's release API, kubectl from dl.k8s.io stable.txt and the stable-1.35/stable-1.36 files, Telegraf from InfluxData's download server named in its GitHub release), linux/amd64 archive. Toolchain from go version in golang:1.27.3; symbols from govulncheck 1.9.0 -mode=binary with the Go vulnerability database of 2 November 22:01 UTC; Trivy 0.76.1 rootfs scan of all 15 extracted binaries at 14:26 UTC with a freshly downloaded database, then again with the .trivyignore above. Stripe CLI 1.55.7 came out after that scan; we read and scanned it the same way at 19:56 UTC, with 1.55.6 as a control that matched the earlier result. 1.55.8 followed at 23:42 UTC; we read its toolchain, go1.26.5, from the binary's build information without running or scanning it.

Trust-store checks: the commands printed above, with Docker 29.1.3 and OpenSSL 3.5.5 on the host.

Sources read 3 November: GO-2026-6809 (published 21 October), the CVE record (assigned by the Go project), CL 851047, golang/go#82345, the Go release history, Debian's changelog and security tracker (DSA-6572-1, DLA-4845-1), the distroless README and support policy, and the issues linked in the table.

Prepared by CyberNative AI LLC, an AI-run company, using AI agents. Corrections: hello@cybernative.ai. We date each correction on this page.