那份文件點名了一個沒有人擁有的套件
研究人員掃描了 6,214 個企業網域,尋找 llms.txt,也就是各家公司如今為 AI 代理人發布的那種機器可讀檔案,並在這些檔案裡找到 237 個被點名、卻從來沒有人註冊過的套件與網域。他們把其中幾個註冊了下來。某個 Fortune 500 網路內部的第一個編碼代理人,在不到四分鐘內就裝上了其中一個。攻擊者沒有碰過任何維護者、任何登錄庫、任何儲存庫。Bromure Agentic Coding 的年齡門檻預設就開著,設在兩天,而攻擊者取走的名字,就結構而言必然是一次全新的發布。
一家廠商把某個自己從未發布過的套件的安裝指令,寫進了自己的文件裡。那個名字就 空在那兒,誰都能拿走。編碼代理人讀了文件,相信了它,然後執行了安裝。
過去這兩年的某個時候,你的開發工具開始隨附一個新檔案。它就放在 robots.txt
旁邊,叫做 llms.txt,裡面裝著一份寫給機器而非寫給人看的產品純文字摘要。這個
API 做什麼。哪些端點重要。要開始用得先裝哪個套件。編碼代理人會去抓它,而且因為它
來自廠商自己的網域,代理人就把它當成板上釘釘的事實。
一位名叫 Alon Hertz、以 PANDEX 之名工作的研究者,去看了這些檔案到底叫代理人做
什麼。他的方法只有三步,沒有任何漏洞利用:從企業目錄拉下 llms.txt 與
llms-full.txt,在沙箱裡拿真正的代理人去跑它們、看代理人伸手去拿什麼,然後把跑出來
的每一個套件名與主機名,拿去 npm、PyPI 以及各網域登錄機構比對。他在
whatwouldai.do 上說明了這套工具,而網站上那句標語,最後
成了整件事的結論:代理人會對任何東西動手。
這次掃描涵蓋 6,214 個運作中的網域,來自 Fortune 500 企業、大型科技公司、金融科技 與國防承包商,以及超過 8,000 份這類檔案。其中有 120 份檔案裡,那個叫代理人去安裝的 東西根本不存在。在整個語料庫中,有 237 個被點名的套件名與主機名是空的:在 npm、 PyPI、RubyGems、NuGet、crates.io 與 Packagist 上可以隨手註冊,或在 Vercel、 Render、Fly 與 Netlify 上是空著的子網域。任何人只要付出註冊一個帳號的代價,就能 拿走它們。
於是研究人員挑了幾個,用那些名字發布了無害的套件,裡面放一個安裝時會回家報到的 信標,然後等著。
第一個回呼在不到四分鐘內就到了。第二個在一小時之內。接下來的一段時間裡,他們 記錄到來自數十個組織的安裝,其中好幾個是 Fortune 500 的環境,驅動它們的是跑在 Anthropic 的 Claude、OpenAI 的 Codex 與 Nous Research 的 Hermes 上的代理人。Bruce Schneier 在 9 月 4 日接手了這份研究, 並且用它應得的方式來讀:資料與可執行程式碼之間的界線正在崩塌。那篇貼文引用的一位 研究者用的字更少。「信任模型壞了。」代理人「把廠商文件當成基準事實,而且不會質疑 它」。
Clerk 為一個從未出貨的套件寫了文件
最清楚的例子來自一家身分驗證廠商,它的文件告訴讀者去執行:
npx clerk-next-fix-auth-protection
Clerk 從來沒有以這個名字發布過獨立套件。也許那個指令是個簡寫,也許它指向某個內部
的東西,也許是寫文件的人弄錯了。原因不重要,因為對一個代理人來說,那個名字讀起來
就是真的。別人把它註冊了。安全通報資料庫現在把那個套件列為 MAL-2026-11069,而它
做的事正是你會為偵察而打造的那種:npm 的安裝掛鉤觸發,把執行安裝的帳號名稱、機器
的主機名稱、工作目錄與一個時間戳記送了出去。
那是某個人建置環境的一張地圖,在那個人的網路內部,由那個人自己的編碼代理人蒐集,而 代理人當時正照著那個人自己的廠商文件在走。
為什麼平常那套建議會直接指向酬載
每一份供應鏈指南都叫你拿套件名字去對照官方文件。這個建議在這裡失效,因為那個壞 名字正是從官方文件裡來的。
把我們七月寫過的問題反過來,你就得到這一 個。在那裡,代理人幻想出一個看起來合理的套件名,攻擊者去註冊它慣常會編出來的那些 名字,所以脆弱的那一步是模型的猜測。在這裡模型什麼都沒猜。它跑去找廠商,讀廠商說 了什麼,照著做,而正是那個一絲不苟的順序把它送進去的。
登錄庫被入侵也是另一種形狀。上個月當 一條發布流程把某個真實套件的十個下毒版本推了出去 時,得先有東西壞掉:一條相信了留言的流程。在這裡什麼都沒壞。一位技術寫作者打了一 個跟已發布產物不完全對得上的指令,這是軟體業裡最尋常的失誤,而名字與產物之間的 那道縫隙,變成了網路上任何人都能撿起來用的執行原語。
接住這件事的那一層,已經開著了
Bromure Agentic Coding 讓每個編碼代理人跑在你 Mac 上一台硬體虛擬化的 Linux VM 裡, 而代理人抓取的每一個套件,都會先經過那道邊界主機這一側的代理伺服器,在任何一個位元 組抵達 VM 之前。那裡套用了好幾層。就這個故事而言,其中一層自己就能把事情辦完,而 且那正是一個新工作區已經開著的那一層。
年齡門檻讓一個剛被取走的名字變成非事件。 工作區會拒絕比某個界線更新的套件版
本,出廠設定是兩天。代理伺服器透過改寫途經的登錄庫版本清單來執行這件事:它移除比
界線更新的版本,並把 latest 重新指向倖存下來的最新版。今天早上才第一次發布的套
件,沒有任何一個版本老得足以熬過這道篩子,所以從代理人的角度看,那個名字底下根本沒
有東西可裝。如果代理人直接指名要一個太新的版本,抓取會以 HTTP 451 回來,本文寫著這
個套件真實的年齡以及所要求的最小值,而 npm 會原封不動地把那段文字印出來。
帶著這一層再讀一次這場攻擊。整套手法都倚賴一個名字在攻擊者選定的時刻,從未發布變 成已發布。那個轉變就是一次全新的發布。攻擊者唯一避不開的性質,正是預設政策所盯 住的那一個。
安裝腳本剝除,拿掉的正是酬載所用的那個機制。 被觀察到的那個套件,是在 npm 的
preinstall、install、postinstall 與 prepare 掛鉤裡做事的。開啟腳本剝除
後,代理伺服器會在傳輸途中把每個 npm tarball 解壓,從 package.json 刪掉那四個
鍵,把 dist.integrity 與 dist.shasum 從中繼資料裡抹掉,好讓 npm 自己的雜湊檢查
仍然通得過,然後重新壓縮。套件裝得起來;信標卻沒有地方可以跑。真的需要掛鉤的套
件,例如 better-sqlite3 這類原生繫結的編譯器,可以放進每個工作區各自的允許清
單,保留它們的掛鉤。
信譽過濾認得這個套件的名字。 MAL-2026-11069 是一則惡意軟體通報。把 socket.dev
選為工作區的套件過濾器之後,它的受害套件封鎖會在任何嚴重程度的惡意軟體與仿冒名稱
訊號上觸發;免費的 OSV 檢查則不需要 API 金鑰,就涵蓋全部八個被攔截的生態系。當某
個信譽來源給不出判斷時,不論是因為速率限制、網路中斷,還是因為它不涵蓋那個生態
系,Bromure 都會朝關閉的一側失敗,而不是放行這次抓取。下載會暫停下來問你,逐個套
件、逐個版本地問,所以一個有辦法誘發查詢失敗的攻擊者,什麼也拿不到。
它蒐集到的偵察資訊屬於那台 VM。 關於這份研究的每一個標題都在說同一件事:代理人 正在企業網路上安裝來歷不明的程式碼。信標的收穫是執行安裝的使用者名稱、機器的主 機名稱,以及工作目錄。在一個 Bromure 工作區裡,這三個欄位描述的是你 Mac 上的一台 Linux 客體,而不是一台加入網域、帶著值得放進目標清單的內部名稱的建置主機。待在那 台 VM 裡的憑證都是誘餌,真正的值只存在於主機端代理伺服器的記憶體裡。要是敵意程式 碼真的想把其中一個帶去一台它並非為之鑄造的主機,代理伺服器會擋下那個請求,並讓 VM 在半空中暫停。
預設拒絕回答了這份研究的另外一半。 那些沒人認領的名字裡,有不少是主機名稱而不 是套件:過期的網域,以及 Vercel、Render、Fly 與 Netlify 上的免費子網域,就坐在代理人 會去抓的文件裡。工作區的外送防火牆是一張帶有 Unmatched traffic 設定的有序規則 表;把它設成 Deny,VM 就只會對你列出的那些主機開連線,其他一概不通,任何協定 都一樣。有兩個元件在客體之外執行這件事:虛擬網路交換器依目的地 IP 位址比對每一條 流,主機端代理伺服器再依 TLS 伺服器名稱比對一次。一個瞄準某人上週二才註冊的網域的 信標,出不了這台機器。規則的修改不需要重開機,就會送達正在跑的工作階段。
每一次嘗試都會得到一列紀錄。 在 Activity only 這個追蹤層級上,代理伺服器會
為每一個離開 VM 的請求寫下一筆中繼資料紀錄,帶著時間戳記、主機、連接埠、方法、路
徑、狀態、延遲與位元組數,而且完全不儲存任何請求主體。
bromure-cli trace hostnames my-workspace 會把這個工作區聯絡過的每一個不同主機都
印出來,並附上次數。你會在事情發生的當天,在自己的機器上,看到一個你手下工程師沒
有人認得的回呼網域,而不是幾個月後在別人的資料集裡讀到它。
已經開著的東西
一個新工作區出廠時,年齡門檻就是開著的,最小值兩天,而這場攻擊撞上的正是這個設
定。如果你的工作等得起,把它往上調,在步進器上最高可到 90 天;至於你自己團隊
當天發布、當小時就要安裝的那些套件,用 Exempt packages,寫成
npm:our-package 的形式。
今天值得打開的東西
在工作區的 Supply Chain 面板裡,開啟安裝腳本剝除,並挑一個套件過濾器。在 Guardrails 裡,把 Unmatched traffic 設成 Deny,並列出這份工作需要的 主機。在 Tracing 裡,Activity only 會保留聯絡過的主機軌跡,內容一點都不 留。上面每一項修改,都會套用到已經在跑的工作階段。
沒有人會去校對一份寫給機器看的檔案
PANDEX 的掃描器還在找另一樣東西。除了無人認領的套件與網域之外,它也在找那些寫給
代理人、而不是寫給讀者看的指示:埋在檔案裡的指令,有時還用 Unicode 的把戲與零寬字元
藏起來。廠商的建置流水線生出 llms.txt,你的代理人把它吃下去,而在這兩家公司裡,都
沒有人讀過在它們之間傳遞的是什麼。
Bromure 把抓回來的頁面當成不可信的輸入。代理人從外面拉進來的內容,不論是檔案內容、
網頁、issue 的本文,還是指令輸出,都會以 tool_result 區段的形式抵達模型。開啟原
始碼偵測器之後,一個跑在你 Mac 上的 PromptGuard 模型會在模型據此行動之前,於傳輸
途中為這些區段評分,而你來決定它是記錄、詢問,還是封鎖。沒有任何內容會被送到別處
去掃描。
我們花了十年學會驗證產物:為套件簽章、釘住雜湊、為建置做證明。這份研究講的是這一 切之前的那一步,在那裡,一個代理人決定要去要哪一個產物,而它依據的是一份沒有任何簽 章、由一位趕時間的技術寫作者寫出來的文件。文件已經變成一種可執行的輸入,而各個登 錄庫裡滿是空著的名字,就等著有人注意到某家廠商提過它們。
拿 grep 掃一遍你自己的 llms.txt,找出那些你從來沒有發布過的名字。那是一個上午的
工作量,而且能把你從別人的目標清單上拿下來。然後給你的代理人一台機器,在那上面,四
分鐘前才發布的套件裝不起來,而且你看得見它去過的每一個地方。
安裝 Bromure Agentic Coding,把安裝這條路徑放到主機上。