fix(image): the production image ships npm, and npm is where its CVEs are - #349
Merged
Conversation
… are
ui#347 made docker-scan run on a release PR instead of only after the tag. Its
first run on the v0.4.0 candidate reported five vulnerabilities that were not on
main's baseline — all five in `tar 7.5.16`, which is not in package-lock.json at
all. It comes from npm's bundled dependency tree inside the node:26-alpine base
image.
The production stage runs `node server.js` on the Next standalone output and
never invokes npm or npx. So the package manager is pure attack surface in the
artifact we publish, and it is also the artifact's largest source of scan noise:
Trivy on the built image goes from 6 HIGH/CRITICAL to 1 when npm is removed. The
five cleared are tar x2, brace-expansion x2 and undici — none reachable from
anything this image executes, all of them counted against us.
The one that remains is sharp, which is pre-existing on main, unreachable today
(no images: block in next.config.ts, next/image imported in zero files, /public
holds five SVGs which bypass sharp entirely) and needs an overrides entry rather
than a Dockerfile change.
Also: the same scan flags two CRITICAL "Google (GCP) Service-account" secrets in
the shipped JS chunks. Both are one Textarea placeholder,
`{"type": "service_account", ...}`, telling the user what shape to paste.
Replaced with an instruction rather than a truncated JSON sample — clearer copy,
and a secret scanner should not have to be taught to ignore our own UI hints. A
secret alert that is always false is one nobody reads the day it is true.
Verified against a real build: baseline image has npm's tar 7.5.16, hardened
image has no npm and no npx on PATH, and it serves — /, /login and /api/health
all 200 from a container run off the hardened image.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What found this
ui#347 made
docker-scanrun on a release PR rather than only after the tagthat publishes the image. On its first run it earned its keep: the v0.4.0
candidate image reported five vulnerabilities that were not on main's baseline.
All five are
tar 7.5.16.taris not inpackage-lock.json:It comes from npm's own bundled dependency tree inside
node:26-alpine.The production stage does not use npm
Next standalone output, started with
node. Nonpm, nonpx, at build timeor runtime, in this stage. The package manager is dead weight in the artifact we
publish — and it is where nearly all of that artifact's reported risk lives.
Measured, on real builds of both
Trivy
--severity HIGH,CRITICALagainst the two images:Cleared:
CVE-2026-59873,CVE-2026-59874(tar),CVE-2026-13149,CVE-2026-14257(brace-expansion),CVE-2026-12151(undici). Newlyintroduced: none.
The remaining one is
sharp 0.34.5, which is pre-existing on main, unreachabletoday (no
images:block innext.config.ts,next/imageimported in zerofiles,
/publicholds five SVGs which bypass sharp), and needs anoverridesentry rather than a Dockerfile change — see ui#344.
It still runs
and
npm/npxare gone from the image:The other thing that scan found
Two CRITICAL "Google (GCP) Service-account" secret detections in the shipped
JS chunks:
It is one textarea placeholder in
edit-credential-dialog.tsx—{"type": "service_account", ...}— telling the user what shape of JSON topaste. No secret material, and it has been alerting since 2026-06-08.
Replaced with
Paste the full service-account key JSON. That is better copyanyway (an instruction beats a truncated sample), and it means the scanner does
not need a suppression for our own UI hint. A secret alert that is always
false is the one nobody reads on the day it is true — which matters more here
than in most codebases.
Checked there is no second copy:
grep -rn '"type": *"service_account"' src/returns nothing else.
Verified
npm,npxpresent in imagetsc --noEmitvitest run