TL;DR
package.json 升完,不代表 Docker image 就乾淨了。Node.js image 的弱掃結果,通常至少要先分成兩類來看:專案自己的套件,以及 base image 內建 npm 帶進來的套件。這次 image 被掃到的 glob、minimatch、tar、diff,最後不是 app 的 node_modules 沒升乾淨,而是 node:22-alpine 內建的 npm@10.9.4 自己帶了舊版相依套件。解法不是只改 package.json,而是把 image 裡的 npm 升到 11.11.1。
先講結論
遇到 Node.js image 弱掃,不要先急著懷疑 lockfile 沒更新。先做這個判斷:
- 先查 app 依賴樹
- 再查 container 內 npm 的全域依賴樹
- 最後再決定要改
package.json/ lockfile,還是改Dockerfile
這樣可以很快分清楚:你修的是應用程式套件問題,還是 base image 工具鏈問題。
情境
這次在整理 Node.js 專案的弱掃時,先把 package.json 跟 yarn.lock 裡能升的套件都升掉了,包含 runtime dependency 和 dev dependency。
專案側看起來已經很乾淨:
package.json已升到較新的依賴版本yarn.lock也重新生成- 本地依賴樹裡沒有看到掃描報告列出的那些舊版
diff、glob、tar
但重新打包 Docker image 去掃,結果還是一直看到這些項目:
diff@5.2.0glob@10.4.5minimatch@9.0.5tar@6.2.1tar@7.4.3
問題
看起來很像「套件根本沒升成功」,但實際上專案依賴樹跟 image 掃描結果對不起來。
如果只盯著 package.json 跟 yarn.lock,很容易卡在這個問題:
npm ls diff glob minimatch tar --all --depth=6
在專案裡看到的結果很少,甚至跟弱掃報告列出的版本完全不同;可是 image 掃描結果又很明確,表示那些有弱點的版本真的存在於容器內。
嘗試過程
這種情況,我後來會固定分兩層來查。
第一層是 app 套件:
- 檢查
package.json - 檢查
yarn.lock - 檢查
node_modules的實際依賴樹
第二層是 npm 自己的套件樹:
- 檢查 base image 的 Node / npm 版本
- 直接在容器裡看全域 npm dependency tree
像這次用的 base image 是:
FROM node:22-alpine
進去看之後就很清楚了:
docker run --rm node:22-alpine sh -lc "node -v && npm -v && npm ls -g diff glob minimatch tar --all --depth=6 || true"
結果直接對上弱掃報告:
v22.22.1
10.9.4
/usr/local/lib
└─┬ npm@10.9.4
├── glob@10.4.5
├── minimatch@9.0.5
├── tar@6.2.1
├── tar@7.4.3
└── diff@5.2.0
也就是說,掃描命中的不是專案安裝的 app dependency,而是 base image 內建的 npm 套件樹。
解法
既然 root cause 在 npm,就不能只升專案 dependency,還要在 image 裡把 npm 一起升掉。
這次最後加的是:
FROM node:22-alpine
RUN apk update && apk upgrade --available && sync
RUN apk add --update ca-certificates openssl && update-ca-certificates
# Upgrade npm to fix bundled tar CVEs (tar@6.x bundled in npm@10, fixed in npm@11.11.1+)
RUN npm install -g npm@11.11.1
升完之後再驗證:
docker run --rm <your-image> sh -lc "npm -v && npm ls -g tar --depth=2 || true"
會看到類似:
11.11.1
/usr/local/lib
└─┬ npm@11.11.1
└── tar@7.5.11
這時候 image 裡的舊版 tar@6.x 就不在了,弱掃結果才會真的消掉。
為什麼會這樣
因為 Docker image 的弱掃不是只掃你的應用程式目錄,也會掃整個檔案系統裡已安裝的套件。
對 Node.js image 來說,常見來源至少有兩塊:
-
專案自己的依賴 來自
package.json/ lockfile /node_modules -
base image 內建工具鏈 像是
npm本身,以及npm內部再依賴的glob、minimatch、tar、diff
所以同樣看到 tar,不代表一定是你專案某個 library 帶進來的。它也可能只是 npm 自己用來處理 package archive 的內部依賴。
如果沒有先把這兩類分開,很容易出現兩種誤判:
- 明明 app 套件已經升完,卻一直懷疑 lockfile 沒更新
- 明明問題在 base image,卻一直在
package.json裡盲目加 overrides / resolutions
快速判斷流程
下次遇到類似情況,我會直接照這個順序查:
- 先看
package.json和 lockfile,確認專案是否真的還 pin 在舊版 - 跑 app 依賴樹,確認問題是不是來自
node_modules - 直接進 base image 或目標 image 跑
npm ls -g ... - 如果命中的是 npm 內建依賴,就改
Dockerfile - 如果命中的是 app dependency,才回頭改套件版本或 lockfile
學到的事
看 Node.js Docker image 弱掃時,先問自己一句:這個弱點是 app 套件,還是 npm 自己的套件? 先分清來源,再決定要改 package.json,還是改 Dockerfile。這樣不只比較快,也比較不容易修錯地方。