From 5e7ffa5ae774f12ae0124739d8caec94917fdab3 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Tue, 18 Aug 2026 09:29:10 +0200 Subject: [PATCH] sec(build): bump the runtime base to alpine:3.24.1 for fixable OS advisories (#1842) The runtime stage was pinned to alpine:3.21.3, which carries 17 OS package advisories with a published fix: 2 CRITICAL and 15 HIGH across libcrypto3, libssl3, musl, musl-utils and zlib. The CRITICAL is CVE-2026-31789, a heap buffer overflow reachable from a large X.509 certificate, in a service that terminates TLS and connects to its database over TLS. The measurement uses --ignore-unfixed, so every one of the 17 had a fix available upstream and none of them was waiting on anything. The base is now alpine:3.24.1, pinned to the multi-arch index digest rather than a per-platform one so the pin stays correct for every TARGETARCH this image is built for. 3.21.7 also clears the fixable set and would have been the smaller move, but 3.24 is the current release line and 3.21 reaches end of support first, so this buys a longer runway for the same change. The digest was resolved three ways that agree: the Digest field from docker buildx imagetools inspect, the registry v2 Docker-Content-Digest header, and an independently recomputed SHA-256 over the raw index document. The third one matters because it hashes the content rather than trusting a value the server reports, and a genuine digest went stale mid-flight during #1835 when the upstream tag was re-pushed onto a rebuilt image. Measured on the built artifact, not the base tag, because a clean base does not prove a clean image: the runtime stage installs ca-certificates, postgresql-client, curl and tzdata on top of it. Building the identical Dockerfile on both bases and scanning the results gives 17 fixable CRITICAL and HIGH on alpine:3.21.3 and 0 on alpine:3.24.1, with /app/cudly and /usr/local/bin/migrate at 0 in both. The image scanner added by #1841 still passes on the rebuilt image, and its own self-test suite is 9 passed, 0 failed. Confirmed the runtime stage still provides what the binary and entrypoint.sh need, since a minor bump can move package names. psql, pg_dump, curl, sh, addgroup, adduser, ca-certificates and tzdata all resolve on both linux/amd64 and linux/arm64, and the multi-arch index covers the same eight platforms as before. End to end against a postgres container the image runs all 97 migrations, serves /health with HTTP 200, and Docker's own HEALTHCHECK reports healthy, which exercises the in-image curl and shell. Trivy scan-type: image is deliberately still not added. That is the next change and is tracked separately; adding it in the same commit as the bump would mean the gate and the fix it depends on land together with no independent evidence that the gate can fail. The stale justification in scripts/scan-shipped-image.sh, which cited alpine:3.21.3 as the reason the gate would land red, is updated to say the blocker is gone. Closes #1842 --- Dockerfile | 5 ++++- scripts/scan-shipped-image.sh | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6512386f0..8600ea228 100644 --- a/Dockerfile +++ b/Dockerfile @@ -117,7 +117,10 @@ RUN npm run build # Runtime stage - multi-arch base image # ============================================== # Image pinned to a SHA256 digest for reproducible builds. -FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c +# To refresh: `docker buildx imagetools inspect alpine:3.24.1` and update the +# digest below. This is the multi-arch index digest, not a per-platform one, so +# it stays correct for every TARGETARCH this image is built for. +FROM alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b # Re-declare args for use in this stage ARG TARGETARCH diff --git a/scripts/scan-shipped-image.sh b/scripts/scan-shipped-image.sh index 7f5df9cb4..25063f40b 100755 --- a/scripts/scan-shipped-image.sh +++ b/scripts/scan-shipped-image.sh @@ -39,9 +39,11 @@ # WHAT IT DOES NOT COVER. OS package CVEs in the base image (musl, openssl, # zlib, curl). govulncheck only knows about Go modules and the Go standard # library. A Trivy scan-type: image step would cover those; it is not added -# here because the pinned alpine:3.21.3 runtime base already carries fixable -# CRITICAL/HIGH openssl, musl and zlib advisories, so such a gate would land -# red. Tracked separately. +# here, and is tracked separately. The reason it was originally deferred is +# gone: the runtime base was alpine:3.21.3, which carried fixable CRITICAL/HIGH +# openssl, musl and zlib advisories that would have landed such a gate red on +# day one. The base is now alpine:3.24.1, measured clean of fixable +# CRITICAL/HIGH OS advisories, so adding that gate is unblocked. # # Exit 0 = every Go binary found, and no advisory with a published fix. # Exit 1 = at least one fixable advisory, or the enumeration came back empty.