一次工作階段,一百個儲存庫
Mandiant 的 AI Risk and Resilience 2026 報告描述了一名攻擊者:他接管了某 SaaS 供應商一個正在執行的 AI 編碼助理工作階段,藉此讓對方裝上被下毒的 PyPI 套件,竊走 GitHub OAuth 權杖,並把 Shai-Hulud 蠕蟲散布到約 100 個內部儲存庫。報告沒有說工作階段是怎麼被奪走的。在 Bromure Agentic Coding 的工作區裡,它也不需要說:那個工作階段裡的權杖是 `ghp_` 誘餌,想把它送出去就會讓虛擬機暫停,而把一台機器變成一百個儲存庫的那些推送,會停在主機端的代理上。
有人接管了一個正在執行、已通過驗證、而且受信任的編碼工作階段,就在某位 開發者自己的機器上。之後發生的一切,一路到一百個儲存庫,全都出自那台機器 當下所握有的東西。
某家軟體即服務公司的開發者問他的編碼助理,某個再平常不過的問題該用哪個套件。
助理說了一個。開發者說好,而這正是擁有助理的全部意義,剩下的交給
pip install。
那時攻擊者早就握著這個工作階段,打出那則建議的人就是他。
Mandiant 在 AI Risk and Resilience Report 2026 中描述了這個案例,這份報告由 Google Cloud 的事件應變部門於本月發布; The Hacker News 在 9 月 16 日作了報導。 攻擊者劫持了開發者工作站上一個正在執行的編碼助理工作階段。再走五步,他 就抵達了大約一百個內部程式碼儲存庫,以及公司自己的套件命名空間,第二位員工 在那裡拉下了被下毒的版本。
一環一環看這條鏈
Mandiant 偏偏扣住了你最想知道的那一段。公開的案例研究沒有說入侵發生在什麼 時候,也沒有說攻擊者是怎麼拿到一個執行中工作階段的控制權。被竊的瀏覽器 cookie、惡意擴充套件、共用機器上被入侵的 CLI,報告一個也沒挑。想在裡面找到 修補方式的人,只能空手離開。
第 2 到第 6 步完全沒有任何漏洞利用。套件安裝就照套件安裝的方式運作,以開發者
身分執行的程式碼就去讀開發者磁碟上擺著的權杖。帶著有效權杖的 git push 就
推得上去,帶著有效發布權限的發布就發布得出去。只有一步是入侵,另外五步都是
在花錢。
乘數是憑證,而且還在變大
Shai-Hulud 是那隻自我複製的供應鏈蠕蟲,從 2025 年起一路吞食維護者帳號;它會
在這樣的故事裡當作酬載出現,是因為第 5 步正是作者當初造它的目的。GitGuardian
在八月拆解了一個近期變種,
發現它會掃過 469 個憑證所在位置,
比早期版本的 189 個更多:開發環境、CI/CD 工具、雲端設定、AI 工具設定、套件
管理器的設定檔、shell 歷史、.env 檔案、IDE 設定、CLI 快取。這隻蠕蟲不是去
獵捕漏洞,而是去讀一整個檔案櫃。
GitGuardian 的分析用一句話講完了這個類型:
「攻擊者已經不再試圖打破信任關係,而是開始使用那些本來就讓這些關係得以運作 的憑證。」
一台工作站之所以變成一百個儲存庫,是因為它握有價值一百個儲存庫的授權。那個 數字出自一份清單,而要往清單裡放什麼,由你決定。
那個工作階段握著的是誘餌
Bromure Agentic Coding 讓代理在你 Mac 上的 Linux 虛擬機裡執行,而那台機器不 握有任何真正的機密。你不需要打開任何開關才有這個效果。工作區存放憑證的方式 本來就是如此。
你真正的 GitHub 權杖加密留在主機上。虛擬機收到的是一份保留結構的假貨:
ghp_ 後面接 36 個字元,總共 40 個,所以 gh 自己的前綴與長度檢查都會順利
通過。Bromure 把它匯出成 GH_TOKEN,並寫進 ~/.git-credentials 和 gh 的
設定裡;那是整台機器裡唯一的 GitHub 憑證。主機上的代理會在請求離開虛擬機之後
才在線路上換上真正的值,而且只在目的地符合該憑證所鑄給的主機時才換。每一份
假貨都是決定性的,由真值加上每次安裝專屬的 32 位元組鹽,經 HKDF-SHA256 推導
而來,所以會對自己金鑰取指紋的客戶端,在不同工作階段之間看不出任何輪換。
拿第 4 步來對付它看看。竊密程式抵達,掃過那 469 個位置,找到環境變數,找到
~/.git-credentials,找到 gh 的設定,然後把找到的東西寄出去。每一次讀取都
成功,每一個檔案都躺在蠕蟲預期的地方。攻擊者最後拿到的,是一串四十個字元、
在任何地方都無法通過驗證的字串,除非它再穿回某一台特定的 Mac。
第 5 步和第 6 步都需要第 4 步交出一個能用的權杖,而第 4 步交出的是誘餌。
竊取本身就是警報
一份假權杖只有一個正當的去處。不會有任何無辜的請求,把鑄給 github.com 的
ghp_ 誘餌送往別的主機,所以代理會掃描每一個外送請求的標頭與內容,找出任何
離開自身範圍的假貨。它跑的是 Aho-Corasick 自動機,便宜到足以對準全部流量。
當代理找到一個,它做的不只是記錄:
- 它以 HTTP 451 拒絕該請求,一個位元組都到不了目的地;
- Bromure 當場把虛擬機暫停;
- 警示會給你 關機、保留以供調查(先把磁碟、家目錄與共用資料夾匯出以 便鑑識)或自負風險的 繼續;
- Security Timeline 多出一列紅色的 Credential brokering;
- Bromure 把該工作區標記為已遭入侵,於是下次啟動會抹掉磁碟映像與持久家目錄。 你的權杖、SSH 金鑰與工作區設定都能挺過這次抹除。
這些你都不用去開;偵測器一直開著。第 4 步的外送嘗試本身就是讓工作階段停下來 的那件事,所以這起事件的結局是一台暫停的虛擬機和一筆時間軸紀錄,而不是一百 個儲存庫加一份案例研究。
一百個儲存庫就是一百次推送
假設你連保險都想多上一層。第 5 步是蠕蟲往儲存庫裡寫,而主機會把寫入分類。
工作區的護欄為每個服務帶著一份寫入政策。對 GitHub 而言,
這份政策不只讀 REST API,也讀 git over HTTPS:git push 以
git-receive-pack 抵達,算成寫入;git fetch 以 git-upload-pack 抵達,算
成讀取。唯讀會拒絕這些推送。寫入前先詢問是新工作區的預設值,它會把每
一次推送都攔下來,交給主機端一個寫明操作名稱的對話框。兩種設定下 fetch 都能
通過,所以代理照樣能工作。
做這個判斷的是主機上的代理,在虛擬機之外。一個工作階段已經屬於別人的代理, 既關不掉這份政策,也繞不過去,因為政策根本不在代理那一側執行。一隻想要一百個 儲存庫的蠕蟲,會收到一百次拒絕,或者問你一百次。
而且套件終究得先送到
第 3 步正是 Mandiant 給出具體建議的地方:拿密碼學校驗碼與允許清單去驗證 AI 推薦的第三方相依套件,並讓相依流量走你自己掌控的儲存庫。
在工作區裡,每一次套件擷取本來就會經過主機,因為代理是虛擬機通往網路的唯一
路徑。Bromure 在 pypi.org 與 files.pythonhosted.org 攔下 PyPI,並在那裡
套用主機的政策。虛擬機裡的 pip.conf 可以把代理送出來的東西收得更緊,但放不
鬆。有三層會替 Python 套件發言:
- 年齡門檻,預設開啟,最短兩天,會拒絕比門檻更新的版本;理由是剛發布的 版本,正是最可能剛被劫持的那一個。PEP 503 索引不帶時間戳,所以 Bromure 會 即時去查發布時間;
- OSV 檢查,對
api.osv.dev查詢,免費且不需金鑰,會擋下任何帶有你所選 嚴重度以上安全公告的版本; - socket.dev 的受害套件過濾,對惡意軟體、已知惡意軟體、搶註相似名稱與 流氓安裝腳本觸發。
封鎖會以 HTTP 451 回來,內容開頭是
Bromure Supply-Chain Security blocked this request: 接著是原因,而 pip 會
一字不改地印出來。你看得到安裝為什麼失敗,代理人也看得到,它往往能自己去釘一
個較舊的版本。那個較舊的版本,正是年齡門檻一直想把它導向的那一個。
Mandiant 的三項控制,以及它們住在哪裡
報告以三項建議收尾這個案例研究。每一項都指向一個 Bromure 工作區早已把界線 畫好的位置。
驗證 AI 推薦的相依套件
Mandiant 要求對助理建議的任何東西都做校驗碼與允許清單。你的工作區會在 虛擬機看到登錄檔回應之前,就先對它套用政策。代理會把太新的版本從版本清單 裡拿掉,所以在代理人眼中它們還不存在;若是直接指定了太新的版本,它會拒絕, 並在錯誤訊息裡引出該套件的真實年齡。信譽服務的金鑰留在主機上,不在那台 執行安裝的機器裡。
讓原始金鑰與長效權杖離得遠遠的
這就是把線路上的界線寫成建議的樣子。虛擬機裡沒有任何檔案、環境變數或行程 握有真正的 API 金鑰、OAuth 權杖、AWS 機密或 SSH 私鑰。Bromure 把訂閱的 OAuth 權杖加密存在主機上,並在到期前約五分鐘於主機端更新,所以客體端不帶 任何更新權杖。兩者之間的通道只往一個方向走:主機把假貨寫進去,而虛擬機沒有 任何呼叫可以回頭索取真的權杖。
讓相依流量走你自己掌控的東西
代理是虛擬機對外的唯一路徑。每一個 HTTPS 請求都經由虛擬通訊端送到主機,由 主機終結 TLS、檢查請求,再用 macOS 自己的 TLS 堆疊重新送出。正是這條唯一 的路徑,讓另外兩項控制從建議變成強制。沒有任何套件擷取能跳過檢查,也沒有 任何憑證能不經替換就離開。
還有,刻意把波及範圍縮小
一百個儲存庫會倒,是因為一台機器帶著一百個儲存庫的授權。把工作區切成一個 專案一個,或一道憑證界線一個。每一個對應唯一一台虛擬機,沒有兩個共用一台, 而且各自只帶那個工作區的工作所需要的憑證。任何一台機器上的清單,都在一個 設定面板裡由你決定。
沒辦法修補的那一部分
Mandiant 用一句描述形狀而非事件的話,框住了這個類型:「一個被下毒的資料來源、 模型相依或擴充掛鉤,都可能把一個受信任的代理變成內部偵察、橫向移動,或是自 沙箱自主逃逸的通道。」
三個不同的入口,一句話,而且三個都適用。入口決定攻擊者怎麼進來;某位開發者 機器裡的東西,決定他能走多遠。這次的攻擊者是從被劫持的工作階段進來的。下一個 會從別的地方進來,而報告沒有替你指出任何一扇該鎖的門。
為入口之後的那一步做設計。假設這個工作階段已經屬於別人——這正是案例研究藉著 始終不解釋第 1 步,強加給你的假設——然後再問,那個工作階段能碰到什麼。
這起事件的其中一個版本結束在第 3 步:終端機裡一個 451,Security Log 裡一行 紀錄。另一個版本結束在第 4 步:一台暫停的虛擬機,加上時間軸上一列紅色紀錄, 記著有一份憑證試圖離開。兩個版本都到不了第 5 步,而那一百個儲存庫就在第 5 步。
把代理放在一台只握著誘餌的機器上。 安裝 Bromure Agentic Coding,然後儘管讓人把工作階段拿走。