Skip to content

sec(build): bump the runtime base to alpine:3.24.1 for fixable OS advisories - #1843

Merged
cristim merged 1 commit into
mainfrom
sec/1842-alpine-base-bump
Aug 18, 2026
Merged

cristim merged 1 commit into
mainfrom
sec/1842-alpine-base-bump

Conversation

@cristim

@cristim cristim commented Aug 18, 2026 •

Copy link
Copy Markdown
Member

What

The runtime stage was pinned to alpine:3.21.3, which ships 17 OS package advisories that all have 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 base is now alpine:3.24.1, pinned by digest.

Why 3.24.1

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. All four candidate lines were measured rather than assumed, because 3.23.5 and 3.22.5 were rebuilt on 2026-06-22, after 3.24.1 was published on 2026-06-16, so "latest" was not automatically the cleanest:

candidate fixable CRITICAL/HIGH
alpine:3.21.3 (current) 17 (2 CRITICAL, 15 HIGH)
alpine:3.21.7 0
alpine:3.22.5 0
alpine:3.23.5 0
alpine:3.24.1 (chosen) 0

The digest

sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b

This is the multi-arch OCI index digest, not a per-platform manifest digest, so the pin stays correct for every TARGETARCH this image is built for. The new index covers the same eight platforms as the old one (386, amd64, arm64v8, armv6, armv7, ppc64le, riscv64, s390x).

Resolved three ways that agree:

method value
docker buildx imagetools inspect Digest: sha256:28bd5fe8...943f8b
registry v2 Docker-Content-Digest header sha256:28bd5fe8...943f8b
recomputed SHA-256 over the raw index document sha256:28bd5fe8...943f8b

The third matters because it hashes the content rather than trusting a value the server reports. A genuine digest went stale mid-flight during #1835 when the upstream tag was re-pushed onto a rebuilt image, and the pin is what caught it.

Measured on the built artifact, not the base tag

A clean base does not prove a clean image: the runtime stage installs ca-certificates, postgresql-client, curl and tzdata on top of it. So the identical Dockerfile was built on both bases and the resulting images scanned.

Before, base tag: trivy image --severity CRITICAL,HIGH --ignore-unfixed --scanners vuln alpine:3.21.3

alpine:3.21.3 (alpine 3.21.3)
=============================
Total: 17 (HIGH: 15, CRITICAL: 2)

