iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
AI Security

一個 Agent 我查不動,一個我改得動:內部威脅偵測的 30 天系列 第 10

我在 hook 裡多睡 4 秒,Cursor 就多等 4 秒:把模型塞進 hook 之前,先驗這個前提

  • 分享至 

  • xImage
  •  

我在 Cursor 的 hook 裡加了一行 time.sleep(4),Agent 那一步就剛好慢了 4 秒。

原本今天要把模型(Jev)搬進 hook,讓它在每次改檔的時候判斷這次改動的性質。動手之前,我先回頭驗一句自己寫過、卻沒有證據的話。Day 9 的結尾我寫:「Cursor 每次寫檔都在等它回來。」

這句話決定了模型該放在哪裡:

  • 如果 Cursor 會等,hook 裡多花的每一毫秒,都會直接加在 Agent 的每一步上。模型放進 hook,就是讓每一次改檔都多等一次模型。
  • 如果 Cursor 不會等,hook 慢一點無所謂,但它跑出來的結果可能來不及被用上,先後順序也不一定。

兩種情況的設計完全不同,所以今天只做一件事:量。


實驗場:今天這個對話

今天我在這個專案開了一個 Agent 對話,讓它幫我整理這個系列的流量數據。這個專案為了診斷,在 hooks.json 的 15 個事件上都掛了 hook_diag.pypostToolUseafterFileEdit 另外再掛一支 audit_edit.py。所以 Agent 做的每一件事,前後都留下了紀錄。

每一筆紀錄的 ts,是 hook 腳本開始跑的時間。payload 裡有 tool_use_id,可以把同一次工具呼叫前後的事件串起來。有了這兩樣,就看得出兩個事件之間隔了多久。

問題是,光看間隔,分不出那段時間是 Cursor 在等 hook,還是 Cursor 本來就要花這麼久。要分得出來,得讓 hook 故意變慢,看間隔會不會跟著變長。


探針:一個旗標檔,控制 hook 要不要多睡

我給探針定了三個條件:

  1. 不用重開 Cursor。 hook 每次觸發都是開一個新的 python 行程,改完腳本,下一次觸發就生效。
  2. 一刪就關掉。 用旗標檔控制,不必再改一次程式碼。
  3. 睡了多久要寫進紀錄。 事後才分得出哪幾筆是實驗、哪幾筆是平常。

加在 hook_diag.py 的就是這一段:

flag = Path(__file__).resolve().parent / "_sleep_probe.json"
if flag.exists():
    cfg = json.loads(flag.read_text(encoding="utf-8"))
    m = re.search(r'"hook_event_name"\s*:\s*"([^"]+)"', record.get("stdin_text", ""))
    if m and m.group(1) in cfg.get("events", []):
        probe_ms = float(cfg.get("ms", 0))
        record["probe_sleep_ms"] = probe_ms   # 睡了多久也記進紀錄

# ……照常把紀錄寫完之後才睡,所以 ts 記的還是 hook 開始的時間
if probe_ms:
    time.sleep(probe_ms / 1000)

hooks 資料夾裡沒有 _sleep_probe.json,這段就什麼都不做。


第一輪:讓 after 類的 hook 多睡 4 秒

旗標設成 {"ms": 4000, "events": ["afterShellExecution", "postToolUse"]},然後讓 Agent 跑兩次最簡單的指令 Get-Date

一次 Shell 呼叫會依序經過四個事件:preToolUsebeforeShellExecution → 指令執行 → afterShellExecutionpostToolUse

我要看的是 afterShellExecutionpostToolUse 這一段。這時指令已經跑完,模型也還沒拿到結果,中間只有 Cursor 自己。如果這一段變長,只可能是 Cursor 在等 hook。

20:49:10.192               preToolUse
20:49:10.809  +   616 ms   beforeShellExecution
20:49:21.617  + 10808 ms   afterShellExecution   ← 探針睡 4000 ms
20:49:26.310  +  4693 ms   postToolUse

這一段平常是 714 ms,那是這個對話前 20 次 Shell 呼叫的中位數。兩次探針分別是 4,693 ms 和 4,627 ms,比平常多了 3,979 ms 和 3,913 ms,就是那 4 秒。

為什麼選 4 秒?因為這一段平常的起伏不小。20 次裡,p90 是 1,472 ms,還有一次 7,591 ms 的極端值,原因我沒有追。探針睡得太短,會被正常的起伏蓋過去;睡 4 秒,比 p90 大了快三倍。

而且只看一次不夠。那一次 7.6 秒的極端值,就比探針的 4.7 秒還長。兩次探針都落在「714 加 4,000」附近,不是隨機亂跳,這才算數。

Cursor 會等 after 類的 hook 跑完,才往下走。


第二輪:postToolUse 之後,Agent 等不等?

postToolUse 是最後一站。它之後,是 Agent 拿到工具結果、想好下一步、送出下一個工具。這一段一定混了模型產生回覆的時間,沒辦法像上一段那樣乾淨。

我本來想找一個乾淨的對照。紀錄裡,Cursor 的改檔工具有時會拆成「先 Read、再 Write」兩步,兩步之間沒有模型介入,平常只隔 0.9 秒左右。我把 postToolUse 設成睡 6 秒,對一個草稿檔做一次替換,想看 Read 的 postToolUse 會不會把後面的 Write 往後推。結果那一次它沒有拆成兩步,只記到一次 Write。想要的對照沒有出現。

只好退一步,直接比「postToolUse 開始 → 下一個工具的 preToolUse」:

postToolUse → 下一個工具的 preToolUse
  沒有探針        4,381 / 4,633 / 4,763 ms
  探針睡 4 秒     7,762 / 8,079 ms
  探針睡 6 秒    10,019 / 9,298 ms

每次都差不多是「原本的 4.5 秒,加上睡掉的時間」。如果 Cursor 不等 postToolUse,有沒有探針都應該落在 4.5 秒上下。看起來,工具的結果要等 postToolUse 跑完,才會回到 Agent 手上。

這一段每種條件只有兩三次,又混了模型的時間,我先當成強烈的跡象,不當定論。


這代表什麼

hook 不是旁觀者。它睡多久,Agent 就等多久。

放回原本的計畫:如果我在 postToolUse 裡呼叫模型,Agent 每改一次檔,就要停下來等模型回答一次。「在背景跑一跑、不影響使用」這種想像,在我這台機器上不成立。

至於要等多久、值不值得等,還得先知道幾件事:現在的 hook 已經讓每一步多花多少時間、hook 同時跑會不會出事,以及問一次模型要多久。這是接下來三天要量的。


今天的成果,你可以直接拿去用

先確認你的 Agent 會不會等你的 hook。十分鐘做得完:

  1. 把上面那段探針放進你的 hook,開頭記得 import json, re, timefrom pathlib import Path
  2. 建旗標檔,events 填你想驗的事件,例如 afterShellExecution
  3. 讓 Agent 跑兩三次最簡單的指令。
  4. 刪掉旗標檔,再跑兩三次當對照。
  5. 比同一段事件的間隔:有探針的那幾次,有沒有剛好多出你設的秒數?

挑段落的時候,挑中間沒有模型介入的那一段,例如 afterShellExecutionpostToolUse。做完記得把探針從 hook 裡拿掉,不要只刪旗標檔。


誠實欄

  • 乾淨的對照只有兩次。 afterShellExecution 那段跑了兩次探針,結果一致,但就是兩次。
  • postToolUse 那段混了模型的時間。 每種條件兩三次,只能當跡象。
  • before 類的 hook 我沒加探針。 preToolUsebeforeShellExecution 能擋住工具,照理一定會等,但我沒量。
  • 一台機器、一個版本。 Windows、Cursor 3.21.9。換了版本,行為可能不同。
  • 實驗期間,Agent 每一步都變慢了。 探針改的是真的 hook。做完之後旗標已刪、hook_diag.py 已還原。

明天把帳攤開:模型還沒放進去,現在這幾支 hook 已經讓每一步多等多久?讀一個檔只要 11 毫秒,猜猜前後的 hook 加起來要多久。


今天對應的威脅: T3(藉 Agent 之手規避控制)。會拖慢每一步的稽核,最後的下場通常是被關掉;關掉之後,事後就舉不出證。

實驗與程式: 2026-09-24 跑。探針加在 .cursor/hooks/hook_diag.py(實驗後已移除),時間線用 day10-lab/probe_report.py 攤開。資料是這個專案的 hook-diag.jsonl。Cursor 3.21.9。本篇沒有引用外部來源。


上一篇
當基準只是代理指標:模型「錯」了六題,但我說不出它錯在哪
系列文
一個 Agent 我查不動,一個我改得動:內部威脅偵測的 30 天10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言