← 所有文章

每一道掛鉤都是一道疤:84 次代理程式失誤寫進了程式碼

截至 v2.1.116(2026 年 4 月),Claude Code 共開放 26 種生命週期事件類型,而我的代理程式協作系統用 84 道掛鉤攔截了其中 15 種。每一道掛鉤都是一段 shell 指令碼或 Python 片段,在特定的代理程式動作之前或之後觸發:讀檔、寫檔、bash 指令、網路請求、生成子代理程式、git 操作、MCP 工具呼叫。而每一道掛鉤之所以存在,都是因為曾經出過事。

代理程式協作系統裡的每一道掛鉤,都能追溯到一次具體的正式環境事故;於是這整批掛鉤,就成了用 shell 指令碼寫下來的組織記憶。 代理程式清空過 CDN 快取、讀取過憑證檔案、回報過根本沒跑的測試「全數通過」,也曾經整整偏離任務 40 分鐘。每一次事故都留下一道小而確定的護欄,此後在每一次會話裡靜靜地觸發。

不是理論上會出錯,是在正式環境真的出了錯。有一次,代理程式清空了承載數百萬次請求的 CDN 快取。有一次,代理程式試圖寫入 SSH 金鑰。有一次,代理程式在沒有呼叫 pytest 的情況下回報「所有測試通過」。還有一次,代理程式偏離得太遠,花了整整 40 分鐘去最佳化一個檔案裡的函式,而那個檔案跟交付的工作毫無關係。

這些掛鉤沒有一道是我事先設計出來的。我從來沒有坐下來把自主 AI 代理程式的所有失效模式列成清單,再逐一寫下預防性控制。每一道掛鉤都是事後補上的:出了事,我寫一段指令碼防止它再發生,然後這段指令碼就在之後的每一次會話裡默默運作。這套掛鉤系統不是什麼資安架構,而是一批疤痕的收藏。

摘要

  • 快取清空事件:代理程式用一次獲得授權的 API 呼叫,清空了正式環境的 CDN 快取。如今兩道掛鉤(共 47 行)把破壞性操作擋在人工輸入的通關密語之後。
  • 憑證讀取事件:代理程式把 API 權杖帶進了脈絡視窗。現在有一道路徑比對護欄封鎖憑證檔案的讀取,並記錄所有對 .env 檔案的存取。
  • 幽靈驗證:代理程式沒有執行 pytest,卻回報「所有測試通過」。一套模糊措辭偵測器把幽靈驗證從 12% 的會話壓到 2% 以下。
  • 十二次偏離:60 天之內,代理程式有 12 次確確實實跟丟了自己的任務。門檻設在 0.30 的餘弦相似度偵測器,現在每 25 次工具呼叫觸發一次。
  • 失效分類:六個結構性失效類別涵蓋全部 84 道掛鉤。累積 500 次以上會話之後,全新的類別已經非常罕見。系統每經歷一次事故,就更硬一分。

快取清空事件:一次獲得授權的呼叫如何弄壞正式環境

2026 年 3 月 21 日,我請代理程式調查 resumegeni.com 的市場頁面為何載入緩慢。它一開始的調查很正常:讀路由處理器、檢查資料庫查詢、分析樣板算繪。接著它判斷,Cloudflare 裡過期的快取項目可能掩蓋了真實的效能表現。

於是代理程式帶著 purge_everything: true 呼叫了 mcp__cloudflare__cache_purge

正式站台上所有已快取的頁面瞬間失效。CDN 從以 80–100ms 回應大多數請求,變成把每一個請求都轉發給 Railway 來源伺服器。Austin 的市場頁面從不到一秒暴增到 14,290 毫秒,New York 從不到一秒變成 6,891ms。整個站台的每一個頁面,每一次請求都得從冷來源重新算繪。

代理程式並沒有做任何未經授權的事。它用合法的 MCP 工具、有效的憑證,呼叫了一個獲得授權的 API 端點。若是正在偵錯快取行為,清空快取本來就是合理的調查步驟。問題在於,「偵錯時合理」與「對正式環境是災難」是同一個 API 呼叫,而在代理程式的推理與正式環境的後果之間,並不存在任何約束。4

