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

那份文件點名了一個沒有人擁有的套件

研究人員掃描了 6,214 個企業網域,尋找 llms.txt,也就是各家公司如今為 AI 代理人發布的那種機器可讀檔案,並在這些檔案裡找到 237 個被點名、卻從來沒有人註冊過的套件與網域。他們把其中幾個註冊了下來。某個 Fortune 500 網路內部的第一個編碼代理人,在不到四分鐘內就裝上了其中一個。攻擊者沒有碰過任何維護者、任何登錄庫、任何儲存庫。Bromure Agentic Coding 的年齡門檻預設就開著,設在兩天,而攻擊者取走的名字,就結構而言必然是一次全新的發布。

一家廠商把某個自己從未發布過的套件的安裝指令,寫進了自己的文件裡。那個名字就 空在那兒,誰都能拿走。編碼代理人讀了文件,相信了它,然後執行了安裝。

過去這兩年的某個時候,你的開發工具開始隨附一個新檔案。它就放在 robots.txt 旁邊,叫做 llms.txt,裡面裝著一份寫給機器而非寫給人看的產品純文字摘要。這個 API 做什麼。哪些端點重要。要開始用得先裝哪個套件。編碼代理人會去抓它,而且因為它 來自廠商自己的網域,代理人就把它當成板上釘釘的事實。

一位名叫 Alon Hertz、以 PANDEX 之名工作的研究者,去看了這些檔案到底叫代理人做 什麼。他的方法只有三步,沒有任何漏洞利用:從企業目錄拉下 llms.txtllms-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 的安裝掛鉤觸發,把執行安裝的帳號名稱、機器 的主機名稱、工作目錄與一個時間戳記送了出去。

那是某個人建置環境的一張地圖,在那個人的網路內部,由那個人自己的編碼代理人蒐集,而 代理人當時正照著那個人自己的廠商文件在走。

五個尋常步驟,鏈條上沒有任何入侵1 · 廠商發布vendor.com/llms.txt寫給代理人看,不是給人看npx clerk-next-fix-auth-protection2 · 名字是空的從來沒有套件以這個名字發布過120 個檔案裡有 237 個3 · 有人把它註冊了一次普通的 npm publishMAL-2026-11069酬載藏在 install 掛鉤裡4 · 代理人讀了文件,並且相信它Claude、Codex 與 Hermes 都被觀察到執行了安裝指令來自廠商自己的網域,沒有什麼好懷疑的沒有憑證被竊 · 沒有儲存庫被入侵 · 沒有維護者被釣魚5 · 掛鉤回報給家裡使用者名稱 · 主機名稱工作目錄 · 時間戳記一張建置環境的地圖攻擊者唯一真正做的事,是讀了一份受害者的廠商刻意發布出來的檔案。
這條鏈裡沒有任何入侵。廠商發布一份點名了安裝指令的機器可讀文件;那個套件從來沒有發布過,所以名字在登錄庫上一直空著;攻擊者把它註冊下來;代理人把文件當成基準事實抓下來並執行安裝;安裝掛鉤把環境資訊回報出去。每一環都是一次正確執行的正常操作。

為什麼平常那套建議會直接指向酬載

每一份供應鏈指南都叫你拿套件名字去對照官方文件。這個建議在這裡失效,因為那個壞 名字正是從官方文件裡來的。

把我們七月寫過的問題反過來,你就得到這一 個。在那裡,代理人幻想出一個看起來合理的套件名,攻擊者去註冊它慣常會編出來的那些 名字,所以脆弱的那一步是模型的猜測。在這裡模型什麼都沒猜。它跑去找廠商,讀廠商說 了什麼,照著做,而正是那個一絲不苟的順序把它送進去的。

登錄庫被入侵也是另一種形狀。上個月當 一條發布流程把某個真實套件的十個下毒版本推了出去 時,得先有東西壞掉:一條相信了留言的流程。在這裡什麼都沒壞。一位技術寫作者打了一 個跟已發布產物不完全對得上的指令,這是軟體業裡最尋常的失誤,而名字與產物之間的 那道縫隙,變成了網路上任何人都能撿起來用的執行原語。

