LiteLLM 1.107.0 無須 import 即觸發 .pth 鈎子,作者實測免啟動盤查與 uv 冷卻
LiteLLM 1.107.0 Ran Without an Import: The .pth Startup Hook, a No-Python Audit, and a uv Cooldown
LiteLLM 1.107.0 曾於 10 月 21 日在 PyPI 上架約 100 分鐘,安裝套件後,.pth 啟動鈎子可在 Python 程序啟動時執行負載,無須 import litellm。
文章以無害替身展示 .pth 啟動鈎子的觸發範圍,並提供不啟動可疑 Python 環境的盤查方式及依賴冷卻設定,方便讀者檢視事故應對措施。
有來源註明的彙編 + 在我的電腦上進行三次小型測試
主要來源: Kunal Singh,PyPI 上遭入侵的 LiteLLM 1.107.0 會安裝憑證竊取程式及後門(SafeDep,2026-10-21)。
廠商事件報告: Krrish Dholakia、Ishaan Jaffer、Oliver Jensen、Nate Armstrong,已控制:2026 年 10 月 21 日 LiteLLM v1.107.0 版本遭入侵(LiteLLM,最後更新於 2026-10-21 20:30 UTC)。
安全公告: OSV MAL-2026-2144。討論: Hacker News 討論串(截至 2026-10-22 有 548 分)。
我沒有安裝、下載或執行惡意套件。以下測試使用無害的替代品及公開的 PyPI 索引。攻擊細節引述自上述來源。
10 月 21 日,litellm==1.107.0 曾在 PyPI 上架約 100 分鐘(11:39 至 13:23 UTC),內含憑證竊取程式及後門。v1.107.0、latest、main-latest 及 main-stable 這些 Docker 標籤,則在 11:46 至 14:20 UTC 期間指向受污染的映像檔。幾乎每篇貼文都反覆提到一點:你不必匯入 litellm 也會中招。只要安裝就足夠;之後,主機上的任何 Python 程序都會執行該惡意程式碼。pip 本身也不例外。
這一點會改變你檢查及清理的方式。以下會介紹無害替代品上的運作機制、無需啟動環境便能讀取環境內容的掃描器,以及原本可以跳過這個版本的版本固定設定。
運作機制:site-packages 根目錄中的三個檔案
SafeDep 的分析指出,七個 wheel 都在 litellm-1.107.0.data/purelib/ 下包含三個額外檔案。如果 wheel 有 <name>.data/purelib/ 資料夾,pip 便會將其內容複製到 site-packages 的根目錄,與其他所有套件並列。這些檔案是:
-
cook.pth:一行,import cook -
cook.py:竊取程式,包裝在exec(zlib.decompress(b64decode(...)))中 -
sitecustomize.py:一個守護程序,每小時從 GitHub 檔案讀取伺服器位址,並執行該伺服器傳回的任何程式碼
兩個啟動掛鈎都依賴 Python 有文件記載的行為。site 模組文件指出,啟動時,.pth 檔案中以 import 加空格或定位字元開頭的行「會透過 exec() 執行」;而 site 隨後會嘗試匯入名為 sitecustomize 的模組。兩者都不取決於你的程式碼匯入了甚麼。只有 -S 能阻止它們,因為它會完全關閉 site。
SafeDep 發現,唯一可見的改動是建置設定中的一行(data = "cook" 位於 [tool.maturin]);sdist 則沒有包含 cook/ 資料夾。LiteLLM 表示,v1.107.0 的 Git 標籤沒有問題,而 wheel 是從一個仿冒儲存庫建置的(Berri-AI/litellm)。因此,查看該標籤或 sdist,並不能讓你知道 pip 安裝了甚麼。
測試 1:無害的啟動掛鈎
我建立了一個一次性 venv,內有一個包含 import demo_hook 的 .pth,以及一個 sitecustomize.py;兩者都只會在記錄檔中新增一行。我分別使用 Debian 的系統 Python 3.13.5,以及由 uv 管理的 CPython 3.10.21 執行(pth_demo.sh,輸出經刪減):
=== base interpreter: /usr/bin/python3 (3.13.5)
stdlib sitecustomize that would shadow ours: /usr/lib/python3.13/sitecustomize.py
$ python -c pass -> .pth hook ran
$ python -m pip --version -> .pth hook ran
$ python -I -c pass -> .pth hook ran
$ python -E -s -c pass -> .pth hook ran
$ python -S -c pass -> (nothing ran)
=== base interpreter: uv cpython-3.10.21
stdlib sitecustomize that would shadow ours: None
$ python -c pass -> .pth hook ran / sitecustomize ran
$ python -m pip --version -> .pth hook ran / sitecustomize ran
$ python -I -c pass -> .pth hook ran / sitecustomize ran
$ python -S -c pass -> (nothing ran)
我的結論是:
-
pip --version會執行掛鈎。因此 LiteLLM 報告才會説:「不要先啟動pip install。」在受感染的 venv 中執行 pip 來「修復」它,只會再次執行竊取程式。 -
-I(隔離模式)沒有幫助。它會忽略環境變數和用戶 site 目錄,但環境本身的site-packages中,.pth檔案仍會執行。只有-S才停用了這個掛鈎。 -
sitecustomize酬載是否會執行,取決於解譯器的組建版本。 Debian 在標準函式庫目錄中附帶自己的sitecustomize.py,而它在sys.path中的位置較前,因此我植入的那個從未載入。在由 uv 管理、沒有標準函式庫副本的 Python 上,它則有載入。我用自己的測試檔案進行測試,並非真正的酬載。不要把這視為防護措施,因為.pth竊取程式在兩者上都執行了。
第 2 次執行:不啟動環境,直接進行稽核
由於在可疑環境中啟動 Python 會再次執行酬載,掃描器只讀取檔案,不會執行正在檢查的解譯器。請從乾淨的解譯器使用-S -I執行:
python3 -S -I scan_site.py ~/code ~/.cache /usr/lib /usr/local/lib /opt
scan_site.py(僅使用標準函式庫,約 120 行)會遍歷site-packages/dist-packages的根目錄,並對每個項目:
- 列出每個包含
import行的.pth檔案,並利用各套件的RECORD找出是哪個發行套件安裝了該檔案(沒有任何套件認領的檔案會被標記) - 標記
site-packages內任何sitecustomize.py/usercustomize.py,以及.start檔案;這是 Python 3.15 的新啟動進入點格式(PEP 829) - 標記
*.data/purelib/下位於 site-packages 根目錄的RECORD項目;1.107.0 正是以這種方式封裝的 - 檢查
litellm-1.107.0.dist-info及公告中列出的兩個/tmp標記
在只由文字檔構成的測試樣本中(沒有安裝或執行任何內容),它找出所有植入的指標,並以代碼 2 結束。在實際主機上,它於 0.27 秒內檢查了 8 個 site-packages 目錄,沒有發現任何指標。它標記了一個合法的可執行.pth:setuptools 的distutils-precedence.pth。這就是實際上的難題。合法套件也會附帶可執行的.pth行(setuptools、可編輯安裝,以及某些 coverage 和 virtualenv 工具),所以「任何import行」都會在乾淨的機器上觸發警示。將檔案擁有者與RECORD核對,才能讓輸出短到真正看得完。
根據 LiteLLM 的報告,指標可確認曾經暴露,但沒有發現任何指標,並不能證明環境安全:清理暫存資料和新建容器都會抹去標記。若 1.107.0 曾在能連接外部網絡的主機上執行,應假設資料已成功外洩。從 shell 刪除三個檔案及其__pycache__項目,終止佔用/tmp/remote_code_loader.lock的常駐程序,重建環境,並輪替該主機可能存取的所有項目:雲端角色、proxy-DB 供應商金鑰、主金鑰和虛擬金鑰、DATABASE_URL密碼,以及環境變數中的機密。
第 3 次執行:冷卻期會解析出甚麼版本
HN 討論串不斷提到相依套件冷卻期(略過最近 N 小時內上載的任何版本),也有人反駁指冷卻期會延誤緊急修正。利用 PyPI 的上載時間(另加 LiteLLM 時間線記載的 1.107.0 上載時間 11:39:51 UTC,因為該版本已被刪除),cooldown_check.py會計算解析器在 12:00 UTC,也就是冷卻期開始後 20 分鐘,會選擇甚麼版本:
no cooldown -> 1.107.0 (malicious, deleted)
1 hour -> 1.106.2
24 hours -> 1.106.0
7 days -> 1.105.1
它會選擇符合限制的最高版本,而非最新上載的版本,因為像 1.103.7(當天 09:35 UTC)這類回補版本的上載時間晚於 1.106.x。使用 uv 0.13.4 對照今天的索引:uv pip compile --exclude-newer "2026-10-20T12:00:00Z"會解析至litellm==1.106.0,與 24 小時那一列相符,而--exclude-newer "7 days"會解析至 1.105.1。
1 小時那一列只是剛好碰上有利時機。若在 12:45 使用同一個解析器,1 小時冷卻期會讓 1.107.0 通過,因為它一直保留至 13:23。24 小時冷卻期足以涵蓋這宗事件,而且仍有餘裕。
若要將此設為專案的預設值,uv 會在pyproject.toml中接受相對時長。我用以下設定鎖定一個只有單一相依套件的專案:
[tool.uv]
exclude-newer = "7 days"
uv lock解析了 65 個套件,將litellm固定在 1.105.1,並把exclude-newer-span = "P7D"儲存在uv.lock。若再配合已提交的鎖定檔(pip 用戶可使用pip install --require-hashes -r requirements.txt),新上載的版本就不會在重建時悄然混入。對容器而言,冷卻期無法處理latest這類可變標籤;應改為固定映像檔摘要。
程式碼代理的角度
LiteLLM 的工作假設(截至其 20:30 UTC 更新時尚未確認):它發現一些案例,當中 Claude Code 將用戶的 GitHub CLI OAuth 權杖嵌入回應本文,而這些本文被寫入一個可從互聯網存取的內部 LiteLLM 部署的記錄中,攻擊者再從中竊取了三名開發人員的權杖。無論最終調查結果如何,這項簡單而低成本的建議仍然適用:如果你的 LLM 閘道會記錄完整的請求/回應本文,就應把這些記錄視為機密資料,遮蔽符合權杖模式的字串,並避免管理介面暴露於公眾互聯網。
我沒有測試的項目
- 真正的惡意 wheel 檔案或 Docker 映像。我沒有下載或執行過它們。以上載荷行為資訊來自 SafeDep 和 LiteLLM。
- Debian 上
sitecustomize的遮蔽是否適用於真正的sitecustomize.py載荷,或在 macOS/Windows 上是否同樣成立。 - 掃描器在 macOS 或 Windows 路徑、conda 環境或容器映像內的情況。匯出映像的檔案系統,再讓掃描器掃描;我沒有測試這個做法。
- 使用雜湊值鎖定的 pip 安裝,以及其他工具的冷卻設定(pip、Poetry、Renovate)。我只測試了 uv。
為何這很重要
大多數 AI 技術堆疊都會在某處引入 LLM 路由器或 SDK,通常是透過你沒有選用的框架;而 .pth 這個技巧會令其安裝步驟成為這部機器上每個 Python 程序的一部分。無需啟動 Python 即可進行掃描;採用 24 小時或以上的冷卻期並使用鎖定檔、固定映像摘要,並把閘道記錄視為憑證儲存庫。
作者 YongBo Yu,常駐多倫多的 AI 工程師。更多資訊請瀏覽 yongbo-yu.vercel.app 和 GitHub。
來源:DEV Community · dev.to