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

那個 hook 從未抵達模型

8 月 31 日,Token Security 在 The Hacker News 上逐一介紹了 Anthropic 為本機 Claude Code 工作階段新推出的 Compliance API 端點。它們是實實在在的進步,而那篇文章對於「記錄在哪裡停下」講得很精確:在工具真正執行前就發動的 hook、躺在磁碟上的檔案、在工作階段之外啟動的行程,以及一切由 Bedrock 或 Vertex 提供服務的東西。上週那隻 npm 蠕蟲,一輩子都活在那道縫隙裡。Bromure Agentic Coding 把探針接在 VM 與外界之間的那條線上,所以記錄下來的是實際發生了什麼,而不是有人對模型說了什麼。

一份記錄只能裝下有人對模型說過的話。一場代理工作階段裡有趣的那一半,正是 沒有人說出口的那一半。

今年大半時間裡,一位想知道 Claude Code 在某台筆電上到底做了什麼的工程主管, 只有兩個選擇:讀開發者的終端回捲紀錄,或是買一套第三方包裝工具。Anthropic 的 合規端點涵蓋 claude.ai 和 Claude Desktop,對 Claude Code 幾乎沒有著墨。

這在 8 月 11 日改變了。Anthropic 為本機工作階段推出了三個 Compliance API 端點: 一個列出工作階段,一個取得某個工作階段的中繼資料,第三個則從 GET /v1/compliance/apps/sessions/local/{session_id}/messages 拉出完整記錄。 8 月 31 日,Token Security 在 The Hacker News 發表了 一篇說明它們能給你什麼的導覽

那篇文章由 Token Security 贊助,但它依然是我讀過談「一份日誌的視界在哪裡」 最細心的說明之一。

三種區塊,以及它們涵蓋了什麼

記錄以三種區塊的形式送達。用文章自己的話說:「凡是傳達給模型的內容,都會被 記錄成三種區塊型別:text、tool_usetool_result。三者合起來涵蓋了使用者 提示、bash 指令、讀取與寫入,甚至包括 MCP 指令。」

這已經很多了:模型選過的每一條 shell 指令、它讀過的每一個檔案、它呼叫過的每一個 MCP 工具。如果你的問題是「這個代理在星期二決定做了什麼」,記錄能回答你,而且 是從 Anthropic 的伺服器回答,而不是從你正在調查的那台機器上的某個檔案。

真正在做事的是第一個子句:凡是傳達給模型的內容

視界

有四樣東西落在那道邊界之外,文章把四樣都點名了。

Hook。 「Hook 是最清楚的例子:它們在本機執行,位於模型做出決定與工具真正 執行之間,而且可以阻止某個工具執行或某個提示被送出。」 Hook 是開發者磁碟上的 一段程式碼,在模型選定某個動作到該動作真正發生的縫隙裡發動。模型從來沒聽說過 它,所以記錄也從來不會載著它。

磁碟。 「兩者都看不到磁碟上放著什麼:設定檔、已安裝的 skill 與外掛及其 .md 檔(除非它們在某次工作階段中被用到),或是在工作階段之外啟動的行程。」 一個你裝好卻從沒喚起過的 skill 不會留下任何痕跡,代理一小時前啟動又忘掉的 背景行程也一樣。

其他模型供應商。 「如果你在非 Anthropic 的模型上執行 Claude Code,你完全 拿不到 Compliance API 的涵蓋範圍,因為它只記錄與 Anthropic 模型的互動。跑在 Bedrock、Foundry 或 Google Cloud 上的工作階段不會被涵蓋。」 涵蓋範圍止於 Anthropic 的模型,於是 Codex、Gemini CLI、Grok,以及你的團隊下一季會裝的任何 東西,都被留在外面。

權限決定。 開發者對一條危險指令的核准,或是讓整段提示直接通關的繞行旗標, 都落進 OpenTelemetry,而不是合規記錄。

這一切都是正確的行為。一份錨定在模型上的日誌,記下的就是模型看見的東西。給你 下一次安全審查的問題是:你最需要看見的那些東西,落在這條線的模型那一側嗎?