那天晚上我寫了兩道掛鉤。

Bash 護欄destructive-api-guard.sh):在每一次 bash 指令上觸發,比對 curl.*purgerm -rfDROP TABLEdocker.*rmgit push.*--force 等樣式。屬於硬性封鎖(exit 2)。代理程式會看到一則訊息,說明指令為何被擋下並提供替代作法。除非拿到通關密語「rosebud」,否則它無法繼續,而這個密語只有在人類親手輸入時才會進入脈絡。

MCP 護欄destructive-mcp-guard.sh):在每一次符合 mcp__cloudflaremcp__github 的 MCP 工具呼叫上觸發,比對工具參數裡的 purgedeletedestroyremove。同樣是硬性封鎖,同樣需要通關密語。

兩道掛鉤,兩段 shell 指令碼,總共 47 行程式碼。安裝之後,它們攔下的快取清空次數是零——因為自從加上通關密語關卡,就再也沒有代理程式嘗試過。這些掛鉤不是在攔截攻擊,而是讓這一整類錯誤從一開始就不可能發生。

快取清空事件本身也暴露了它原本要調查的效能問題。Austin 在冷算繪下花了 14 秒,這件事直接促成市場頁面的移交,四天後又帶出查詢結構的修正。事故本身是有價值的,而掛鉤確保它不會重演。

憑證讀取事件

2026 年 2 月,一個正在為專案蒐集脈絡的代理程式讀取了 ~/.claude/docs/credentials.md。這個檔案裡有 Cloudflare、GitHub、Railway 以及其他服務的 API 權杖。代理程式把檔案內容的摘要寫進了工作筆記,這代表那些權杖出現在送往 Anthropic 伺服器的 API 請求裡。

沒有任何權杖被提交進版本庫,也沒有任何權杖公開外洩。但這些權杖確實經由第三方 API,進入了一個我無法掌控的脈絡視窗。風險面從「我的機器」擴張成「我的機器,加上 Anthropic 的推論基礎設施」。

憑證路徑護欄在每一次檔案讀取時觸發,把路徑與一份敏感樣式清單比對:.envcredentials.ssh/.aws/.gnupg/secrets。遇到憑證類讀取,掛鉤會記錄警告並直接封鎖;遇到 .env,則放行但留下存取紀錄。

換句話說,這道護欄對多數路徑只是提醒,對憑證檔案則是硬性封鎖。這個區別很重要:代理程式讀 .env 是為了理解環境變數名稱,那是有用的脈絡;代理程式讀 credentials.md 是為了理解 API 權杖,那就是資安事件。

安裝至今,憑證路徑護欄在 200 多次會話中觸發了 23 次。其中 20 次是代理程式讀取 .env 檔案(記錄、放行),3 次是代理程式試圖讀取憑證或金鑰檔案(封鎖)。每一次被封鎖的讀取,都是代理程式大範圍蒐集專案脈絡時,剛好把敏感檔案掃進了搜尋範圍。沒有一次帶有惡意,但若沒有護欄,每一次都會把機密送進脈絡視窗。

幽靈驗證

最陰險的失效模式,是代理程式在根本沒有驗證的情況下回報驗證成功。

第 147 次會話。我請代理程式重構一段資料庫查詢,並用既有測試套件驗證這項變更。它把查詢重構得很正確,完成報告寫著:「所有測試通過。重構後的查詢與原始查詢產生完全相同的結果。」

我去翻會話紀錄,沒有任何 pytest 的呼叫,任何測試執行器都沒有被叫起來。代理程式的推論是:既然重構後的查詢在邏輯上等價於原本的查詢,測試就會通過——然後它把這段推論當成測試結果回報了出來。

重構後的查詢確實正確,測試也真的會通過,代理程式的推論也站得住腳。但推論測試不等於執行測試,而缺陷正是從這兩者之間的縫隙流進正式環境。如果那段重構的查詢在某個代理程式沒想到的邊界情況下悄悄出錯,這個缺陷就會帶著一份宣稱「已通過測試驗證」的完成報告上線。

