← 返回所有文章
發佈於 · 作者 Renaud Deraison

撤銷權杖,就是引爆點

10 月 8 日,tensorlake 0.5.144 帶著一隻 Shai-Hulud 蠕蟲上了 npm。它竊取 GitHub、npm、雲端和 SSH 憑證,在受害者的儲存庫裡植入 .claude/settings.json,並安裝一個監控程式,每 60 秒檢查一次被竊的 GitHub 權杖,一旦權杖失效就執行 rm -rf ~/。撤銷權杖,也就是標準事件應變流程的第一步,會抹掉受害者的家目錄。在 Bromure Agentic Coding 裡,預設的存在時間閘門讓 npm 看不到這個版本,而機器裡的 GitHub 權杖從來就不是真的。

蠕蟲在程式碼裡留了一串字給發現它的人: 「IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner」。意思是:撤銷這個權杖, 就會抹掉擁有者的電腦。程式碼說到做到。

10 月 8 日星期三 01:12 UTC,tensorlakeai/tensorlake 的發布工作流程把 tensorlake SDK 的 0.5.144 版發布到 npm。Tensorlake 是給 AI 代理用的無伺服器沙箱平台,它的 npm 套件每週約有 12,000 次下載。新版本來自專案自己的儲存庫,由專案自己的工作流程建置,npm 上還顯示了它的來源證明(provenance attestation)。

Socket 在 11 分鐘後標記了它。 StepSecurity、 Aikido 和 The Hacker News 在同一天跟進。這個版本帶著 Shai-Hulud 蠕蟲的新變種,而這一隻還會挾持人質。

它是怎麼進來的

StepSecurity 重建了時間線。10 月 7 日 01:20 UTC,有人以一位維護者的名義,直接把一個 commit 推上儲存庫的主分支。接下來幾個小時又來了七個 commit。沒有人發現,一天後發布工作流程 建置了 main 上的程式碼並把它發布出去。來源證明是正確的。它證明了套件在哪裡建置,而那個地方 早已落入攻擊者手中。

套件的 package.json 多了一行:

"preinstall": "node lib/setup.mjs"

npm 在套件解壓到定位之前就會執行 preinstall。這個腳本下載 Bun 執行環境,再用它執行 lib/Math_Symbol.js,一個 856 KB 的混淆酬載。它在 CI 上會跳過自己。StepSecurity 的總結是: 「目標是開發者的機器。」

它拿走什麼,送到哪裡

酬載會蒐集 GitHub 和 npm 權杖、AWS、Azure 和 GCP 的雲端金鑰、Kubernetes 和 HashiCorp Vault 的機密、SSH 金鑰、.env 檔案、瀏覽器儲存的登入資訊、加密貨幣錢包,以及 AI 寫程式 工具的設定:.claude、.cursor、.kiro、Windsurf 和 Zed。它把戰利品加密後送到 iseekaigogo.com;送不出去的話,就在受害者自己的 GitHub 帳號裡建立一個公開儲存庫放進去, 描述寫著「Shai-Hulud: Here We Go Again.」。網域被拿下時,它會透過大約 30 個公開 RPC 端點, 到一個以太坊合約裡查新的網域。

接著它開始擴散。拿著受害者的 npm 權杖,它下載受害者的每個套件,把自己加進去,升版號, 重新發布。拿著 GitHub 權杖,它以 [email protected] 的身分,用 chore: update dependencies 這則訊息,把 .claude/settings.json 和 .vscode/tasks.json commit 進受害者的儲存庫。下一個在 Claude Code 或 VS Code 裡打開 這些專案的開發者,會再執行一次蠕蟲。就像 StepSecurity 的 Ashish Kurmi 說的,這個惡意 程式「在有人用 Claude Code 或 VS Code 打開專案時會再次執行」。

人質

新的部分是一個叫 gh-token-monitor 的服務。在 Linux 上,它是 ~/.config/systemd/user/ 裡的 systemd 使用者單元;在 macOS 上,是位於 ~/Library/LaunchAgents/com.user.gh-token-monitor.plist 的 LaunchAgent;在 Windows 上,是一個執行 monitor.ps1 的登入工作。StepSecurity 描述了它做的事:

每 60 秒,最長 24 小時,這個服務會拿那個權杖去問 GitHub API。如果 GitHub 拒絕它, 服務就執行 rm -rf ~/。

假設你是受害者。你得知自己裝的某個套件偷走了你的 GitHub 權杖。標準流程說要立刻撤銷。你撤銷 了,不到一分鐘,你的家目錄就沒了:你的 checkout、還沒 commit 的工作、dotfiles,還有蠕蟲 做過什麼的證據。Socket 在程式碼裡找到了以字面字串寫明的威脅: IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner。

所以現在的安全公告都附上了一條順序規則。StepSecurity 的第 3 步是找出並移除監控程式,並且 先備份所有不能失去的東西。它用粗體警告:「在完成第 3 步之前,不要撤銷任何 GitHub 權杖。」 Socket 也這麼說。你花在搜尋監控程式上的每一個小時,攻擊者都握著一個有你 push 權限的有效 權杖。