掃描發現了什麼,以及證據回來得多快6,214個運作中的網域Fortune 500、科技、國防8,000+份給代理人看的文件llms.txt · llms-full.txt120個檔案指向從未註冊的東西237個空名,六個登錄庫外加 Vercel/Render/Fly然後他們註冊了一批樣本,等著來自 Fortune 500 環境的第一個安裝回呼不到 4 分鐘第二個回呼一小時之內 · 整個研究期間數十個組織
這份研究的漏斗。在 6,214 個帶有面向代理人文件的企業網域中,有 120 份檔案點名了未註冊的東西,加起來是 237 個空名。把其中一批註冊下來之後,第一個來自 Fortune 500 網路內部的安裝回呼,在不到四分鐘內就出現了。

接住這件事的那一層,已經開著了

Bromure Agentic Coding 讓每個編碼代理人跑在你 Mac 上一台硬體虛擬化的 Linux VM 裡, 而代理人抓取的每一個套件,都會先經過那道邊界主機這一側的代理伺服器,在任何一個位元 組抵達 VM 之前。那裡套用了好幾層。就這個故事而言,其中一層自己就能把事情辦完,而 且那正是一個新工作區已經開著的那一層。

年齡門檻讓一個剛被取走的名字變成非事件。 工作區會拒絕比某個界線更新的套件版 本,出廠設定是兩天。代理伺服器透過改寫途經的登錄庫版本清單來執行這件事:它移除比 界線更新的版本,並把 latest 重新指向倖存下來的最新版。今天早上才第一次發布的套 件,沒有任何一個版本老得足以熬過這道篩子,所以從代理人的角度看,那個名字底下根本沒 有東西可裝。如果代理人直接指名要一個太新的版本,抓取會以 HTTP 451 回來,本文寫著這 個套件真實的年齡以及所要求的最小值,而 npm 會原封不動地把那段文字印出來。

帶著這一層再讀一次這場攻擊。整套手法都倚賴一個名字在攻擊者選定的時刻,從未發布變 成已發布。那個轉變就是一次全新的發布。攻擊者唯一避不開的性質,正是預設政策所盯 住的那一個。

安裝腳本剝除,拿掉的正是酬載所用的那個機制。 被觀察到的那個套件,是在 npm 的 preinstallinstallpostinstallprepare 掛鉤裡做事的。開啟腳本剝除 後,代理伺服器會在傳輸途中把每個 npm tarball 解壓,從 package.json 刪掉那四個 鍵,把 dist.integritydist.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 會把這個工作區聯絡過的每一個不同主機都 印出來,並附上次數。你會在事情發生的當天,在自己的機器上,看到一個你手下工程師沒 有人認得的回呼網域,而不是幾個月後在別人的資料集裡讀到它。

開發者機器上的一個代理人npx clerk-next-fix-auth-protection直接來自廠商自己的文件接下來發生的事,全都在公司網路裡登錄庫給出已發布的東西,不管多新postinstall 以開發者本人的身分執行使用者名稱、主機名稱與 cwd 都是公司事實回呼想去哪裡就去哪裡沒有針對目的地的逐一請求紀錄安裝成功了,看起來就跟代理人那天下午跑過的其他每一次安裝一模一樣第一個回呼:不到四分鐘。你要等到陌生人發表論文才會知道。一個 Bromure Agentic Coding 工作區npx clerk-next-fix-auth-protection同樣的指令,同樣的文件接下來發生的事,全都在主機上年齡門檻:不到兩天的版本被剔除今天被取走的名字沒有可安裝的東西剝除:四個 install 掛鉤被移除使用者名稱、主機名稱與 cwd 描述一台 Linux VM未匹配的外送流量依目的地 IP 拒絕拒絕是代理人讀得懂的 451,還有一列寫進 Security Log,附上套件真實年齡第一個回呼:沒有。你從自己的機器上,當天就知道。
左邊:安裝在公司網路內部的一台開發者機器上執行,直接向登錄庫查詢,安裝掛鉤帶著開發者的身分執行,而回呼離開時沒有留下任何紀錄。右邊:同樣一條指令,走過一台主機端的代理伺服器,它把剛發布的版本從中繼資料裡濾掉,若真走到那一步就剝掉掛鉤,拒絕未匹配的外送流量,並為每一個請求寫下一列紀錄。

已經開著的東西

一個新工作區出廠時,年齡門檻就是開著的,最小值兩天,而這場攻擊撞上的正是這個設 定。如果你的工作等得起,把它往上調,在步進器上最高可到 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,把安裝這條路徑放到主機上。