From eb03b612688f0b06d9770e1675961f5405a29b35 Mon Sep 17 00:00:00 2001 From: Paul Nothaft <53005142+the-luap@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:29:31 +0200 Subject: [PATCH] =?UTF-8?q?chore(security):=20close=2021=20frontend=20imag?= =?UTF-8?q?e=20CVEs=20=E2=80=94=20nginx=201.30=20base=20+=20apk=20cache-bu?= =?UTF-8?q?st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend image kept shipping vulnerable OS packages (nginx 1.28.3-r1, curl/libcurl 8.19.0, c-ares 1.34.6) despite the apk upgrade line, for two independent reasons: 1. The runtime stage's apk upgrade layer was cached indefinitely — the CACHEBUST build-arg CI passes (github.run_number) was only declared in the builder stage, and ARGs don't cross stage boundaries. Both Dockerfiles now redeclare CACHEBUST in the runtime stage and consume it in the apk RUN, so every build re-runs the upgrade and picks up current Alpine security updates. 2. nginx itself can never upgrade via apk on the nginx.org-based image: the bundled nginx-module-* packages pin the exact nginx version, so Alpine's patched 1.28.3-r4 is unreachable (verified empirically — apk add --upgrade nginx is a silent no-op). nginx fixes must come via the base tag, so bump to nginx:1.30-alpine (current stable, 1.30.4 on Alpine 3.24, same nginx.org conf.d layout — drop-in). Verified: local image build scans clean with Trivy (0 OS findings, was 21); container serves /health, SPA fallback, and BRAND_TITLE envsubst as non-root nginx user. Closes code-scanning alerts 371-374, 376-392 (nginx HTTP/2 & module CVEs, curl CVE-2026-5773/-6276 + 6 medium, c-ares CVE-2026-33630). --- backend/Dockerfile | 9 ++++++++- frontend/Dockerfile | 24 +++++++++++++++++------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index e89058db..688820ed 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -27,8 +27,15 @@ FROM node:22-alpine WORKDIR /app +# Redeclare CACHEBUST — ARGs don't cross stage boundaries, so the builder +# stage's declaration never reached this stage. Consuming it in the RUN below +# busts that layer's cache every CI run (CACHEBUST=github.run_number), so the +# image always picks up current Alpine security updates instead of reusing a +# stale cached upgrade layer. +ARG CACHEBUST=1 + # Upgrade all packages to fix security vulnerabilities (OpenSSL, libexpat, BusyBox CVEs) -RUN apk upgrade --no-cache +RUN echo "cachebust=${CACHEBUST}" && apk upgrade --no-cache # Upgrade the npm CLI in the final image so its bundled deps are patched # (sigstore 4.x, tar) — closes CVE-2026-48815 and the older @sigstore/core / tar diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 586e4754..eed1130d 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -29,14 +29,24 @@ COPY . . # Build the application RUN npm run build -# Production stage (Alpine 3.23 with OpenSSL 3.5.5, patched libexpat) -FROM nginx:1.28-alpine +# Production stage (nginx stable 1.30 on Alpine 3.24). The 1.28 base is a +# dead end for the nginx HTTP/2 + rewrite/charset CVEs (CVE-2026-42055 / +# -49975 / -9256 / -48142): nginx.org's nginx-module-* packages pin the exact +# nginx version, so `apk upgrade` can never pull Alpine's patched 1.28.3-r4 — +# nginx fixes have to come via the base image tag, not apk. +FROM nginx:1.30-alpine -# Upgrade all Alpine packages for security fixes. The explicit nginx upgrade -# closes the HTTP/2 + rewrite/charset CVEs (CVE-2026-42055 / -49975 / -9256 / -# -48142, fixed in nginx 1.28.3-r4) and busts any cached layer still carrying -# the vulnerable r1 build. -RUN apk upgrade --no-cache && apk add --no-cache --upgrade nginx +# Redeclare CACHEBUST — ARGs don't cross stage boundaries, so the builder +# stage's declaration never reached this stage. Consuming it in the RUN below +# busts that layer's cache every CI run (CACHEBUST=github.run_number), so the +# image always picks up current Alpine security updates. Without this, the +# upgrade layer was cached indefinitely and builds kept shipping curl 8.19.0 / +# c-ares 1.34.6 for weeks after fixed packages landed in the Alpine repo. +ARG CACHEBUST=1 + +# Upgrade all Alpine packages for security fixes (nginx itself is version- +# pinned by its module packages — see the FROM comment above). +RUN echo "cachebust=${CACHEBUST}" && apk upgrade --no-cache # Install runtime dependencies. `gettext` provides envsubst, used by # docker-entrypoint.sh for the BRAND_TITLE / BRAND_DESCRIPTION runtime