在筆電上:流程的第一步觸發了抹除1. npm install[email protected]preinstall: node lib/setup.mjs偷走真的GitHub 權杖(push 權限)npm 權杖(發布權限)~/.aws ~/.kube ~/.ssh安裝gh-token-monitor (LaunchAgent)儲存庫裡的 .claude/settings.json2. 監控程式,每 60 秒GET api.github.com/userAuthorization: 真權杖200 OK → 繼續等攻擊者仍握有你的push 權限401 → 執行處理程序3. 你撤銷權杖就像每份流程說的rm -rf ~/checkout、未 commit 的工作、dotfiles,還有證據
在筆電上,被偷的權杖是真的,所以撤銷刻不容緩。監控程式把這次撤銷變成抹除開發者的家目錄。

在 Bromure 機器裡做同樣的安裝

在 Bromure Agentic Coding 裡,Claude Code、Codex 和其他代理都在你 Mac 上的 Linux VM 裡 執行,一台機器一個 VM。VM 開啟的每條連線,包括每次 npm 下載,都會經過 Mac 上的代理伺服器。 在其中一台機器裡重演 10 月 8 日:代理把 tensorlake 加進專案,然後執行 npm install。

那個版本還不存在

每台新機器都預設開啟供應鏈的存在時間閘門,最少兩天。當 npm 向登錄庫要 tensorlake 的 版本清單時,代理伺服器會拿掉所有發布不到兩天的版本,並讓 latest 指向夠舊的最新版本。 10 月 8 日那天,就是 0.5.143。npm 裝了它就繼續往下走。從機器內部看,0.5.144 從來沒存在過, 等到它的時間超過門檻時,它早就從 npm 上消失了。

如果 lockfile 或代理指名要 0.5.144,代理伺服器會用 HTTP 451 拒絕下載,並附上一段 npm 會 原樣印出的理由:

Bromure Supply-Chain Security blocked this request: npm package
[email protected] published 3 hours ago — policy requires 2 days minimum

代理也會讀到這一行,並自行鎖定前一個版本。

同一個 Supply Chain 面板裡還有兩層。選了 socket.dev 並開啟 Block compromised packages 時,只要 socket.dev 標記了該套件,下載就會被拒絕;10 月 8 日 Socket 只花了 11 分鐘。開啟 Strip preinstall / install / postinstall / prepare from npm tarballs on the fly 時,代理伺服器會改寫 tarball,"preinstall": "node lib/setup.mjs" 這一行在 npm 看到之前就消失了。這一行是蠕蟲唯一的入口。

假設它還是執行了

把每一層都拿掉,讓酬載執行。它以 VM 使用者的身分,在這台機器自己的家目錄裡執行,開始尋找 清單上的東西。

權杖都是替身。 VM 裡沒有任何檔案、環境變數或程序持有真正的 GitHub 權杖、雲端機密或 SSH 私鑰。GH_TOKEN 和 ~/.git-credentials 裡放的是格式正確的 ghp_ 權杖,Mac 上的 代理伺服器只在請求要送往 GitHub 時才換上真的。~/.ssh 裡沒有私鑰:SSH_AUTH_SOCK 是通往 Mac 上 ssh-agent 的橋接,ssh-agent 只負責簽章,從不交出金鑰。你 Mac 的家目錄、鑰匙圈和 瀏覽器設定檔根本不在 VM 裡。蠕蟲加密並送出的是一包替身,除非透過那台代理伺服器,GitHub、 AWS 和 Kubernetes 在任何地方都會拒絕它們。

對外的路要經過 Mac。 在 Guardrails 面板裡把 Unmatched traffic 設為 Deny,並為模型供應商、登錄庫和 GitHub 設好規則的機器,永遠不會連到 iseekaigogo.com,也不會連到那 30 個以太坊 RPC 端點中的任何一個。不論設定如何,這些連線 都會出現在 Security Timeline 裡,它在每台機器上都一直開著。

寫入要等你點擊。 新機器對每個 git 代管平台都使用 Prompt before write。蠕蟲在 GitHub 上的備案是在你的帳號裡建立公開儲存庫:那是一個 POST /user/repos,屬於寫入,所以 macOS 會顯示 Allow write on "github.com" from workspace "…"?,附上請求在線路上的原貌。 它擴散時會把 .claude/settings.json commit 進你的儲存庫並推送:同一個對話框,這次附的是 POST /acme/app.git/git-receive-pack。你沒叫代理建立一個叫「Shai-Hulud: Here We Go Again.」的儲存庫。你點下 Don't allow,蠕蟲的擴散就在你的 Mac 上結束。

人質握的是替身

gh-token-monitor 單元照樣會把自己裝進 VM 的 ~/.config/systemd/user/,並開始每 60 秒 用它找到的權杖呼叫 api.github.com/user。那個權杖就是替身。代理伺服器為 GitHub 換上真的, GitHub 回 200,監控程式繼續等。

這把應變的順序反過來了。在筆電上,真權杖在攻擊者手裡,比的是在對方使用之前撤銷它,又不能 觸發抹除。在 Bromure 裡,真權杖從沒進過 VM,攻擊者手上沒有東西可以跟你比快。除了你的代理 伺服器,沒有任何地方接受他拿走的那個 ghp_ 值。

把機器關機,監控程式就跟著停了。打開 Rewind home…,選安裝前那次開機的檢查點:Bromure 每次開機成功都會建立一個。單元檔、~/.local/bin 裡的腳本,以及植入機器 checkout 裡的 .claude/settings.json 檔案,全都回到原本的樣子。片刻之前的家目錄會先保存為一個新的還原點, 所以倒轉本身也能復原。想要從頭來過,Reset disk 會從簽署過的基礎映像重新複製系統磁碟。

就算開關還是觸發了,rm -rf ~/ 刪的也只是一個 VM 裡的 /home/ubuntu。你 Mac 的家目錄 不在它的範圍內,而 Rewind home… 能從最後一個檢查點把機器的家目錄找回來。

在 Bromure 裡:版本被藏起來,人質是替身機器 VM(Ubuntu)$ npm install tensorlake+ [email protected]如果酬載還是執行了GH_TOKEN=ghp_…(替身)~/.ssh:沒有私鑰只有這台機器的家目錄gh-token-monitor 用替身查詢 GitHub抹除只碰得到這個 VM 的 /home/ubuntu,Rewind home 能把它找回來代理伺服器,在 Mac 上存在時間閘門:latest → 0.5.143指名 0.5.144 → 451,印出理由防火牆:iseekaigogo.com → 拒絕以太坊 RPC 端點 → 拒絕每個判定都記在 Security TimelinemacOS 對話框:Allow write on …?POST /user/reposPOST /acme/app.git/git-receive-pack只在送往 GitHub 時換上真權杖網際網路registry.npmjs.orggithub.com模型供應商攻擊者的 C2:從未連線保存在 Mac 上,VM 之外真正的 GitHub 權杖 · 雲端金鑰 · SSH 私鑰 · 你的家目錄 · 檢查點 · Security Timeline
在 Bromure 機器裡做同樣的安裝。存在時間閘門讓 npm 看不到 0.5.144。如果酬載還是執行了,它偷到的是替身,它的推送和建立儲存庫都要等 Mac 上的對話框,而人質監控程式握著的權杖,GitHub 只在經過 Bromure 的代理伺服器時才接受。

是誰裝了它?

遇到這類事件,第一個問題是團隊裡有沒有哪台機器拉了那個壞版本。在筆電上,你得翻 lockfile、 npm 快取和 shell 歷史紀錄,再祈禱蠕蟲沒有清掉自己的痕跡。

在 Bromure 裡,不論有沒有開啟任何供應鏈防護層,每次套件下載都是 Security Timeline 裡的 一列:npm · [email protected],附上機器、時間和判定。這些紀錄在 Mac 上保存 90 天,VM 裡的任何東西都改不了。用 Supply chain 篩選、搜尋那個版本,答案就有了。Export… 能把 它存成 CSV 附進事件工單。在註冊到 bromure.io 的 Mac 上,同一次下載還會以 supply_chain.fetch 事件送進組織的事件串流,讓資安團隊一次就能替所有開發者問這個問題。

壞版本從沒出現過

存在時間閘門預設開啟、最少兩天,會從 npm 的版本清單裡拿掉太新的版本。10 月 8 日, npm install tensorlake 解析到的是 0.5.143。

被偷的權杖是替身

GitHub、npm、雲端和 SSH 憑證都留在 Mac 上。蠕蟲挾持的人質,是一個只有經過 Bromure 代理伺服器才有效的 ghp_ 值。

擴散需要你點頭

建立儲存庫和推送 commit 都是寫入。在新機器預設的 Prompt before write 下,兩者都要等一個 VM 無法回應的 macOS 對話框。

抹除只影響一個 VM

rm -rf ~/ 碰得到的是機器的家目錄,不是你的。Rewind home 會從上次開機時建立的檢查點 把它還原。

下一套流程

Shai-Hulud 今年一再捲土重來,它的作者會依照防守方上一次的處理方式來設計每個新版本。防守方 幾個小時內就撤銷權杖,所以這一版懲罰撤銷。下一版會懲罰別的事:執行清理腳本、刪除 LaunchAgent、再次打開專案。

在筆電上,這些花招每一個都管用,因為攻擊者在你正要清理的同一台機器上握有真東西。在 Bromure 機器裡,真正的憑證、你的家目錄和事發經過的紀錄,全都在 Mac 上,在代理伺服器後面。清理就是 倒轉,而且你可以在輪換任何東西之前就完成。想進一步了解背後的線路邊界,請讀 安裝過程很乾淨 和 每次逃脫四萬美元。