2026 年 4 月 30 日,Semgrep 的威脅研究團隊發布了一份令人不安的報告:廣泛使用的深度學習框架 PyTorch Lightning(PyPI 套件名 lightning)在版本 2.6.2 和 2.6.3 中被植入惡意程式碼。這不是一次普通的漏洞通報——這是一場精心策劃的供應鏈攻擊,代號為「Shai-Hulud」。
如果你曾經執行過 pip install lightning,你的開發環境、CI/CD 管線、GitHub 倉庫,甚至雲端機密,可能都已經暴露。
攻擊的起點:兩個被感染的版本
這場攻擊的入口點是 PyPI 上的 lightning 套件。攻擊者成功將惡意版本的 2.6.2 和 2.6.3 上傳到 PyPI,這兩個版本包含了隱藏的 _runtime 目錄,裡面裝載了混淆處理過的 JavaScript 酬載(payload)。
問題有多嚴重?PyTorch Lightning 是當前 AI 領域最受歡迎的訓練框架之一。Teams building image classifiers, fine-tuning LLMs, running diffusion models, or developing time-series forecasters frequently have lightning somewhere in their dependency tree。任何正在做圖像分類、大型語言模型微調、擴散模型推理、或時間序列預測的團隊,幾乎都可能在某個依賴中引入這個套件。
執行 pip install lightning 就足以觸發攻擊。模組被匯入時,隱藏的反向連線程式自動執行,竊取你的登入憑證、認證令牌、環境變數和雲端機密,同時嘗試感染你的 GitHub 倉庫。
根據 Semgrep 的報告,這波攻擊與「Mini Shai-Hulud」攻擊出自同一個威脅組織。兩者的 IOC(入侵指標)結構一致:惡意 commit 訊息沿用 Dune 主題命名規則,這次的 campaign 使用 EveryBoiWeBuildIsAWormyBoi 前綴來與原始的 Mini Shai-Hulud 攻擊區別。
跨生態系的攻擊:從 PyPI 到 npm
這場攻擊最值得注意的特色,是它不僅停留在 Python 生態系。
與 Mini Shai-Hulud 直接攻擊 npm 不同,這次的入口是 PyPI。但惡意酬載本身仍然是 JavaScript,而蠕蟲的散播途徑則是透過 npm。
一旦惡意程式在受害者的機器上執行,如果它找到了 npm publish 憑證,就會執行以下操作:
- 將
setup.mjs(dropper)和router_runtime.js注入到該令牌能夠發布的所有套件中 - 在
package.json中設定scripts.preinstall來執行 dropper - 提升 patch 版本號,然後重新發布
- 任何下游開發者安裝了這些套件,就會完整執行惡意程式
換句話說,這不是一場簡單的 PyPI 攻擊。它是一場跨生態系的供應鏈戰爭,從 Python 出發,穿越 JavaScript 的世界,最終感染所有能夠觸及的地方。
四管齊下的資料竊取架構
這款惡意程式的資料竊取機制,在設計上相當成熟。它使用四個並行管道,確保即使其中一個路徑被封鎖,資料仍然能被送出去。
1. HTTPS POST 到 C2 伺服器
被竊取的資料會立即透過 443 埠 POST 到攻擊者控制的伺服器。網域和路徑以加密字串的形式儲存在酬載中,使得靜態分析難以取得具體位址。
2. GitHub commit 搜尋死信箱(Dead-drop)
惡意程式會定期輪詢 GitHub commit 搜尋 API,尋找以 EveryBoiWeBuildIsAWormyBoi 為前綴的 commit 訊息。這些訊息攜帶一個雙重 base64 編碼的令牌,格式為 EveryBoiWeBuildIsAWormyBoi:<令牌>。解碼後,令牌用於驗證 Octokit 客戶端,進行後續操作。
3. 攻擊者控制的公開 GitHub 倉庫
惡意程式會建立一個新的公開倉庫,名稱從 Dune 主題單詞中隨機選取,描述為「A Mini Shai-Hulud has Appeared」。這個倉庫直接可被搜尋。被竊取的憑證以 results/results--<編號>.json 的形式提交,超過 30MB 的檔案會被分割成編號的區塊。commit 訊息使用 chore: update dependencies 作為掩護。
4. 推送回受害者自己的倉庫
如果惡意程式取得了 ghs_ 開頭的 GitHub 伺服器令牌,它會直接將被竊取的資料推送到受害者自己倉庫的所有分支。
這種「用你的金鑰鎖你的門」的做法,讓事後鑑識變得格外困難——資料從受害者自己的帳號流出,可能只留下極少的痕跡。
什麼東西被偷了?
攻擊者的目標範圍相當全面,涵蓋了本地檔案、環境變數、CI/CD 管線、以及三大雲端服務商。
檔案系統
掃描 80 種以上的憑證檔案路徑,針對 ghp_、gho_ 和 npm_ 令牌進行正規表示式匹配,每個檔案最多掃描 5MB。
Shell 與環境變數
執行 gh auth token 命令,並將 process.env 中的所有環境變數全部傾倒。
GitHub Actions
在 Linux runner 上,透過內嵌的 Python 來傾印 Runner.Worker 程序記憶體,擷取所有標記為 isSecret: true 的 secrets,以及 GITHUB_REPOSITORY 和 GITHUB_WORKFLOW 等資訊。
GitHub 組織
檢查令牌的 scope(repo、workflow),然後遍歷 GitHub Actions 的組織級 secrets。
AWS
嘗試四種獲取憑證的方式:
– 環境變數
– ~/.aws/credentials 中的設定檔
– IMDSv2(169.254.169.254)實例中繼資料
– ECS(169.254.170.2)任務中繼資料
一旦取得憑證,會呼叫 sts:GetCallerIdentity 進行驗證,然後列舉並擷取所有的 Secrets Manager 值和 SSM 參數。
Azure
使用 DefaultAzureCredential 進行認證,列舉訂閱,然後讀取 Key Vault 中的機密。
GCP
透過 GoogleAuth 進行驗證,列舉並擷取 Secret Manager 中的所有機密。
這份清單涵蓋了本地開發環境、CI runner、以及三大雲端服務商。任何在受影響期間匯入惡意套件的機器,都應該被視為完全淪陷。
開發者工具成為後門
這款惡意程式的另一個特點,是它利用了開發者日常使用的工具作為持續性機制。
Claude Code 掛鉤
惡意程式會在倉庫的 .claude/settings.json 中寫入一個 SessionStart hook,觸發條件設置為 matcher: "*",指向 node .vscode/setup.mjs。這意味著開發者只要在受感染的倉庫中開啟 Claude Code 對話,掛鉤就會被觸發——不需要任何工具使用或用戶操作。
這可能是最早的文件化案例之一,證明惡意軟體在實際攻擊中濫用了 Claude Code 的掛鉤系統。
VS Code 掛鉤
平行地,.vscode/tasks.json 中設定了一個 runOn: folderOpen 的任務,只要專案資料夾被打開,就會執行 node .claude/setup.mjs。
雙向互鎖
注意這兩者的設計:Claude Code 掛鉤指向 .vscode/setup.mjs,VS Code 任務則指向 .claude/setup.mjs。即使你只使用其中一個工具,清除其中一個掛鉤也不會解決問題——另一個會自動重新部署。這樣的互鎖設計,讓清除變得困難。
setup.mjs:Bun runtime 啟動器
無論是從哪個掛鉤被觸發,最終都會呼叫 setup.mjs。這是一個自包含的 Bun runtime 啟動器。如果 Bun 尚未安裝,它會從 GitHub releases 靜默下載 bun-v1.3.13,支援 Linux x64/arm64/musl、macOS x64/arm64、和 Windows x64/arm64。然後執行 .claude/router_runtime.js(完整的 14.8MB 酬載),最後清理 /tmp 中的暫存檔案。
隱藏的 GitHub Actions 工作流程
如果惡意程式持有具有寫入權限的 GitHub 令牌,它會在受害者的倉庫中推送一個名為「Formatter」的 Actions 工作流程。在每次推送時,它會透過 ${{ toJSON(secrets) }} 傾印所有倉庫機密,並作為可下載的 Actions artifact(名為 format-results)上傳。
這個工作流程甚至會自動建立一個 pull request,然後自己 approve 並 merge,完成整個攻擊閉環。對開發者來說,可能只會看到一個奇怪的「格式化」PR,完全不曉得自己的機密已經被全部上傳。
對開發社群的具體啟示
這場攻擊對開發社群的教訓相當深刻,值得每一支開發團隊重新審視自己的安全實踐。
依賴管理的被動性
大多數團隊對待依賴的方式是「裝了就不管」。pip install 某個套件之後,很少有人會去定期檢查它的版本變化或 diff。但供應鏈攻擊恰恰利用了這種被動心態——攻擊者不需要攻破你的防火牆,只需要讓你的信任鏈中的一個環節出問題。
工具生態圈的雙面性
Claude Code 和 VS Code 的掛鉤系統原本是為了提升開發效率而設計的。但這次攻擊證明了,任何自動化執行機制都可能成為攻擊面。特別是 Claude Code 的 SessionStart hook,其觸發門檻非常低——不需要使用者執行任何命令,只要開啟對話就會觸發。這在設計上需要更多的權限管控。
CI/CD 的盲區
GitHub Actions 的 secrets 傾印這裡特別值得注意。許多開發者認為,secrets 設定在 GitHub 上就是安全的,因為只有指定的 Actions 工作流程才能存取。但當攻擊者能夠在倉庫中寫入自己的工作流程時,這個保護機制就完全失效了。
跨生態系的攻擊面
從 PyPI 到 npm 的跨越提醒我們,現代軟體開發是高度交織的。一個團隊可能用 Python 做訓練,用 JavaScript 做前端,用 GitHub Actions 做 CI/CD。攻擊者不需要選擇——他們可以同時攻擊所有層面。
我該怎麼辦?
如果你在 2026 年 4 月 30 日前後安裝或更新了 lightning 套件到 2.6.2 或 2.6.3 版本,以下是你需要做的事情。
立即檢查
首先確認你是否受影響。檢查專案的 requirements.txt、pyproject.toml 或 poetry.lock,看 lightning 的版本是否為 2.6.2 或 2.6.3。如果已經升級到 2.6.4 或更高版本,你已經移除了已知的惡意版本。
旋轉憑證
無論你是否確認受感染,都建議旋轉以下憑證:
– 所有 GitHub 個人存取令牌(尤其是 ghp_ 和 gho_ 開頭的)
– npm 發布令牌
– AWS IAM 金鑰(特別是存在 ~/.aws/credentials 中的)
– Azure 服務主體憑證
– GCP 服務帳號金鑰
– 任何在開發環境環境變數中出現過的 API 金鑰
檢查倉庫
檢查你的 GitHub 倉庫(包括 fork)是否有異常的檔案:
– .claude/ 目錄中的非常規檔案
– .vscode/tasks.json 中不認識的任務
– 不認識的公開倉庫(特別是 Dune 主題名稱的)
– Formatter 工作流程或類似的異常 Actions
審查依賴樹
不僅檢查 direct dependencies,也要檢查 transitive dependencies。攻擊者可以透過任何一個下游套件進入你的系統。使用 pipdeptree 或 poetry show --tree 來檢視完整的依賴圖。
重新審視 CI/CD 安全
考慮為 Actions 工作流程設定更嚴格的權限模型:
– 限制哪些工作流程可以存取 repository secrets
– 對 fork 的 PR 使用 pull_request_target 時特別謹慎
– 定期審查倉庫中的 Actions 工作流程清單
– 考慮使用 GitHub 的「環境保護規則」來限制部署權限
老實說,每次寫這種供應鏈攻擊的分析,我心裡都有一點不安。不是因為技術細節多複雜——事實上,pip install 一個套件然後等著被感染的場景,從技術角度來說簡單到令人沮喪。真正讓人不安的,是這種攻擊揭露了我們對軟體信任體系的根本裂縫。
我們每天執行幾十次 pip install、npm install、go get,幾乎從不停下來想:我剛剛把什麼東西裝到了我的機器上?那個套件的維護者是誰?他的帳號安全嗎?他的 CI/CD 有沒有漏洞?
這些問題沒有簡單的答案。開源生態系的運作仰賴信任,而信任恰恰是無法規模化的東西。當 PyPI 上有數十萬個套件、npm 上有數百萬個套件的時候,沒有人能夠逐一審查它們。
但或許我們至少可以做一件事:在執行 install 之前,給自己三秒鐘停下來想一下。這三秒鐘不能解決所有問題,但它代表了一種態度——對安全的尊重,對信任的謹慎。而在這個連 PyTorch Lightning 都可能被植入惡意程式的時代,這種態度可能是我們唯一的防線。