fix(ci): pass trivy image scans
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 15s
CI / build-images (push) Successful in 3m22s
CI / test (push) Successful in 26s
CI / lint (push) Successful in 28s
CI / push (push) Has been skipped
CI / image-scan (push) Failing after 16s

Three independent issues caused 35 + 15 HIGH/CRITICAL findings:

1. Client base — nginx:1.27-alpine pulled Alpine 3.21.3, which
   carries old libcrypto3/libssl3/libpng/libxml2/musl/nghttp2/zlib.
   Bumped to nginx:1.29-alpine and added `apk update && apk
   upgrade --no-cache` so the layer pulls every available patch
   in the current Alpine repo. Verified: 0 findings.

2. Server runtime — npm/yarn/corepack ship bundled inside the
   node:20-alpine image at /usr/local/lib/node_modules/npm/...
   They drag in cross-spawn, glob, minimatch, tar, and friends —
   all of which had HIGH CVEs. We never invoke them at runtime
   (CMD is `node dist/main.js`); deleting them in the runtime
   stage cuts 11 of the 15 findings. Also added `apk upgrade`
   for parity with the client.

3. Multer transitive — @nestjs/platform-express pulls in
   multer 2.0.2 which has three HIGH CVEs (fixed in 2.1.1).
   Added a pnpm `overrides` entry to pin it.

For the remaining lodash CVE-2026-4800: Trivy lists 4.18.0 as
the fixed version, but that release isn't published on npm. Added
.trivyignore + wired --ignorefile into the trivy invocations,
with a comment explaining why and when to re-evaluate.

Verification (local builds + trivy scans):
- teh-riehl-server: 0 HIGH/CRITICAL across all targets.
- teh-riehl-client: 0 vulnerabilities (alpine 3.23.4).
- Server boots, /api/health returns ok.
- Client serves /config.js with APP_API_BASE_URL override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 16:54:28 -07:00
parent d31b2c6dc7
commit efd073ebe1
6 changed files with 51 additions and 23 deletions
+6
View File
@@ -276,9 +276,11 @@ jobs:
# failure is visible inline (the SARIF run below writes only to file).
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD:/src" \
aquasec/trivy:latest image \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--ignorefile /src/.trivyignore \
--format table \
tehriehlincremental-server:${SHA_SHORT} || true
# Real gate + SARIF artifact.
@@ -289,6 +291,7 @@ jobs:
--severity HIGH,CRITICAL \
--exit-code 1 \
--ignore-unfixed \
--ignorefile /src/.trivyignore \
--format sarif --output /src/trivy-image-server.sarif \
tehriehlincremental-server:${SHA_SHORT}
@@ -297,9 +300,11 @@ jobs:
run: |
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD:/src" \
aquasec/trivy:latest image \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--ignorefile /src/.trivyignore \
--format table \
tehriehlincremental-client:${SHA_SHORT} || true
docker run --rm \
@@ -309,6 +314,7 @@ jobs:
--severity HIGH,CRITICAL \
--exit-code 1 \
--ignore-unfixed \
--ignorefile /src/.trivyignore \
--format sarif --output /src/trivy-image-client.sarif \
tehriehlincremental-client:${SHA_SHORT}
+11
View File
@@ -0,0 +1,11 @@
# Trivy CVE allowlist.
# Format: one CVE ID per line, comments allowed. Used by both fs and image scans.
#
# Each entry below MUST cite why we can't simply upgrade. Re-evaluate quarterly
# (or whenever the fixed version actually publishes to npm).
# lodash CVE-2026-4800: Trivy lists 4.18.0 as the fixed version, but that
# release does not exist on npm (latest stable is still 4.17.21). lodash is
# pulled in transitively via @nestjs's dependency chain; nothing in our code
# imports it directly. Re-check when 4.18.0 is actually published.
CVE-2026-4800
+5
View File
@@ -25,5 +25,10 @@
"prettier": "^3.3.3",
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.1"
},
"pnpm": {
"overrides": {
"multer": "^2.1.1"
}
}
}
+7 -1
View File
@@ -33,7 +33,13 @@ COPY packages/client packages/client
RUN pnpm --filter @teh-riehl/client build
# ---- Runtime ----------------------------------------------------------------
FROM nginx:1.27-alpine AS runtime
FROM nginx:1.29-alpine AS runtime
# Pull the latest patched system packages BEFORE copying app content, so the
# layer cache for our content stays small and we only re-fetch upgrades when
# the base image moves. Knocks out the libcrypto/libpng/libxml/musl/nghttp2
# /zlib HIGH+CRITICAL vulns that ship in the unpatched base.
RUN apk update && apk upgrade --no-cache && rm -rf /var/cache/apk/*
# Static SPA + custom nginx config + the runtime-config entrypoint.
COPY --from=builder /repo/packages/client/dist /usr/share/nginx/html
+15 -1
View File
@@ -57,7 +57,21 @@ RUN set -eux; \
# ---- Runtime ----------------------------------------------------------------
FROM node:20-alpine AS runtime
RUN apk add --no-cache openssl tini
# Patch the base image, install runtime-only system deps, and strip out the
# npm/yarn/corepack CLI bundles. We never invoke any of those at runtime
# (CMD is `node dist/main.js`) and their bundled deps (cross-spawn, glob,
# minimatch, tar, etc.) drag in a long list of HIGH-severity CVEs that
# would fail the image scan.
RUN apk update && apk upgrade --no-cache && \
apk add --no-cache openssl tini && \
rm -rf /usr/local/lib/node_modules/npm \
/usr/local/lib/node_modules/corepack \
/usr/local/bin/npm \
/usr/local/bin/npx \
/usr/local/bin/corepack \
/opt/yarn-v* \
/var/cache/apk/*
WORKDIR /app
COPY --from=builder /deploy/node_modules ./node_modules
+7 -21
View File
@@ -4,6 +4,9 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
multer: ^2.1.1
importers:
.:
@@ -2855,18 +2858,14 @@ packages:
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
multer@2.0.2:
resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==}
multer@2.1.1:
resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==}
engines: {node: '>= 10.16.0'}
mute-stream@0.0.8:
@@ -3925,10 +3924,6 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -4427,7 +4422,7 @@ snapshots:
body-parser: 1.20.4
cors: 2.8.5
express: 4.22.1
multer: 2.0.2
multer: 2.1.1
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
@@ -6628,23 +6623,16 @@ snapshots:
minipass@7.1.3: {}
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8
ms@2.0.0: {}
ms@2.1.3: {}
multer@2.0.2:
multer@2.1.1:
dependencies:
append-field: 1.0.0
busboy: 1.6.0
concat-stream: 2.0.0
mkdirp: 0.5.6
object-assign: 4.1.1
type-is: 1.6.18
xtend: 4.0.2
mute-stream@0.0.8: {}
@@ -7680,8 +7668,6 @@ snapshots:
xmlchars@2.2.0: {}
xtend@4.0.2: {}
yallist@3.1.1: {}
yargs-parser@21.1.1: {}