新版本先放一天再裝:這週幾起供應鏈攻擊的共同點
LiteLLM 惡意版在 PyPI 上架 1 小時 44 分;pnpm 和 GitHub 都把「新版本冷卻期」預設成一天,原因就在這裡
這週一口氣出了好幾起供應鏈攻擊,手法各不相同。看完之後,我對「隨時保持最新版」這個習慣有點動搖。
這週發生什麼事
先說 LiteLLM。攻擊者偷了 3 名工程師的 GitHub 憑證,用其中有管理權限的帳號關掉原本限制改程式碼的保護,再把發布系統讀原始碼的位置改成一個名稱很像的仿冒儲存庫。所以惡意版本是透過官方流程正常建置、正常上架的,GitHub 上公開的 1.107.0 原始碼反而乾乾淨淨。
這個惡意版會在 Python 執行環境裡加檔案,同一個環境的其他 Python 程式一啟動也可能觸發,就算它們根本沒用 LiteLLM。被偷的包括雲端憑證、AI 服務金鑰和 SSH 私鑰。
報導裡還有個細節:LiteLLM 團隊發現 Claude Code 在特定情況下會把 GitHub 登入憑證放進回覆內容,這些內容可能被記錄在內部系統裡。是不是因此外洩還在調查,但用 AI 代理寫程式的人,大概都該想一下自己的 token 會跑進哪些 log。
OVHcloud 的 CLI 也中了,10 月 19 日發布的 v0.11.0 到 v0.15.0 共 5 個版本被植入竊資程式,一樣是用被入侵的開發者 GitHub 帳號換掉發布檔案。WordPress 的 SEO 外掛 Rank Math PRO 則是 3.0.123 版被植入後門。Arch Linux 的 AUR 上有人上傳名字跟 yay、paru 很像的套件,裝下去攻擊者能用 root 執行遠端指令。
Python 這邊也有一件不算攻擊、但很容易漏掉的事。eMudhra 旗下 emSign 的憑證私鑰出問題,Python 10 月 21 日緊急發布 3.15.1、3.14.9、3.13.17,把內附的 pip 更新到 26.2.2。問題是更新 Python 不會順便更新你既有的虛擬環境:venv 裡的 pip、certifi、uv 要各自升級,容器要關掉 layer cache 重建。只更新作業系統的憑證也不夠,因為 pip 會讀自己內附的 certifi。
1 小時 44 分
回到 LiteLLM。受污染的 PyPI 版本在台灣時間 10 月 21 日 19:39 上架,21:23 下架,前後 1 小時 44 分。Docker Hub 上的映像檔更晚,22:20 才清完。
pnpm 文件裡剛好有一句話:大多數惡意版本會在一小時內被發現並從 registry 移除。它的 minimumReleaseAge 設定就是為這個做的,新版本發布後要經過指定的分鐘數才會被安裝,間接相依也算。這個設定從 v10.16.0 開始有,v11 起預設值是 1440,也就是一天。
GitHub 這週推出的 NPM 套件防火牆(公開預覽,目前只給 GitHub Enterprise Cloud)也有一樣的機制,叫版本冷卻期,預設 1 天,可以設 1 到 365 天。它另外會檢查新版是不是少了原本有的 SLSA 來源證明,或多了 preinstall、postinstall 這類安裝時就會執行的指令碼。
兩邊各自把預設值定在一天,我覺得不是巧合。大多數專案晚一天拿到新版幾乎沒有損失,而至少以 LiteLLM 這次來說,一天的冷卻期就足夠避開 PyPI 上那個惡意版本。
冷卻期管不到的地方
冷卻期只管套件管理器那一層。LiteLLM 的 Docker 映像檔被換掉時,有人就算沒指定 1.107.0,只是拉了標成最新版的映像也會中,latest 這個 tag 不會幫你等一天。AUR 那種名字拼錯的套件它也擋不住,那是裝錯東西,不是裝太快。
反過來,真的需要緊急修補的時候,例如這週的 Node.js 安全更新,你會希望它不要擋。pnpm 有 minimumReleaseAgeExclude 可以依套件名稱排除。
用 pnpm 11 以上、沒有另外改設定的話,冷卻期本來就是開著的。還停在 pnpm 10 的專案,可以在 pnpm-workspace.yaml 加一行:
minimumReleaseAge: 1440
Python 那邊,記得把 venv 裡的 pip 也升一下。
參考: