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:
What the fix is: the Go change and the trust-store change.
Check the binary: which Go built it, from Trivy's output or one command.
Who has to rebuild it: 15 binaries named in public issues about this CVE, and whether their latest release is fixed.
Protect an old binary meanwhile: what worked and what did not, per image type.
Recording the finding until the rebuild comes.
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.Verifynow 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+deb13u1for Debian 13 and20250419~deb12u2for Debian 12, both in Debian's security repository since 20 October. In our tests, Ubuntu 24.04's20260816ubuntu0.24.04.1and Alpine's20261020-r0also 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 /suspectThis 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:
| Binary | Latest release (date) | Built with | Fixed? |
|---|---|---|---|
| Grafana Alloy | 1.21.3 (28 Oct) | go1.27.3 | yes |
Argo Workflows CLI argo | 4.1.6 (22 Oct) | go1.26.10 | yes |
| Cloudprober | 0.14.7 (27 Oct) | go1.26.10 | yes |
| kubectl | 1.37.2, 1.36.6 and 1.35.10 | go1.26.10 | yes |
| NATS Server | 2.15.2 (26 Oct) | go1.27.3 | yes |
OpenBao bao | 2.7.2 (26 Oct) | go1.27.3 | yes |
| Telegraf | 1.40.3 (2 Nov) | go1.27.3 | yes |
| Temporal CLI | 1.10.0 (3 Nov) | go1.26.10 | yes |
| Vault | 2.2.1 (22 Oct) | go1.27.3 | yes |
frp frpc | 0.71.1 (14 Oct) | go1.26.8 | no; rebuild requested in frp#5591 |
| Kopia | 0.23.1 (16 Jun) | go1.25.11 | no; Go 1.25 has no fix; kopia#5768 |
| Stripe CLI | 1.55.6, 1.55.7 and 1.55.8 (30 Oct to 3 Nov) | go1.26.5 in each | no, though released after the fix; stripe-cli#2169 |
| yq | 4.54.1 (29 Sep) | go1.27.1 | no; 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):
| Setup | Unfixed binary (Go 1.26.9 and 1.27.2) | Fixed binary (Go 1.26.10 and 1.27.3) |
|---|---|---|
| Image as built | trusted 12 of 12 | rejected 12 of 12 |
ca-certificates upgraded in the image (20250419+deb13u1) | rejected 12 of 12 | rejected 12 of 12 |
SSL_CERT_FILE pointing at a fixed bundle, nothing else changed | trusted 12 of 12 | rejected 12 of 12 |
SSL_CERT_FILE and SSL_CERT_DIR both set (below) | rejected 12 of 12 | rejected 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:
| Image | Its CA package | Unfixed binary |
|---|---|---|
gcr.io/ (also static:latest) | 20250419+deb13u1 | rejected 12 of 12 |
gcr.io/ | 20250419~deb12u1 | trusted 12 of 12 |
alpine:latest (3.24) | 20261020-r0 | rejected 12 of 12 |
mikefarah/yq:4.54.1 (Alpine 3.24.2) | ca-certificates-bundle 20260909-r0 | trusted the 6 emSign hosts, rejected the 6 AAA hosts |
debian:bookworm-slim after apt-get install ca-certificates | 20250419~deb12u2 | rejected 12 of 12 |
ubuntu:24.04 after apt-get install ca-certificates | 20260816ubuntu0.24.04.1 | rejected 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-upgradeupgrades the package only if the image already has it.Alpine based. The Alpine base image installs only the
ca-certificates-bundlepackage, and so does the yq image. There,apk upgrade --no-cache ca-certificatesexited 0 and changed nothing; the binary still trusted the emSign hosts. Name the package that is installed:RUN apk upgrade --no-cache ca-certificates-bundleA full
apk upgrade --no-cache, orapk 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:nonrootandbase-debian12:latestall still held both roots on 3 November. Move to-debian13and 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 runuses a copy you already have without checking for a newer one, and analpine:latestcached before 20 October still holds the emSign root. Thechmodlines 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 firstopensslline 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-certsalready 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 inSSL_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-imageTo confirm that the bundle and both variables work, run a small Go client with the same
-vand-eflags. It usesgolang: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.orgprints TRUSTED and both eMudhra hosts print REJECTED withx509: certificate signed by unknown authority. Ifletsencrypt.orgis 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/nullFor 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-01With 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~deb12u2after 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,. 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.