┌────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┬──────────────────────────────────────────────────────────────┐
│  Library   │ Vulnerability  │ Severity │ Status │ Installed Version │ Fixed Version │                            Title                             │
├────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤
│ libcrypto3 │ CVE-2026-31789 │ CRITICAL │ fixed  │ 3.3.3-r0          │ 3.3.7-r0      │ openssl: OpenSSL: Heap buffer overflow on 32-bit systems     │
│            │                │          │        │                   │               │ from large X.509 certificate...                              │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-31789                   │
│            ├────────────────┼──────────┤        │                   ├───────────────┼──────────────────────────────────────────────────────────────┤
│            │ CVE-2025-15467 │ HIGH     │        │                   │ 3.3.6-r0      │ openssl: OpenSSL: Remote code execution or Denial of Service │
│            │                │          │        │                   │               │ via oversized Initialization...                              │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2025-15467                   │
│            ├────────────────┤          │        │                   │               ├──────────────────────────────────────────────────────────────┤
│            │ CVE-2025-69421 │          │        │                   │               │ openssl: OpenSSL: Denial of Service via malformed PKCS#12    │
│            │                │          │        │                   │               │ file processing                                              │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2025-69421                   │
│            ├────────────────┤          │        │                   ├───────────────┼──────────────────────────────────────────────────────────────┤
│            │ CVE-2026-28387 │          │        │                   │ 3.3.7-r0      │ openssl: OpenSSL: Arbitrary code execution due to            │
│            │                │          │        │                   │               │ use-after-free in DANE TLSA authentication...                │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-28387                   │
│            ├────────────────┤          │        │                   │               ├──────────────────────────────────────────────────────────────┤
│            │ CVE-2026-28388 │          │        │                   │               │ openssl: OpenSSL: Denial of Service due to NULL pointer      │
│            │                │          │        │                   │               │ dereference in delta...                                      │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-28388                   │
│            ├────────────────┤          │        │                   │               ├──────────────────────────────────────────────────────────────┤
│            │ CVE-2026-28389 │          │        │                   │               │ openssl: OpenSSL: Denial of Service vulnerability in CMS     │
│            │                │          │        │                   │               │ processing                                                   │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-28389                   │
│            ├────────────────┤          │        │                   │               ├──────────────────────────────────────────────────────────────┤
│            │ CVE-2026-28390 │          │        │                   │               │ openssl: OpenSSL: Denial of Service due to NULL pointer      │
│            │                │          │        │                   │               │ dereference in CMS...                                        │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-28390                   │
├────────────┼────────────────┼──────────┤        │                   │               ├──────────────────────────────────────────────────────────────┤
│ libssl3    │ CVE-2026-31789 │ CRITICAL │        │                   │               │ openssl: OpenSSL: Heap buffer overflow on 32-bit systems     │
│            │                │          │        │                   │               │ from large X.509 certificate...                              │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-31789                   │
│            ├────────────────┼──────────┤        │                   ├───────────────┼──────────────────────────────────────────────────────────────┤
│            │ CVE-2025-15467 │ HIGH     │        │                   │ 3.3.6-r0      │ openssl: OpenSSL: Remote code execution or Denial of Service │
│            │                │          │        │                   │               │ via oversized Initialization...                              │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2025-15467                   │
│            ├────────────────┤          │        │                   │               ├──────────────────────────────────────────────────────────────┤
│            │ CVE-2025-69421 │          │        │                   │               │ openssl: OpenSSL: Denial of Service via malformed PKCS#12    │
│            │                │          │        │                   │               │ file processing                                              │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2025-69421                   │
│            ├────────────────┤          │        │                   ├───────────────┼──────────────────────────────────────────────────────────────┤
│            │ CVE-2026-28387 │          │        │                   │ 3.3.7-r0      │ openssl: OpenSSL: Arbitrary code execution due to            │
│            │                │          │        │                   │               │ use-after-free in DANE TLSA authentication...                │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-28387                   │
│            ├────────────────┤          │        │                   │               ├──────────────────────────────────────────────────────────────┤
│            │ CVE-2026-28388 │          │        │                   │               │ openssl: OpenSSL: Denial of Service due to NULL pointer      │
│            │                │          │        │                   │               │ dereference in delta...                                      │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-28388                   │
│            ├────────────────┤          │        │                   │               ├──────────────────────────────────────────────────────────────┤
│            │ CVE-2026-28389 │          │        │                   │               │ openssl: OpenSSL: Denial of Service vulnerability in CMS     │
│            │                │          │        │                   │               │ processing                                                   │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-28389                   │
│            ├────────────────┤          │        │                   │               ├──────────────────────────────────────────────────────────────┤
│            │ CVE-2026-28390 │          │        │                   │               │ openssl: OpenSSL: Denial of Service due to NULL pointer      │
│            │                │          │        │                   │               │ dereference in CMS...                                        │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-28390                   │
├────────────┼────────────────┤          │        ├───────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤
│ musl       │ CVE-2026-40200 │          │        │ 1.2.5-r9          │ 1.2.5-r11     │ musl: musl libc: Arbitrary code execution and denial of      │
│            │                │          │        │                   │               │ service via stack-based...                                   │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-40200                   │
├────────────┤                │          │        │                   │               │                                                              │
│ musl-utils │                │          │        │                   │               │                                                              │
│            │                │          │        │                   │               │                                                              │
│            │                │          │        │                   │               │                                                              │
├────────────┼────────────────┤          │        ├───────────────────┼───────────────┼──────────────────────────────────────────────────────────────┤
│ zlib       │ CVE-2026-22184 │          │        │ 1.3.1-r2          │ 1.3.2-r0      │ zlib: zlib: Arbitrary code execution via buffer overflow in  │
│            │                │          │        │                   │               │ untgz utility                                                │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2026-22184                   │
└────────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┴──────────────────────────────────────────────────────────────┘