Hypervisor 邊界 — 主機 proxy 與虛擬交換器VM 送出或收到的每一個位元組,不論來自哪個行程、哪個代理抵達模型的內容text · tool_use · tool_result使用者提示它選定的 bash 指令它要求的讀取與寫入MCP 工具呼叫兩條線之間在工具執行之前就發動的 hook安裝腳本與 node-gyp,同一條指令的子行程送達的 tarball,來自npm、PyPI、Cargo、MavenMCP 伺服器自己的對外請求在任何工作階段之外啟動的行程跑在 Bedrock、Foundry或 Vertex 上的工作階段兩份記錄都準確。它們回答的是不同的問題,因為它們錨定在不同的邊界上。
同一場代理工作階段外圍的兩道邊界。內側那道是模型:凡是傳達給它的內容都在 Compliance API 的記錄裡。外側那道是 Hypervisor:VM 送出或收到的一切都會穿過 Bromure 的 proxy 與虛擬交換器。有趣的區域是兩者之間的空間,hook、安裝期間產生的子行程、套件 tarball、MCP 伺服器自己的流量,以及非 Anthropic 的後端,全都住在那裡。

上週那隻蠕蟲就住在那道縫隙裡

四天前,一個每週下載量超過 15 萬次的 npm 程式碼產生器,開始夾帶憑證竊取程式。 我們 在星期六寫過那條發布鏈: 一位陌生人在某個 pull request 上留言,專案自己的發布工作流讀了那則留言、卻沒有 確認是誰寫的,於是十個帶簽章的版本就送出去了。

把那份惡意程式再讀一次,另一隻手上拿著合規記錄。追一下每個步驟會出現在哪裡。

代理執行 npm install。那是一個 tool_use 區塊,記錄裡有。之後的一切都是 子行程。

第一波根本沒有宣告任何安裝腳本。它的酬載藏在 binding.gyp 裡,也就是 node-gyp 會讀的建置描述檔,位於 node-gyp 會拿 Python 去求值的 conditions 欄位中。模型從來沒有為它輪到過一次,也沒有任何工具呼叫點到它的名字:一段建置 檔案裡的 Python 運算式,以那條被記錄成再平常不過的指令的孫行程身分執行。

跟著來的掃描,在家目錄底下走過超過 150 個 glob 樣式,尋找 SSH 金鑰、.env 檔案、雲端憑證和登錄檔權杖。這一切,沒有人問過模型半句。

然後是常駐。在每一個能碰到的儲存庫裡,蠕蟲寫下一個 .claude/settings.json, 裡面帶著一個 SessionStart hook,每當有開發者在 Claude Code 裡打開這個專案, 就執行 setup.mjs。一個 hook、放在磁碟上、在任何人徵詢模型之前發動:四個盲點 裡的兩個,擠在同一個檔案裡。挑中那個位置的攻擊者,幾乎肯定從沒讀過任何廠商的 合規更新日誌。

整段敵意窗口持續了三小時十一分鐘,其中沒有任何一部分抵達過模型。

Bromure 把探針放在哪裡

Bromure Agentic Coding 讓每個工作區以自己的虛擬機器跑在 Apple Silicon 上,離開 那台 VM 的一切都會經過一個 proxy 和一個虛擬交換器,而它們住在 Hypervisor 的 Mac 那一側。交換器把 VM 的 80 埠和 443 埠流量導進 proxy,不需要設定任何環境 變數,客體也沒有任何東西可以關掉它。因此這份記錄不依賴代理願意配合,也不依賴 代理知不知道 proxy 在那裡。

Activity only 這個追蹤層級以上,每一個請求都會得到一筆中繼資料紀錄: 時間戳記、主機、埠、方法、路徑、狀態碼、延遲、在任何憑證替換之前量到的請求 位元組數、回應位元組數、proxy 在送出時替換掉了哪些憑證,以及當請求帶著一個 並非 Bromure 簽發的 bearer 權杖時的警告。不論這個請求來自代理、來自某個 postinstall 腳本、來自某個自顧自忙碌的 MCP 伺服器,或來自某人三小時前啟動 後就忘掉的行程,這筆紀錄都存在。

Security Timeline 視窗(Window → Security Timeline…)就在旁邊,回答的是另一 個問題。Trace Inspector 告訴你代理送出了什麼;時間軸告訴你 Bromure 的引擎決定了 什麼。一張依時間排列的表格,最新的在最上面,以顏色區分:綠色代表放行,紅色 代表阻擋,藍色代表資訊——憑證代管、防火牆、供應鏈、護欄、提示注入、憑證使用、 上游 TLS。它在記憶體裡保留 5,000 列。持久的副本則是 ~/Library/Application Support/BromureAC/traces/ 底下加密過的工作階段追蹤檔, 以與你工作區機密相同的鑰匙圈金鑰用 AES-GCM 封起來。

同一次安裝,在 Bromure 工作區裡讀起來是這樣。