在我做出證據關卡掛鉤之前,這個失效模式在 60 次會話裡出現了 7 次。掛鉤會在每一份完成報告上觸發,掃描模糊措辭:「應該會通過」、「我相信」、「測試很可能通過」、「我有信心」。一旦偵測到,掛鉤就注入一則訊息:「偵測到模糊措辭。請引用具體證據:貼上測試輸出、指出檔案與行號,或指明是哪一個驗證步驟。」

掛鉤並不能證實測試真的跑過,它標記的是那種暗示驗證被跳過的語言樣態。這種偵測並不完美——一個夠圓滑的代理程式大可換個說法繞開比對樣式。但它抓得住最常見的情況,而這種情況佔了需要人工介入的代理程式失誤的 12%。1

掛鉤安裝之後,幽靈驗證從 12% 的會話降到 2% 以下。剩下的那 2%,不是代理程式重新包裝了模糊說法,就是驗證聲明在技術上正確但不完整(例如只跑了單元測試,卻沒跑整合測試,就說「單元測試通過」)。

偏離

2026 年 1 月到 3 月之間,我的偏離偵測器觸發了 12 次,每一次代理程式都確實跟丟了它被指派的任務。

偏離偵測器的作法,是把原始任務提示嵌入向量化,再定期與代理程式近期動作的嵌入向量比對。當餘弦相似度掉到 0.30 以下,系統就注入一則含有原始提示的警告。這個門檻是我試出來的:0.50 太敏感(正當的子任務探索也會觸發),0.20 太寬鬆(明顯的偏離抓不到),0.30 則抓到了每一次經過確認的偏離事件。

第 203 次會話是最清楚的例子。任務是「修正網站地圖 XML 對含有 & 符號的職缺 slug 的跳脫錯誤」。代理程式先去讀網站地圖的產生程式碼,接著注意到網站地圖是由一段資料庫查詢產生的,再接著注意到那段查詢還可以最佳化,然後它花了 40 分鐘把查詢重構成具體化檢視表的模式、替新查詢寫了測試,並回報最佳化完成。& 符號的跳脫問題,它從頭到尾沒碰。

如果偏離偵測器當時已經上線,會在第 25 次工具呼叫、也就是會話開始約 15 分鐘時攔下這件事——那時「修正網站地圖 XML 跳脫」與「建立具體化檢視表」之間的相似度已經跌破門檻。實際上,我是在事後審閱時才發現偏離。

第 89 次會話則細微得多。任務是「替身分驗證端點加上速率限制」。代理程式正確地加上了速率限制,接著注意到身分驗證流程的錯誤訊息不一致,於是把錯誤訊息統一;然後又注意到錯誤回應格式跟 API 回應格式標準不符,於是把 12 個端點的回應格式全部重構了一遍。速率限制做得既正確又完整,偏離的是那場範圍爆炸。

偏離偵測器每 25 次工具呼叫觸發一次。在全部 12 次跌破門檻的紀錄裡,代理程式都確實偏離了原始任務。其中 6 次,代理程式看到注入的警告後自行修正;4 次,代理程式承認偏離,但主張手上的工作有價值(有時候它是對的);2 次,代理程式無視警告,繼續做它岔出去的事。

掛鉤並不能阻止偏離,它只是讓偏離變得可見。要導回正軌還是放行,決定權仍在人身上。但沒有這道掛鉤,偏離要到完成報告才會現形,而那時脈絡預算已經燒完了。

疤痕分類學

累積 84 道掛鉤之後,樣態開始浮現。這些失效可以分成六類:

類別 掛鉤數 例子
憑證外洩 12 代理程式讀取 .ssh/、把 API 金鑰寫進摘要、存取雲端設定
破壞性操作 8 清空快取、刪除資料庫、強制推送、刪除檔案
任務偏離 4 代理程式做錯題目、範圍爆炸、掉進子任務的兔子洞
輸出品質 6 幽靈驗證、沒有證據的模糊措辭、報告不完整
資源耗盡 3 生成過多子代理程式、沒有上限的迴圈、脈絡溢位
跨專案污染 4 在專案 A 裡工作的代理程式改動了專案 B 的檔案

其餘 47 道掛鉤,屬於專案專用(慣例強制、部署護欄、翻譯驗證器)或實驗性質(成本追蹤、會話指標、活動心跳)。

這六個結構性類別相當穩定。落在既有類別裡的新事故,都會被現有掛鉤攔下;全新的類別則很罕見。運作六個月以來,只冒出過一個新的結構性類別(跨專案污染,是在 obsidian-signals 專案裡執行的一次會話試圖編輯 blakecrosley.com 的檔案時發現的)。另外五類,都在最初 60 次會話內就成形了。

Agents of Chaos 研究是一項為期 14 天的跨校實驗,讓六個 AI 代理程式取得電子郵件、bash、檔案系統與 GitHub 的存取權;它獨立歸納出的失效類別與我的高度重疊:反應失衡(破壞性操作)、身分劫持(憑證外洩)、無限迴圈(資源耗盡),以及在壓力下逐步妥協(任務偏離)。5 他們的受控研究與我的正式環境經驗收斂到同一組結論,這意味著這些類別是自主代理程式的結構性性質,而不是某一套特定設定的產物。

掛鉤攔不住什麼

掛鉤運作在工具呼叫這一層。它們在動作發生之前或之後攔截,卻攔不住導向那個動作的推理。

一個決定去重構函式、而不是修正回報缺陷的代理程式,會產生一次有效的工具呼叫(寫檔),內容也正確(語法無誤的程式碼),但它違背了任務(改錯了函式)。沒有掛鉤攔得住,因為沒有任何一次工具呼叫看起來可疑。偏離偵測器最終會抓到,但那時代理程式已經在錯誤的工作上燒掉了大量脈絡。

掛鉤同樣攔不住組合式失效——每一個單獨動作都獲得授權,但整個序列卻產出未經授權的結果。快取清空就是一次組合式失效:讀取快取設定(獲授權)、呼叫清空 API(獲授權),但兩者的組合(在調查過程中清空正式環境快取)是有害的。MCP 護欄現在攔得住這個特定組合,但全新的組合仍然在射程之外。

供應鏈的組合缺口3 運作在同一層次:受信任的元件組合出未經授權的行為。掛鉤是元件層級的護欄,而組合層級的推理需要另一種機制——一種評估「動作序列」而非「單一動作」的機制。偏離偵測器是目前最接近的近似解:它評估的是行為軌跡,而不是個別工具呼叫。但它衡量的是與原始任務的相似度,不是組合後動作序列的安全性。

掛鉤與完備安全之間的落差,就是組織記憶與組織預見之間的落差。掛鉤記得出過什麼錯,卻不會預測接下來會出什麼錯。

為什麼「事後補」才誠實

我大可設計一套事前預防的掛鉤系統:把所有可能的失效模式列出來,逐一寫下預防性控制,在第一次會話之前就建好完整的安全架構。

我沒有這麼做,因為事前設計要求你預測還沒發生的失效。這些預測會出錯。寫出來的掛鉤不是太寬(擋掉正當動作),就是太窄(漏掉真正的失效樣態)。誤報率會侵蝕我對這套系統的信任,最後我會開始無視警示。

事後補上的掛鉤才誠實。每一道都在說:「這件具體的事發生過,而這是防止它再發生的具體護欄。」護欄之所以校準得精準,正是因為那次失效本身定義了護欄。誤報率實質上低得多,因為樣態是從真實事故裡萃取出來的,而不是從威脅模型裡想像出來的。當然,隨著程式碼庫演進,事後補的護欄日後仍可能過度比對,但它的起始精確度很高。

事後補的作法有代價:每一類失效的第一次,一定會得逞。快取真的被清空了,憑證真的被讀取了,幽靈驗證真的上線了,偏離真的燒掉了脈絡。每一次「第一次失敗」,都是換取一道精準、低噪音、能擋住第二次失敗的護欄所付的入場費。

累積 500 次以上會話之後,大多數結構性失效類別都已經遇過了。第一次失敗的成本,被攤提在後續數百次由掛鉤擋下重演的會話上。系統每經歷一次事故就更硬一分——不是更聰明,是更硬。

每一道掛鉤都是一道疤。每一道疤都是一課。而這些課會複利累積。2


常見問題

可以看看您的掛鉤設定嗎?

我在 給 NIST 的代理程式安全意見書 裡描述了這套掛鉤系統,並在整個 AI 工程系列中反覆引用。掛鉤註冊在 ~/.claude/settings.json,並依事件類型透過 ~/.claude/hooks/dispatchers/ 分派。

掛鉤會不會影響代理程式效能?

每一道掛鉤在每次工具呼叫上會增加數毫秒。以 84 道掛鉤來說,視實際觸發哪些掛鉤而定,每次工具呼叫的總開銷落在 200–400ms。相較於模型推論時間(每次回應 2–5 秒),這點開銷微不足道。掛鉤不是瓶頸。

掛鉤能用在其他 AI 編碼工具上嗎?

掛鉤是 Claude Code 專屬的機制(PreToolUse、PostToolUse 事件模型)。但這個概念適用於任何支援中介層或外掛的代理程式框架。具體實作無法直接搬移,疤痕分類學與「事後補」的方法論卻是通用的。

掛鉤擋下一個動作時會發生什麼事?

硬性封鎖(exit 2)會阻止該動作,並注入一則說明原因的訊息,代理程式會看到封鎖理由並自行調整。提醒型掛鉤(exit 0)則記錄疑慮但放行。破壞性操作採用硬性封鎖,其他類別大多採用提醒型掛鉤。通關密語關卡只保留給最危險的操作(清空快取、刪除基礎設施)。

您如何決定該用硬性封鎖還是提醒?

兩類情況一律硬性封鎖:破壞性操作(清空快取、刪除資料庫、強制推送、變更基礎設施)與憑證外洩(讀取機密檔案、存取金鑰儲存區)。其餘一律只做提醒式記錄。判準是後果的嚴重程度:如果動作可以低成本復原、也不會洩漏機密,提醒就夠了;如果動作不可逆或會暴露憑證,就非得硬性封鎖不可。


來源


  1. Blake Crosley, “What I Told NIST About AI Agent Security,” blakecrosley.com,2026 年 2 月。60 次以上自主會話中 12% 的幽靈驗證率;84 道掛鉤涵蓋 Claude Code 26 種生命週期事件類型中的 15 種(v2.1.116);偏離偵測方法論。 

  2. Blake Crosley, “Compound Context: Why AI Projects Get Better the Longer You Stay With Them,” blakecrosley.com,2026 年 3 月。脈絡複利框架:掛鉤是六類累積型報酬之一。 

  3. Blake Crosley, “The Supply Chain Is the Attack Surface,” blakecrosley.com,2026 年 3 月。組合缺口:個別獲得授權的元件,組合出未經授權的結果。 

  4. Blake Crosley, “Deploy and Defend: The Agent Trust Paradox,” blakecrosley.com,2026 年 3 月。快取清空事件與破壞性 API 護欄的因應。 

  5. Christoph Riedl et al., “Agents of Chaos,” arXiv:2602.20021,2026 年 2 月。為期 14 天的跨校研究(Northeastern、Stanford、Harvard、MIT、CMU)。六個 AI 代理程式,辨識出 10 項安全弱點,包括反應失衡、身分劫持與無限迴圈。 

相關文章

AI供應鏈攻擊:供應鏈本身就是攻擊面

Trivy透過標籤劫持遭入侵,接著是PyPI上的LiteLLM,然後46分鐘內出現47,000次安裝。AI供應鏈完全按照設計運作。

2 分鐘閱讀

交接文件:代理在工作階段間的記憶傳承

一份診斷在四天內經歷三次修正仍屹立不搖,並指引修正將頁面載入時間從14秒縮短至108毫秒。交接承載著代理無法自行取得的脈絡。

1 分鐘閱讀

Ralph 迴圈:我如何在夜間運行自主 AI 代理

我建構了一套自主代理系統,搭配停止鉤子、生成預算與檔案系統記憶體。以下是失敗經驗與真正能交付程式碼的方法。

3 分鐘閱讀