他們換掉的是位址,不是套件
2026 年 8 月 31 日的十四個小時裡,部分送往 registry.coder.com 的請求,是由攻擊者加進 Coder 自家 Cloudflare 來源池的伺服器回應的。主機名沒錯,憑證通過驗證,模組帶著一直以來的名稱與版本,而回傳的程式碼開始四處尋找 SSH 金鑰、OIDC 權杖與資料庫密碼。Coder 手上沒有攻擊者的日誌,所以它的公告要你自己去檢查對外流量。這個問題,大多數跑著編碼代理的機器都答不出來。
主機名是 Coder 的。憑證通過驗證。模組帶著你的範本早就釘住的名稱與版本。8 月 31 日的十四個小時裡,其中一些回應的伺服器屬於別人。
一位平台工程師在週一早上更新工作區範本。terraform init 從
registry.coder.com 抓下範本指名的模組(Docker、Zed、aider),跟上個月同一個
地方。Terraform 印出它慣有的一片綠色,建置成功。那天早上、那整個月,沒有任何
工具示警。
9 月 1 日,Coder 發布了 GHSA-vx42-ghc9-gw65, 評為重大,CVSS 9.0。用它自己的話說:「一名身分不明的惡意行為者取得了 Coder 的 Cloudflare 基礎設施存取權,並在 Coder 模組登錄檔所使用的來源池中加入了未經授權 的 IP 位址。」在 8 月 31 日(週一)07:35 至 21:45 UTC 之間,部分對 Coder 模組的 請求是由攻擊者控制的機器回應的。Coder 打造自架的雲端開發環境; BleepingComputer 列出 Dropbox、Palantir、Square、Mercedes-Benz、KKR、EnBW、美國政府單位與國防承包商 都在它的使用者之列。
沒有人動過套件
一支謹慎團隊所有的供應鏈習慣,檢查的都是產出物。名字拼對了嗎。發布者是不是他 所宣稱的那個人。這個版本是昨天才發的嗎。鎖定檔的雜湊還對得上嗎。升版之前先讀 差異。
攻擊者改的是:當你的客戶端去問位址時,由哪些機器回答;產出物本身他沒碰。他沒有
發布任何套件,沒有接管任何維護者帳號,沒有註冊任何相似名稱,也沒有編輯 Coder
登錄檔裡的任何東西。名稱一如既往地解析成功。TLS 交握是對著一張為真實主機名簽發
的有效憑證完成的。模組就叫 docker、zed、aider,因為那正是 Coder 範本會要
的模組,而且它帶著範本早就釘住的版本號。
不一樣的是回傳的內容。被下毒的副本夾帶一個叫 dlp.sh 的 shell 腳本,為 docker、
aider、rstudio-server、windows-rdp 與 zed 各模組各自建了一份,公告裡也各自附了
雜湊。公告把這個酬載描述為「設計用來辨識若干憑證,並將其外傳到一台遠端伺服器,
該伺服器的網域名稱與 Coder 經常使用的某個網域極為相似」的程式碼。那個網域是
coder-infra[.]com,8 月 28 日註冊,比任何人用到它早了三天,解析到
199.91.220.205,回呼位於 http://www[.]coder-infra[.]com/cli/check。
它下手的清單,讀起來像一份開發平台信任的盤點:佈建器的環境變數與機密、使用者的 OIDC 權杖、SSH 金鑰、外部認證憑證,以及 coderd 的設定變數,含資料庫密碼與認證 供應商憑證。
你不需要做任何不尋常的事就會落在那個時間窗裡。依公告所言:「使用 Coder 主要模組 登錄檔(registry.coder.com)作為範本來源,且在這段程式碼可取得的期間內更新過元件 的使用者,可能已安裝了惡意套件。」建立範本、升一版範本、跑一次 dry run,或在關掉 模組快取的情況下建置工作區,都是再普通不過的週一工作。
「檢視防火牆、代理、DNS 與 VPC 流量日誌」
這是 Coder 補救步驟的第五項,也是整份公告裡最有啟發性的一行。第一到第四項是 Coder 幫得上忙的事:找出時間窗內被快取模組的 SQL、清掉它們的 SQL、修補過的版本 (2.37.0、2.36.4、2.35.7 與 2.34.9,全都在 9 月 1 日釋出),以及一份該輪替的憑證 清單。第五項伸到這一切之外,因為能說出誰中招的那些伺服器屬於攻擊者。Coder 看不 到它們,而且直說了:它無法確定地指認每一個被入侵的部署。
於是問題回到你身上,而這是一個關於證據的問題。跑過那個模組的機器上,有任何東西
連過 coder-infra[.]com 嗎?對於一個正式環境的 VPC,多數團隊一小時內就能回答,
因為平台團隊裡已經有人擁有流量日誌。
現在,換成你的編碼代理工作的那台機器再問一次。代理會跑 terraform init、
npm install、make、一次容器建置、一輪測試、一支它在儲存庫裡找到的腳本、一個
某模組下載回來的二進位檔。這每一件事都會開啟連線,而沒有人把它們寫在你一週後能
搜尋得到的地方。代理自己的逐字記錄留著模型發出的工具呼叫,卻沒有任何一行記錄
Terraform 模組裡的 shell 腳本對網路做了什麼,因為模型根本沒看見那件事發生。
第五步的答案沒辦法事後再買。要嘛在請求離開機器的當下就有東西把它記了下來,要嘛 根本沒有東西可搜。
那支腳本本來會找到什麼
就照酬載的說法,讓它跑。它掃過環境找憑證,讀走 SSH 金鑰,抓走 OIDC 與外部認證
權杖,撿起裡面帶著資料庫密碼的設定變數,然後把整包送往 coder-infra[.]com。
Bromure Agentic Coding 給每個工作區一台跑在 Apple hypervisor 上的專屬 Ubuntu 虛擬機,並以一個主機端代理作為客體通往網路的唯一出口。在那裡跑 同一輪掃描,它會帶回完整一套憑證,而且全是假的。
ANTHROPIC_API_KEY 裡放的是 sk-ant-api03-brm-… 誘餌,GH_TOKEN 是長度正確的
ghp_ 字串,~/.git-credentials、~/.docker/config.json 與 ~/.kube/config
裡放的是相對應的佔位值。格式都保留著,所以 gh、docker 與 kubectl 都會照收
不誤。代理把你真正的值加密存在你的 Mac 上,等請求離開虛擬機之後才在線上換進去,
而且只在請求要去的正是這份憑證所簽發的那台主機時才換。沒有開關能關掉這件事;在
這個 app 裡,憑證本來就是這樣運作的。
SSH 金鑰是最乾淨的例子:虛擬機裡根本沒有金鑰可讀。SSH_AUTH_SOCK 指向一座跨越
虛擬 socket、通往主機上代理程式的橋,而那個協定裡有一種請求叫「幫我簽這個挑戰」,
沒有任何一種請求叫「把金鑰交給我」。ls ~/.ssh/id_* 回來是空的,而 git push
照常運作。
AWS 值得自成一段,因為 Terraform 面對的正是它。虛擬機的 ~/.aws/config 指向一個
輔助程式,它吐出的是你真實的 access key ID,配上一把假的 secret key。
Terraform、AWS 的各種 SDK、boto3 與 aws CLI 都不必額外設定就會讀這個輔助程式,
接著用假鑰簽署請求,產出一個格式完整、但 AWS 會拒絕的簽章。主機在出去的路上剝掉
那個簽章,用真正的密鑰重新簽一次。一支在環境裡翻找 AWS 素材的腳本,帶走的是一個
key ID 和四十個什麼都認證不了的字元。
接著它得把戰利品送去某個地方,而工作區的對外防火牆就坐在那裡。把 Unmatched
traffic 設成 Deny,虛擬機就只能連到你列出的主機,別的都不行。強制執行發生兩
次,在客體構不到的兩個位置:虛擬網路交換器按目的 IP 與從 DNS 側錄到的主機名比對
每一條流量,跨所有協定;代理再依 TLS 伺服器名稱比對一次。沒有人會把一個三天大的
網域放進那份清單。規則的修改不必重開機就能送達執行中的工作階段,所以你可以在
建置還在跑的時候補上 deny any coder-infra[.]com。
如果流量還是出去了,可能是工作區跑在預設的全部放行姿態下,也可能是目的地看起來
太普通,那麼誘餌就會做它的第二份工作。代理會掃描每一個對外請求的標頭與內容,尋找
一份正要送往「當初並非為它簽發之處」的假憑證。一個寄往 coder-infra[.]com 的
sk-ant-api03-brm-… 字串毫無模稜兩可之處。代理會在任何一個位元組抵達目的地之前
以 HTTP 451 擋下,暫停虛擬機,並發出一則點名該憑證與兩端主機的警示。Bromure 把
這個工作區標記為已遭入侵,在你抹掉它的磁碟與家目錄之前,它不會再開機。
第五步的答案在事情發生的當下就到手,而不是一週後從日誌裡拼回來。
一道指令回答那份公告
主機代理會為每一個離開虛擬機的請求寫下一筆中繼資料紀錄,不論是哪個行程發出
的:時間、主機、連接埠、方法、路徑、狀態、延遲,以及在任何憑證替換之前量到的
位元組數。把工作區的 Session trace 設成 Activity only,你會拿到這筆
紀錄,而且完全不儲存任何請求內容。接著
bromure-cli trace hostnames my-workspace 就會把這個工作區接觸過的每一個不同
主機連同次數一併印出來。Coder 的第五步,一行搞定,針對那台真正跑過程式碼的
機器。
各個引擎做了什麼判斷
Security Timeline(Window → Security Timeline…)是另一個視圖,看的是 Bromure 的引擎判斷了什麼:每一次對外防火牆的裁決、每一份代理居中處理的憑證、 每一次供應鏈攔阻,全部標上顏色、可以過濾。一條連往三天前才註冊網域的被拒連線, 會在建置還在跑的時候就以一列紅色的 Firewall 出現在那裡,而不是等別人的公告告訴 你該往哪看之後,再去 grep 一個月份的流量日誌。
客體能碰到你 Mac 上的只有一樣東西:共用資料夾,一扇通往你真實檔案系統的即時
讀寫窗口,而入侵後的抹除依設計不會動它。分享專案目錄而不是你的家目錄,那麼一支
獵捕 id_rsa 與 .env 的腳本,走遍的會是一個塞滿簽出與套件快取的 Linux 家
目錄。
當你無法檢查來源,就盯住出口
所有來源驗證最終都化約成同一個動作:把送到的東西,跟你已經信任的某樣東西比對。 一個名字、一個發布者、一份簽章、鎖定檔裡的一個雜湊。只要攻擊者還必須往通道裡放 點什麼給你比對,這個動作就有效。握住通道一段的攻擊者什麼都不放進去,而 Coder 的 來源池就是通道的一段。8 月的 BdThemes 入侵是同一個形狀, 一個被挾持的儲存桶改掉了 35 萬個 WordPress 網站會去抓的 JSON 資訊流,沒有任何發行,也沒有任何檔案被 改動。
十四個小時就是一個工作天。Coder 抓到了,隔天早上就送出修補,公布了雜湊與指標, 也告訴讀者該檢查什麼。但這份公告最後仍然要求每一位讀者,自己去看自己的對外流量, 因為另一端的紀錄已經沒了。
輪替公告列出的金鑰、清掉快取的模組、套上修補。然後決定下一次要讓它碰到什麼, 因為下一次會從一條你從來沒有理由懷疑的通道進來。 安裝 Bromure Agentic Coding,給建置一台自己的機器,等公告出現 的時候,答案已經備好了。