After, base tag: same command against alpine:3.24.1


Report Summary

┌───────────────────────────────┬────────┬─────────────────┐
│            Target             │  Type  │ Vulnerabilities │
├───────────────────────────────┼────────┼─────────────────┤
│ alpine:3.24.1 (alpine 3.24.1) │ alpine │        0        │
└───────────────────────────────┴────────┴─────────────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)

Built artifact, old base (cudly:1842-oldbase)

Report Summary

┌────────────────────────────────────┬──────────┬─────────────────┐
│               Target               │   Type   │ Vulnerabilities │
├────────────────────────────────────┼──────────┼─────────────────┤
│ cudly:1842-oldbase (alpine 3.21.3) │  alpine  │       17        │
├────────────────────────────────────┼──────────┼─────────────────┤
│ app/cudly                          │ gobinary │        0        │
├────────────────────────────────────┼──────────┼─────────────────┤
│ usr/local/bin/migrate              │ gobinary │        0        │
└────────────────────────────────────┴──────────┴─────────────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)


Built artifact, new base (cudly:1842-amd64)

Report Summary

┌──────────────────────────────────┬──────────┬─────────────────┐
│              Target              │   Type   │ Vulnerabilities │
├──────────────────────────────────┼──────────┼─────────────────┤
│ cudly:1842-amd64 (alpine 3.24.1) │  alpine  │        0        │
├──────────────────────────────────┼──────────┼─────────────────┤
│ app/cudly                        │ gobinary │        0        │
├──────────────────────────────────┼──────────┼─────────────────┤
│ usr/local/bin/migrate            │ gobinary │        0        │
└──────────────────────────────────┴──────────┴─────────────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)

Both Go binaries (/app/cudly, /usr/local/bin/migrate) are at 0 on both bases, so the entire delta is OS packages, which is exactly the class #1841's govulncheck-based scanner does not cover.

The #1841 image scanner still passes

bash scripts/scan-shipped-image.sh cudly:1842-amd64 exits 0:

==> 2 Go binary/binaries in cudly:1842-amd64
  -> /app/cudly (built with go1.26.6)
    no fix   GO-2026-5932  in golang.org/x/crypto  (tolerated: no published fix)
    => /app/cudly: clean (1 advisory/advisories without a published fix)
  -> /usr/local/bin/migrate (built with go1.26.6)
    => /usr/local/bin/migrate: clean (0 advisory/advisories without a published fix)

OK: 2 Go binary/binaries scanned in cudly:1842-amd64; no advisory with a published fix.

Its own self-test suite (scripts/test-scan-shipped-image.sh) is 9 passed, 0 failed.

The application still starts

An Alpine minor bump can move package names, so this was checked rather than assumed. psql, pg_dump, curl, sh, addgroup, adduser, ca-certificates and tzdata all resolve on both linux/amd64 and linux/arm64.

End to end against a postgres:16-alpine container, the image:

  • ran all 97 migrations to completion (migrate v4.19.1, exercising psql/libpq and TLS libs),
  • served /health with HTTP 200,
  • and Docker's own HEALTHCHECK reported healthy, which exercises the in-image curl and /bin/sh.

hadolint v2.15.1 (the digest pinned in .pre-commit-config.yaml) is clean on the modified Dockerfile, and shellcheck is clean on the modified script.

Scope

This is the base bump only. Trivy scan-type: image is deliberately not added here, and remains tracked separately. Adding the gate in the same commit as the fix it depends on would mean both land together with no independent evidence that the gate can actually fail. Ordering is bump first, confirm clean, then gate.

