新加坡網路安全局(CSA)的通告 AD-2026-009 於 2026 年 8 月 6 日發布,9 月 7 日仍在更新——一個月過去還在滾動更新,本身就說明事件沒有結束。規模:npm 上超過 1,300 個套件版本被投毒,這些套件合計每月約 20 億次下載

被投毒的是快取類基礎套件,多數人是間接相依

通告點名的典型版本包括 keyv 6.0.0、cacheable 2.5.1、cacheable-request 13.0.20、flat-cache 6.1.24、file-entry-cache 11.1.6。注意這些套件的共同點:幾乎沒人會直接安裝它們,它們躺在 ESLint、各類 HTTP 用戶端和建置工具的相依樹深處。也就是說,「我沒裝過這些套件」不是一個有效的判斷,必須真的去查相依樹。

它偷什麼:清單裡就有 SSH 私鑰

惡意軟體名為 ChainDrop,是 Shai-Hulud 家族的自我傳播變種。它竊取並外傳的內容包括:雲端服務憑證、GitHub 權杖、SSH 私鑰、Kubernetes 設定、Terraform 憑證,外傳目標網域為 npm-cache[.]com(這個名字顯然是照著 npm 官方基礎設施起的)。拿到這些之後,它繼續用被盜的發布者憑證去污染更多套件——這就是它能滾到 1,300 多個版本的原因。

微軟威脅情報在 2026 年 8 月 4 日的分析裡補充了執行細節:涉及 400 多個套件、跨多個互不相關的發布者,酬載是重度混淆的 Bun 版 JavaScript,透過 npm 的 preinstall 生命週期掛鉤在安裝完成之前就執行。CSA 通告點名的掛鉤檔案是 setup.mjsMath_Symbol.jsmath_init.js

最初的入口不是什麼高深的零時差:一封偽裝成 npm 安全告警的釣魚郵件騙走了一個維護者的帳號。

為什麼伺服器管理員必須把這當成自己的事

關鍵在於 preinstall 掛鉤是以你的使用者身分執行的。它能讀到的東西,就是你能讀到的東西。而在一台既跑正式環境又順手 npm install 的機器上,同一個使用者目錄下通常同時躺著:

  • ~/.ssh/id_ed25519——很可能還是一把能免密登入其他伺服器的金鑰
  • 雲端廠商 CLI 的憑證快取、.env 裡的資料庫密碼和 API 金鑰
  • ~/.docker/config.json~/.kube/config~/.terraform.d/

換句話說,供應鏈攻擊在開發者筆電上偷的是程式碼權限,在伺服器上偷的是整片基礎設施的鑰匙

現在就做的四件事

  • 查相依樹npm ls keyv cacheable cacheable-request flat-cache file-entry-cache。也可以直接 grep -n "keyv\|flat-cache\|file-entry-cache" package-lock.json 看鎖定的具體版本號。
  • 命中就按已失陷處理。CSA 的建議很直接:移除受影響版本、把系統當作已被入侵、裝過惡意套件的機器重建、輪換全部暴露的憑證、檢查雲端與程式碼代管環境有沒有異常存取。輪換的範圍包括 SSH 金鑰、GitHub 權杖、雲端 API 金鑰,而不只是改個密碼。
  • 查歷史外連:有沒有連過 npm-cache[.]com。這條能給出「有沒有真的被執行過」的直接證據。
  • 改掉習慣:CI 與正式環境的安裝一律加 --ignore-scriptsnpm ci --ignore-scripts),確實需要建置腳本的套件單獨處理;建置與執行用不同的低權限使用者;給 SSH 私鑰加通行碼;關掉 agent forwarding;正式機上不放能直推正式環境的金鑰。

把建置和執行分開,本身就是一道防線

這類事故裡損失最慘的,通常是「一台機器什麼都幹」:既是建置機、又是正式機、還存著通往其他機器的金鑰。把建置放到一台獨立執行個體上,讓它沒有正式環境憑證、也沒有到正式環境的免密通道,投毒套件最多毀掉一台可隨時重建的機器。用 PM2 在 VPS 上部署 Node.js 應用裡有行程與使用者隔離的做法,VPS 備份策略裡有讓「重建」真正可行的前提條件。SharkCloud 的執行個體按小時計費、可隨時重開,多開一台專門做建置的機器,成本遠低於一次憑證外洩。