Skip to content

fix(code): rebase Dockerfile bait so Module 4 fix is a single FROM bump#51

Merged
sanchezpaco merged 1 commit into
unicrons:mainfrom
sanchezpaco:fix/module-4-dockerfile-baseline
May 1, 2026
Merged

fix(code): rebase Dockerfile bait so Module 4 fix is a single FROM bump#51
sanchezpaco merged 1 commit into
unicrons:mainfrom
sanchezpaco:fix/module-4-dockerfile-baseline

Conversation

@sanchezpaco

Copy link
Copy Markdown
Collaborator

Problem

In Module 4 (Container Security Scanning) the planted base image is node:16.14.0-alpine. Trivy and Grype both fire on it correctly, but the fix path is unstable: any obvious bump leaves a residual HIGH that the attendee can't easily resolve.

Specifically:

Tried in dry-run Outcome
node:22-alpine / node:24-alpine / node:25-alpine (single bump) 1 HIGH on picomatch 4.0.3 bundled inside the npm shipped with each of those images (transitive of tinyglobby)
gcr.io/distroless/nodejs22-debian12 5 new findings, including a CRITICAL on libssl3
cgr.dev/chainguard/node clean, but a vendor-hosted rolling-release image is a strange recommendation for a public workshop

The picomatch finding only fully clears once npm itself is updated to >= 11.13.0 (where tinyglobby was bumped). And npm 11.x requires Node >= 20.17.

The dry-run report previously called this out as "the README's TIP nailed the trajectory but stops one image short" — meaning the attendee can spend a long time chasing base images instead of practicing the actual lesson (run scanner → see finding → fix → move on).

Fix

Rebaseline the Dockerfile so:

  • The base is node:20-alpine3.19 — Node 20.18.1 (compatible with npm 11.13.0) on alpine 3.19 (EOL since 2025-11). Both Trivy and Grype surface real findings (alpine OS package CVEs, musl, node binary CVEs). Grype additionally prints an explicit 188 packages from EOL distro "alpine 3.19.4" — vulnerability data may be incomplete or outdated notice, which is itself a teachable moment.
  • A permanent RUN npm install -g npm@11.13.0 && npm cache clean --force line is added. The attendee does not edit this line. Its job is to silently handle the npm/picomatch story so it does not become the lesson.

The attendee's fix in Module 4 becomes a single FROM line edit:

-FROM node:20-alpine3.19
+FROM node:25-alpine

After that, both scanners go to 0.

Verification

Empirical, end-to-end on a test PR using the same orchestrator workflow (with both Trivy and Grype configured to run in parallel against the same built image): https://github.com/sanchezpaco/secure-pipeline-workshop/pull/11

Stage Trivy Grype
Bait — FROM node:20-alpine3.19 4 HIGH (musl, node binary CVEs) 1 CRITICAL + many HIGH (node binary, musl, libssl3, libcrypto3) plus explicit alpine-EOL warning
Fix — FROM node:25-alpine 0 0

CI runs:

(The fix run also requires Module 3's process.env.* change to code/src/simple-app.js, since Trivy's secret scanner would otherwise flag the planted AKIA. In the workshop's natural module ordering, Module 3 is fixed before Module 4, so this composes cleanly. No change to the Trivy snippet is required.)

Why this matters didactically

The other module fixes in the workshop are one-line edits (pin an action in Module 1, change a value in Module 2, move secrets to env in Module 3, restrict a security group in Module 5). Module 4 was the exception: with the previous bait, the attendee either chased base images for three or four iterations or ended up using a vendor-specific rolling-release image. This change brings Module 4's fix path back in line with the rest.

Related

Independent from PRs #48, #49 (Module 1 fixes) and #50 (Module 2 fixes). Composes cleanly with all of them.

Module 4 (Container Security Scanning) currently uses 'node:16.14.0-alpine'
as the planted base image. Both Trivy and Grype legitimately fire on it,
but the fix path is unstable: bumping to 'node:22-alpine' or 'node:25-alpine'
leaves a residual HIGH (picomatch 4.0.3) bundled inside npm 11.12.x's
transitive 'tinyglobby' dependency. The fix only fully clears once npm is
upgraded to >= 11.13.0. The dry-run report previously chased this through
multi-stage / distroless / Chainguard images, none of which is a good
recommendation for an attendee.

This change rebaselines the bait so the workshop fix stays as a single
'FROM' line edit:

  bait: FROM node:20-alpine3.19   (alpine 3.19 — EOL, surfaces clean
                                    HIGH/CRITICAL findings on both Trivy
                                    and Grype)
  fix : FROM node:25-alpine       (alpine 3.23+, ships node 25 with the
                                    necessary npm baseline)

The 'RUN npm install -g npm@11.13.0' line is permanent (the user does not
add or remove it) so the picomatch issue is silently handled in both
states. npm 11.x requires Node >= 20.17, so the bait must already be on a
Node 20 line — 'node:20-alpine3.19' fits exactly while preserving useful
findings (the EOL alpine version surfaces real OS CVEs that the bump
clears).

Empirically verified end-to-end on a test PR
(https://github.com/sanchezpaco/secure-pipeline-workshop/pull/11):

  bait    Trivy 4 HIGH (musl, node)            Grype many HIGH + 1 CRITICAL
                                               + explicit 'alpine 3.19.4
                                               EOL — vulnerability data may
                                               be incomplete' notice
  fix     Trivy 0                              Grype 0 (No vulnerabilities
                                               found)
@sanchezpaco sanchezpaco merged commit b84c0eb into unicrons:main May 1, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants