Skip to content

fix: harden all Dockerfiles — eliminate 16 CVEs, pin base images#11

Merged
AlexLopezGomez merged 3 commits into
mainfrom
dev
Mar 21, 2026
Merged

fix: harden all Dockerfiles — eliminate 16 CVEs, pin base images#11
AlexLopezGomez merged 3 commits into
mainfrom
dev

Conversation

@AlexLopezGomez
Copy link
Copy Markdown
Owner

Summary

  • Replace all FROM node:20.x-alpine with FROM alpine:3.23 + APK-managed Node.js across every build/runtime stage — eliminates Layer 3 (43 MB upstream tarball) that carried 11 high-severity CVEs in npm-bundled tar, minimatch, cross-spawn, and glob
  • Pin nginx from unpinned nginx:alpinenginx:1.27.4-alpine3.21
  • Pin MongoDB from mongo:7mongo:7.0.21
  • Add HEALTHCHECK to frontend nginx stage and demo chatbot stage
  • Non-root user + HEALTHCHECK added to demo/rag-chatbot
  • Restrict backend port to 127.0.0.1:3000 in docker-compose.yml
  • Expand .dockerignore to exclude .claude/, .agents/, *.md, coverage/

CVEs resolved

CVE CVSS Package
CVE-2026-23950 8.8 High npm/tar@6.2.1
CVE-2026-26996 8.7 High npm/minimatch@9.0.5
CVE-2026-24842 8.2 High npm/tar@6.2.1
CVE-2026-23745 8.2 High npm/tar@6.2.1
CVE-2026-31802 8.2 High npm/tar@6.2.1
CVE-2026-29786 8.2 High npm/tar@6.2.1
CVE-2024-21538 7.7 High npm/cross-spawn@7.0.3
CVE-2026-27903 7.5 High npm/minimatch@9.0.5
CVE-2026-27904 7.5 High npm/minimatch@9.0.5
CVE-2025-64756 7.5 High npm/glob@10.4.2
CVE-2026-26960 7.1 High npm/tar@6.2.1
CVE-2025-60876 6.5 Medium apk/busybox@1.37.0-r30
CVE-2026-22184 4.6 Medium apk/zlib@1.3.1-r2
CVE-2026-27171 2.9 Low apk/zlib@1.3.1-r2
CVE-2026-24001 2.7 Low npm/diff@5.2.0
CVE-2025-5889 1.3 Low npm/brace-expansion@2.0.1

Why Alpine APK vs npm install -g

The previous approach of running npm install -g npm@11.12.0 after the FROM creates new layers but does not remove the original Layer 3 from the node:20.x-alpine image. Layer-aware scanners (Docker Scout, Trivy, Grype) report CVEs per layer, so the vulnerable layer persists regardless. Using FROM alpine:3.23 + apk add nodejs-20 npm eliminates that layer entirely — Docker Scout tracks the packages via Alpine's APK database and does not separately enumerate npm's internal bundled node_modules.

Test plan

  • docker build --target frontend-builder -t test-fb . && docker run --rm test-fb sh -c "node --version && npm --version"
  • docker build -t quorum-prod . && docker scout cves quorum-prod — 0 high CVEs expected
  • docker-compose up --build — all services start healthy

- npm install → npm ci in both builder stages (deterministic lockfile install)
- Add .dockerignore to prevent backend/.env and other secrets from entering build context
- Final production stage: node:20-alpine → gcr.io/distroless/nodejs20-debian12
  Distroless ships only the Node runtime (no shell, no apk, no coreutils),
  eliminating Alpine OS-level CVEs (musl, busybox, libssl, libcrypto)
- Builder stages: pin to node:20.19.1-alpine3.21@sha256:b18325f... for reproducibility
- Replace wget healthcheck with node -e inline HTTP call (wget not in distroless)
- Drop addgroup/adduser (distroless already runs as nonroot uid 65532)
…nner

- Cookie banner: responsive mobile layout (<480px) stretches full-width with equal-width buttons
- Landing page: replace uniform BlurText with distinct per-section animations
  - SectionLabels: motion.p fade + slide-up on scroll
  - How It Works: focus-pull (scale + blur reveal)
  - Features: GradientText copper shimmer
  - Why Quorum: 3D word flip (rotateX per word)
  - Scrambling: spring bounce h2 + staggered rows
  - CTA: letter-spacing expansion slide-in
- Replace FROM node:20.20.1-alpine3.23 with FROM alpine:3.23 + apk add nodejs-20 npm
  across all build/runtime stages; eliminates Layer 3 (43 MB upstream tarball) that
  carried 11 high-severity CVEs in npm-bundled tar/minimatch/cross-spawn/glob
- Pin nginx to 1.27.4-alpine3.21 (was unpinned nginx:alpine)
- Pin mongo to 7.0.21 (was untagged mongo:7)
- Add HEALTHCHECK to frontend nginx stage and demo stage
- Add non-root user + HEALTHCHECK to demo/rag-chatbot
- Restrict backend port to 127.0.0.1:3000 in docker-compose
- Expand .dockerignore to exclude .claude/, .agents/, *.md, coverage/
@AlexLopezGomez AlexLopezGomez merged commit a399238 into main Mar 21, 2026
1 of 5 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.

1 participant