Security Timeline引擎它的判定20:31:02供應鏈npm [email protected] — 已阻擋版本只有 9 分鐘大,閘門是 2 天20:31:02供應鏈npm [email protected] — 已改寫安裝腳本已移除,中繼資料雜湊已更新20:31:07防火牆objects.githubusercontent.com:443 tcp — 已阻擋沒有相符規則,未匹配流量為拒絕20:31:09憑證代管brm_a1b2… → paste.example — 已阻擋外洩企圖,VM 已暫停20:31:09護欄git-receive-pack github.com — 已阻擋這個工作區的 GitHub 是唯讀20:33:41提示注入rules FLAG source=/repo/AGENTS.md
在那次被記錄視為單一 tool_use 區塊的安裝過程中,一份 Bromure Security Timeline 記下了什麼。每一列都來自主機端盯著線路的引擎,所以這些都不需要酬載經過模型,也沒有一列能被 VM 內部的程式碼改寫。

那些列裡沒有一列需要酬載曾經經過模型,也沒有一列能被 VM 內部的程式碼改寫, 因為寫下它們的引擎跑在 Mac 上,而客體沒有任何通往它們的路徑。

不論你跑的是哪個代理,都是同樣那些列

一個團隊統一用 Claude Code。接著有人為了某個專案帶進 Codex,平台組為了資料 落地把一個工作區導向 Bedrock,而一位研究員開始跑 Grok。按照文章自己的說法, 這四個裡有三個在 Compliance API 中什麼都不會產生。

一個讀取 TLS 伺服器名稱的 proxy,用同一種方式看待這四個,而同樣的 llm.requesttool.usecommand.runfile.readfile.write 事件會從 每一個裡面出來——是從流量中抽出來的,而不是某家廠商賞給你的。在一台已加入 bromure.io 工作區的 Mac 上,它們會透過雙向 TLS 串流到組織,一併還有每個防火牆 判定的 egress.firewall、當某個誘餌憑證流向從未為它簽發的主機時的 credential.exfiltration,以及工作區拉取的每一個套件的 supply_chain.fetch。 最後這一項,就算把所有供應鏈強制層都關掉也照樣發出,因為 Bromure 把「看」和 「擋」分開了。這道串流不帶原始提示。它回答的是代理做了什麼,而不是開發者 問了什麼。

Claude Code · Anthropic在 Compliance API 內Claude Code · Bedrock未涵蓋Codex · OpenAI未涵蓋Grok · xAI未涵蓋主機 proxy + 交換器讀的是 TLS 伺服器名稱,不是廠商帳號 ID單一事件串流llm.request · tool.usecommand.run · file.readegress.firewallsupply_chain.fetchcredential.exfiltration
四個工作區跑在四種不同的後端上。錨定在 Anthropic 模型上的記錄只涵蓋第一個。錨定在線路上的記錄涵蓋全部四個,因為它是從流量中萃取出來的,而不是由碰巧在提供權杖的那家廠商賞給你的。

文章收尾的那一句

全文最鋒利的一句話,把涵蓋範圍拋在了身後:「光靠活動日誌,無法告訴你一個代理 的存取是否正當。」

這對任何日誌都成立,也正因如此,Bromure 的引擎自己寫時間軸,而不是把這件事 交給另一個旁觀者。那個視窗裡每一列紅色,記下的都是一個已經發生的決定。一列 供應鏈,意味著一個代理從未收到的套件。一列防火牆,意味著一條從未打開的連線。 護欄那一列是主機以 403 拒絕的推送,而代理把它讀成一次普通的 API 失敗;憑證 代管那一列則是一個從未變成真正機密的誘餌——因為真正的那一份從來就不在 VM 裡。

策略仍然得由你來定。但你回答「這次存取正當嗎」的時機,是在設定工作區的當下, 而不是幾個星期後坐著讀那些列。

把你的探針放在哪裡

如果你在任何規模上跑編碼代理,就把 Compliance API 打開。四個星期前,這個面向 幾乎毫無可見度,而一個星期的記錄會告訴你一些關於自己團隊、你原本不知道的事。

然後問第二個問題:一台記錄看起來乾乾淨淨的機器,你要拿它怎麼辦?在 Bromure 工作區裡,答案已經在螢幕上了。要看主機就打開 Window → Trace Inspector, 要看判定就打開 Window → Security Timeline,或執行 bromure-cli trace hostnames,把那個工作區自開機以來連過的每一個網域讀一遍。

代理不必提起任何一個字,它們也在那裡。 安裝 Bromure Agentic Coding,打開時間軸,看一次安裝。