fix(security): patch HIGH/CRITICAL container image vulnerabilities #81

Merged
eliott merged 1 commit from security/2026-10-image-patching into main 2026-10-28 09:30:45 +01:00
Owner

Why

The vulan/Grype scan of the running rollnote-front container found 6 HIGH/CRITICAL findings, all Alpine packages in the nginx image:

  • brotli-libs 1.2.0-r1 (critical)
  • curl / libcurl 8.22.0
  • libexpat 2.8.5
  • libx11 1.8.13
  • zlib 1.3.2-r0

What changed (Dockerfile only)

  • NGINX_IMAGE nginxinc/nginx-unprivileged:1.31.6-alpine → 1.31.7-alpine, the latest tag. This fixes libexpat and libx11.
  • The latest upstream tag still ships Alpine 3.24.2 packages (zlib r0, curl 8.22, brotli r1). The final stage now runs apk upgrade --no-cache as root and then switches back to USER 101, the image's own unprivileged user. Runtime behaviour is unchanged: the container still runs as uid 101 on 8080 with a read-only rootfs.
  • Build-stage NODE_IMAGE node:24.21-alpine → node:24.22-alpine, in sync with the API. This doesn't affect the final image.

No npm dependency changes; package.json version unchanged (1.2.0, still matches the API changelog check).

Scan: before → after

before (prod container) after (local podman build)
Grype --only-fixed, HIGH/CRITICAL 6 0
Grype --only-fixed, any severity — 0

Verification (local)

  • pnpm lint, pnpm format:check, pnpm typecheck: pass.
  • pnpm test: 135 files, 1038 passed, 2 skipped.
  • Production ng build (inside the image build): OK.
  • podman build: OK.
  • Smoke run with read-only rootfs, --cap-drop ALL and no-new-privileges: nginx starts as uid 101, and / returns 200.
  • Not run locally: Playwright e2e.
  • Docker Hub rate-limited me, so the local build used the identical tag from public.ecr.aws/nginx/nginx-unprivileged.

What to check after deploy

  • Healthcheck green, and the app loads at app.roll-note.com. The CSP headers are unchanged, but check there are no console CSP errors.
  • Coolify's build cache can keep the apk upgrade layer from an earlier build. If a later scan still flags Alpine packages, rebuild without cache.

Follow-ups

  • Once nginx-unprivileged publishes a tag rebuilt on Alpine 3.24.3+, the apk upgrade step can stay as a safety net or be dropped.

https://claude.ai/code/session_011Qbv9zPFHL8Ax1zQWXSov3

## Why The vulan/Grype scan of the running `rollnote-front` container found **6 HIGH/CRITICAL findings**, all Alpine packages in the nginx image: - `brotli-libs` 1.2.0-r1 (critical) - `curl` / `libcurl` 8.22.0 - `libexpat` 2.8.5 - `libx11` 1.8.13 - `zlib` 1.3.2-r0 ## What changed (Dockerfile only) - `NGINX_IMAGE` `nginxinc/nginx-unprivileged:1.31.6-alpine` → `1.31.7-alpine`, the latest tag. This fixes libexpat and libx11. - The latest upstream tag still ships Alpine 3.24.2 packages (zlib r0, curl 8.22, brotli r1). The final stage now runs `apk upgrade --no-cache` as root and then switches back to `USER 101`, the image's own unprivileged user. Runtime behaviour is unchanged: the container still runs as uid 101 on 8080 with a read-only rootfs. - Build-stage `NODE_IMAGE` `node:24.21-alpine` → `node:24.22-alpine`, in sync with the API. This doesn't affect the final image. No npm dependency changes; `package.json` version unchanged (1.2.0, still matches the API changelog check). ## Scan: before → after | | before (prod container) | after (local `podman build`) | |---|---|---| | Grype `--only-fixed`, HIGH/CRITICAL | 6 | **0** | | Grype `--only-fixed`, any severity | — | 0 | ## Verification (local) - `pnpm lint`, `pnpm format:check`, `pnpm typecheck`: pass. - `pnpm test`: 135 files, 1038 passed, 2 skipped. - Production `ng build` (inside the image build): OK. - `podman build`: OK. - Smoke run with read-only rootfs, `--cap-drop ALL` and `no-new-privileges`: nginx starts as uid 101, and `/` returns 200. - Not run locally: Playwright e2e. - Docker Hub rate-limited me, so the local build used the identical tag from `public.ecr.aws/nginx/nginx-unprivileged`. ## What to check after deploy - Healthcheck green, and the app loads at app.roll-note.com. The CSP headers are unchanged, but check there are no console CSP errors. - Coolify's build cache can keep the `apk upgrade` layer from an earlier build. If a later scan still flags Alpine packages, rebuild without cache. ## Follow-ups - Once nginx-unprivileged publishes a tag rebuilt on Alpine 3.24.3+, the `apk upgrade` step can stay as a safety net or be dropped. https://claude.ai/code/session_011Qbv9zPFHL8Ax1zQWXSov3
- nginx-unprivileged 1.31.6-alpine -> 1.31.7-alpine (libexpat, libx11).
- Run `apk upgrade` in the final stage: the latest upstream tag still
  ships Alpine 3.24.2 packages (zlib 1.3.2-r0, curl/libcurl 8.22.0,
  brotli-libs 1.2.0-r1); switch to root for it, then back to uid 101.
- Build-stage Node image 24.21-alpine -> 24.22-alpine, in sync with the API.

Grype (--only-fixed) on the built image: 6 findings -> 0.

Claude-Session: https://claude.ai/code/session_011Qbv9zPFHL8Ax1zQWXSov3
eliott merged commit dae5641edd into main 2026-10-28 09:30:45 +01:00
eliott deleted branch security/2026-10-image-patching 2026-10-28 09:30:45 +01:00
Sign in to join this conversation.
No reviewers
No labels
run-ci
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
RollNote/rollnote-front!81
No description provided.