The stale justification in scripts/scan-shipped-image.sh, which cited alpine:3.21.3 as the reason an image gate would land red, is updated in this PR to record that the blocker is gone.

Pin sites checked

Searched with a loose pattern (alpine:?3, FROM[[:space:]]+alpine) across the whole repo and classified every hit, rather than a targeted one:

site classification
Dockerfile:123 FROM alpine:3.21.3@sha256:... the runtime base, changed
scripts/scan-shipped-image.sh:42 stale comment citing the old base, updated
Dockerfile:18 golang:1.26.6-alpine3.24 builder stage, does not ship
Dockerfile:94 node:24-alpine frontend builder stage, does not ship
Dockerfile.dev:8, Dockerfile.test:8 dev/test images, never shipped
docker-compose*.yml, internal/**/postgres.go postgres:16-alpine, nginx:alpine local dev and test fixtures, not the runtime base
.github/runbooks/compromised-dependency.md:74,77 illustrative example text, not a live pin
.hadolint.yaml prose about Alpine, no version pin

Dockerfile is the only file in the repo with a runtime FROM alpine.

Note on this issue's state

This issue was closed automatically on 2026-08-18T06:16:22Z by the merge of #1841, whose body contains a sentence of the form "This PR does not resolve" immediately followed by a reference to this issue number. GitHub's closing-keyword parser matches the keyword and ignores the negation, so the issue was closed despite the text saying the opposite. Issue #1837 hit the same thing earlier in this run.

That sentence is deliberately paraphrased rather than quoted here, because quoting it verbatim would re-trigger the same parser from this PR body. It has been reopened, and this PR closes it for real.

Refs #1836, #1841.

Closes #1842

Summary by CodeRabbit

  • Security

    • Updated the runtime environment to a newer Alpine Linux release.
    • Resolved fixable critical and high-severity operating-system package vulnerabilities in the shipped image.
  • Maintenance

    • Updated image refresh guidance to reflect the new multi-architecture runtime image.

…isories (#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
@cristim cristim added priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/all-users Affects every user effort/s Hours type/security Security finding triaged Item has been triaged labels Aug 18, 2026
@coderabbitai

coderabbitai Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b8845c1-f6be-44db-926e-a1891cba5c8c

📥 Commits

Reviewing files that changed from the base of the PR and between 6a2117c and 5e7ffa5.

📒 Files selected for processing (2)
  • Dockerfile
  • scripts/scan-shipped-image.sh

Included review availability: 1 review is currently available. Based on recent review activity, included reviews refill at 2 per hour.


📝 Walkthrough

Walkthrough

The runtime stage now uses Alpine 3.24.1 with a refreshed pinned multi-architecture digest. The shipped-image scan documentation records that fixable CRITICAL and HIGH OS-package advisories are cleared.

Changes

Runtime base refresh

Layer / File(s) Summary
Base image and scan guidance
Dockerfile, scripts/scan-shipped-image.sh
The runtime image uses Alpine 3.24.1 with updated digest instructions. The scan documentation records the absence of fixable CRITICAL and HIGH advisories.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Merge Risk: 🟡 Moderate · up to 5e7ff

This PR changes the runtime image and its security-scan claim. Merge readiness depends on confirming that the rebuilt image passes integration tests and that the final shipped image is free of the claimed fixable OS advisories; until those checks are completed or explicitly accepted, merge should wait.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR satisfies the base-image, digest, and vulnerability objectives, but it does not add the required Trivy image gate from [#1842]. Add the gating Trivy image scan and verify that it fails on the known-bad base and passes on the updated image.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the runtime Alpine base image upgrade and its security objective.
Out of Scope Changes check ✅ Passed The Dockerfile update and scanner documentation change directly support the linked issue objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sec/1842-alpine-base-bump

Comment @coderabbitai help to get the list of available commands.

@cristim
cristim merged commit df05daa into main Aug 18, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/security Security finding urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant