Skip to content

sec(build): runtime base alpine:3.21.3 ships fixable CRITICAL/HIGH OS advisories, blocking an image-level Trivy gate #1842

Description

@cristim

Surfaced while implementing #1836 (scan the shipped image). Deliberately kept out of that PR, and filed here so it is not lost.

What

The pinned runtime base alpine:3.21.3 carries fixable CRITICAL and HIGH OS-package advisories today. Measured directly:

$ trivy image --severity CRITICAL,HIGH --ignore-unfixed --scanners vuln alpine:3.21.3
libssl3     CVE-2026-31789  CRITICAL  heap buffer overflow from large X.509 certificate
libssl3     CVE-2025-15467  HIGH      -> 3.3.6-r0
libssl3     CVE-2026-28387  HIGH      -> 3.3.7-r0  (+ CVE-2025-69421, CVE-2026-28388/9/90)
musl        CVE-2026-40200  HIGH      1.2.5-r9 -> 1.2.5-r11
zlib        CVE-2026-22184  HIGH      1.3.1-r2 -> 1.3.2-r0

--ignore-unfixed is in that command, so every one of these has a published fix available.

The libssl3 CRITICAL is the notable one: a heap buffer overflow triggered by a large X.509 certificate, in a service that terminates TLS and connects to databases over TLS.

Why it is filed separately

#1836 adds govulncheck -mode=binary over the Go binaries in the shipped image, which catches the class that actually bit us twice: a stale Go toolchain (#1833) and a prebuilt third-party binary (#1835). It does not cover OS packages.

Trivy scan-type: image would cover them, and was deliberately not added in #1836 for a specific reason: gating on it would land red on day one for a problem that PR does not fix, and a non-gating version would print an unactionable wall on every PR, which is the "looks like coverage" pattern #1836 exists to avoid.

So the ordering is: bump the base, then add the OS-package scan. Doing it the other way round produces a red or ignored check.

Fix direction

  1. Bump the runtime base to a current alpine:3.x patch release that clears the fixable set above, re-pinning by digest. Never drop the @sha256: pin to make the bump easy, and never guess a digest (a real digest went stale mid-flight during sec(build): build the shipped image on go1.26.6 to clear the stdlib CVEs #1835 because the upstream tag was re-pushed; the pin is what caught it).
  2. Re-measure with the same command and confirm the fixable set is empty.
  3. Then add Trivy scan-type: image to CI, gating, alongside the govulncheck binary scan from sec(ci): nothing scans the container image we ship, only source; a baked-in vulnerable binary is invisible by construction #1836.

Verification bar

Both directions. Prove the new scan fails on a known-bad image (for example the current alpine:3.21.3 base) and passes on the bumped one. A scan step that always passes is worse than none, because it reads as coverage.

Allowlist on success: a check written as != "failure" reports success for cancelled and skipped.

Related: #1836 (Go binary scanning in the image), #1833 and #1835 (the two rounds this class of gap hid).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions