撤銷權杖,就是引爆點
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 權限的有效 權杖。
在 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… 能從最後一個檢查點把機器的家目錄找回來。
是誰裝了它?
遇到這類事件,第一個問題是團隊裡有沒有哪台機器拉了那個壞版本。在筆電上,你得翻 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 上,在代理伺服器後面。清理就是 倒轉,而且你可以在輪換任何東西之前就完成。想進一步了解背後的線路邊界,請讀 安裝過程很乾淨 和 每次逃脫四萬美元。