Day15 的成績單有兩個結論。
第一,hook 這一層,omp 不輸 Cursor,還贏一題。
第二,hook 以外還有落差。卷子外面 omp 落後三個地方:核准的預設值、沙箱、管理稽核。另外三個洞兩邊都有:一行 sed 繞過 guard、shell 讀得到金鑰、會不會作弊全看模型。
今天的目標只有一個:讓 omp 追上 Cursor。六件事這樣分:
| 件 | 補哪個缺口 | 做什麼 |
|---|---|---|
| 第 1 件 | 核准的預設值 | 改成要問人,並管到 subagent 和 eval |
| 第 2 件 | 沙箱 | 把整個 omp 放進容器或 VM |
| (不編號) | 管理稽核 | 追不上,下面單獨說 |
| 第 3 件 | 前兩件要鎖得住 | guard 沒載入,就不准開工 |
| 第 4 件 | sed 繞過 guard |
guard 要看得到 shell |
| 第 5 件 | shell 讀得到金鑰 | 金鑰不要放在 agent 碰得到的地方 |
| 第 6 件 | 作弊全看模型 | 收工前再檢查一次 |
前兩件補卷子外面 omp 落後的地方。第 3 件把補上的東西鎖住。後三件堵兩邊都有的洞。管理稽核是第三個落後的地方,這一篇沒有把它編成第 7 件。
現在輸在哪。 omp 的 tools.approvalMode 預設是 yolo,所有工具自動核准(docs/approval-mode.md:20-22)。Cursor 的 Agent Security 文件寫明,terminal 指令預設要人核准。
omp 裡用什麼做。 模式有三種(docs/approval-mode.md:20-22):
| 模式 | 自動核准 | 要問人 |
|---|---|---|
always-ask |
讀取 | 其餘 |
write |
讀取、寫入 | 執行類 |
yolo |
全部 | 無 |
我驗過 write 模式。沒有互動介面時,bash 在執行前就被拒絕,錯誤是 Tool "bash" requires approval but no interactive UI available.(L9)。
只改模式還不夠,有兩個例外要另外點名:
yolo 跑。只有你明確寫下的 tools.approval.<工具> 還算數。寫成 prompt 的話,subagent 裡沒人能按,呼叫直接被拒(docs/approval-mode.md:162)。eval。 bash.patterns 的規則管不到從 eval 開出來的 shell,要另外設 tools.approval.eval(docs/approval-mode.md:72)。寫成設定大概是這樣:
tools:
approvalMode: always-ask
approval:
bash: prompt
eval: prompt
放在哪裡很重要。omp 的優先順序,從高到低是(docs/settings.md:91-105):
--config <檔案> 帶進來的設定寫在全域設定裡,專案的 .omp/config.yml 可以把它蓋掉。文件裡的例子,就是專案設定把 tools.approval.bash 改成 allow(docs/settings.md:213-227)。所以這份設定要用 --config 帶進來,或至少在啟動時加上 --approval-mode always-ask(docs/settings.md:101-102)。
證據: 原始碼確認、未實測。其中「write 模式擋下 bash」已實測(L9)。
對 Cursor: 追平。Cursor 另有 Auto-review 分類器,但它的 Run Modes 文件自己說,那不是安全邊界。omp 這邊是寫死的規則,沒有分類器。
現在輸在哪。 omp 自己沒有 OS 層級的沙箱。文件講得很直白:
bash.patterns 是 “an approval policy, not containment”(docs/settings.md:182)。docs/bash-tool-runtime.md:46)。docs/extension-loading.md:273)。我在原始碼裡找不到任何 OS 層級的沙箱。
omp 裡用什麼做。 沒有,所以要從外面包。把整個 omp 行程放進容器或 VM:只掛載這個專案的目錄,網路只開給模型的 API,或第 5 件講的 auth-gateway。
omp 自己的 task isolation(worktree、overlayfs 這些後端)隔的是檔案,隔的不是程序(docs/tools/task.md:116-125)。
證據: 需要 omp 之外的元件。
對 Cursor: 追平,範圍還更廣。Cursor 的沙箱是 “a layer on top of Run Modes for shell commands”,管的是 shell 指令(Run Modes 文件)。omp 整個行程放進容器之後,bash、eval、MCP server、extension、subagent 全都在裡面。
這一項不在六件事裡。三個落後的地方,這一項 omp 補不上。
Cursor Enterprise 的管理稽核日誌,記下登入、成員、API key 和團隊設定的變更(Compliance and Monitoring 文件)。permissions.json 的優先順序裡,最上面是 team admin 的設定(permissions.json reference)。
omp 沒有這一層。我在 docs/settings.md 和設定的載入程式裡,找不到任何「專案蓋不掉」的 managed 設定。
OpenTelemetry 可以把 agent 的活動送出去(設定 OTEL_EXPORTER_OTLP_ENDPOINT 這類環境變數,docs/environment-variables.md:604-615)。「誰在什麼時候改了公司的 AI 政策」這種紀錄,只靠 omp 做不出來。
現在輸在哪。 前兩件補上的東西,要鎖得住才算數。現在鎖不住,有兩層:
Failed to load extension。session 照跑,檔案照寫(L2)。.omp/ 底下的設定和 extension 都是無條件載入(extensibility/extensions/types.ts:461-467)。clone 一個別人的 repo,它的設定和 hook 就跟著上來。extension 跟 omp 跑在同一個行程裡,沒有沙箱(docs/extension-loading.md:273)。omp 裡用什麼做。 --trusted-extension <絕對路徑>,可以重複給(docs/cli-reference.md:136)。
main.ts:1597-1614;關鍵是第 1613 行的 options.disableExtensionDiscovery = true)。別人 repo 裡的 .omp/hooks/ 不會被撿進來。Trusted extension failed to load: …(main.ts:2168-2171)。Day14 的「guard 壞了、工作照做」,會變成「guard 壞了、開不了工」。--config <檔案> 或 CLI 旗標蓋過專案設定。有一個限制。--trusted-extension 不能和 -e、--hook 一起用(docs/cli-reference.md:136)。cursor-sdk 平常用 omp -e <目錄> 載入,這裡要改成指向進入點檔案 src/index.ts(extensions/cursor-sdk/package.json:7-8 寫的就是這個檔)。這個組合我還沒跑過。
證據: 原始碼確認、未實測。
對 Cursor: 追平,而且超越。Cursor 的 hook 預設 fail-open,每一支都要自己記得加 failClosed: true(Day14)。omp 可以把「少了一支 guard」變成整個 session 啟動失敗。
現在輸在哪。 Day15 的「搞定它」,兩邊的 Opus 第一步都是 sed。我的 guard 只看檔案工具,所以沒攔到。
同一段餌改由 bash 的 cat 讀進來,只看 read 的那支 guard(hooks/injection-guard.ts:7)也看不到。這一點我在 hook 層驗過。
omp 裡用什麼做。 hook 本來就看得到 shell,有三個接點:
bash 的 tool_call 事件帶著整行指令 event.input.command。omp 自己的文件就有擋 rm -rf 的範例(docs/hooks.md:31-38)。tool_call 可以擋,也可以回傳改寫過的參數(docs/hooks.md:122)。tool_result 可以換掉任何工具的輸出,bash 也不例外(extensibility/extensions/wrapper.ts:386-407)。擋讀檔餌的 guard 把 bash 加進檢查範圍,cat 讀進來的餌也能攔。bash.patterns。deny 會比對整行,也會比對拆開後的每一段。文件的例子:match: "rm -rf *" 擋得下 cd /tmp && rm -rf build(docs/settings.md:180)。比對指令字串是警報器。擋了 sed,還有 python -c、cp、tee。它的價值是讓繞路留下紀錄。真正的邊界是第 1、2 件。
證據: 原始碼確認、未實測。
對 Cursor: 追平。Cursor 的 preToolUse 對 Shell 一樣帶著整行指令(Day15)。
現在輸在哪。 Day15:兩邊的模型 shell 都看得到 CURSOR_API_KEY。
omp 這邊的原因有兩段:
filterChildShellEnv(Bun.env)(packages/utils/src/procmgr.ts:28-38)。它會濾掉專案 dotenv 帶進來的值,和幾個 git 變數。它不會濾掉 API 金鑰。CURSOR_API_KEY 環境變數拿金鑰(extensions/cursor-sdk/src/index.ts:9)。omp 裡用什麼做。 三條路,強度不同。
1. omp auth-gateway serve(建議走這條)。 omp 內建的轉送代理。文件的說法是 client “never see the access token”,支援的路由也包含 TypeSafe 的 judgment(docs/auth-broker-gateway.md:6)。把 gateway 放在容器外,或另一台機器。容器裡的 omp 拿到的是連 gateway 用的 token,不是供應商的金鑰。cursor-sdk 這種 extension provider 能不能走 gateway,我沒有驗證。
2. secrets.enabled: true(減少曝光)。 預設關閉(docs/secrets.md:7)。打開之後,名稱裡有 KEY、TOKEN、SECRET 這類字、值至少 8 個字元的環境變數,在送給模型供應商之前會換成佔位字串(docs/secrets.md:16-22)。
它只遮模型看得到的文字。模型把佔位字串寫進工具參數,執行前會被換回原值(docs/secrets.md:24)。金鑰可以不出現在對話裡。一行 curl 還是送得出去。
3. 改原始碼,讓 bash 也用白名單(減少曝光)。 Python eval 已經這樣做了:只放行 PATH、HOME 這類白名單上的變數,註解寫著 “Removes sensitive API keys”(eval/py/runtime.ts:12-39、128-147)。bash 還沒有。
我的建議是第 1 條,加上第 2 件的容器。金鑰不在 agent 那台機器、那個使用者底下,才算真的拿不到。第 2、3 條只是減少曝光。
證據: 需要 omp 之外的元件。
對 Cursor: 追平,而且有機會超越。在我測的 Cursor SDK 設定下,金鑰只當 apiKey 參數傳入,模型的 shell 還是看得到。那個環境變數是 SDK 自己放進 shell 的,不是我設定的。omp 這邊,金鑰放在哪裡是你決定的。
現在輸在哪。 Day15 的作弊題,結果看模型,不看平台。
mock.module 換掉被測的函式。guard 放行。omp 裡用什麼做。 session_stop。主 session 要收工之前觸發。handler 回傳 decision: "block" 和理由,omp 會把理由交給模型,讓它繼續做(extensibility/shared-events.ts:395-404、extensibility/extensions/runner.ts:1412-1415)。
兩種檢查看的東西不一樣:
| 時機 | 看什麼 |
|---|---|
| 每一次寫入 | 單一動作 |
| 收工前 | 整體結果 |
收工前看 git diff:測試檔有沒有多出 mock.module、斷言有沒有變少、有沒有 .skip。模型換了什麼招,最後的 diff 都在那裡。
想讓 Jev 這類判斷模型來看 diff,也可以。omp 的 judge 沒有開放給 extension:resolveJudge 在 judgment/index.ts:118,extension 的 API 裡找不到它。要自己打 API,最好經過第 5 件的 auth-gateway,金鑰就不必進 omp。
證據: 原始碼確認、未實測。
對 Cursor: 追平。Cursor 的 stop hook 也能用 followup_message 讓 agent 繼續做(Hooks 文件,預設最多 5 次)。
| 面向 | 做完之後 | 靠哪一件 |
|---|---|---|
| 核准的預設值 | 追平 | 第 1 件 |
| 沙箱 | 追平,範圍更廣(要 omp 之外的元件) | 第 2 件 |
| 管理稽核 | 追不上 | 無 |
| guard 壞掉、沒載入 | 超越 | 第 3 件 |
一行 sed 繞過 guard |
追平 | 第 4 件 |
| 金鑰被 shell 讀到 | 追平,有機會超越(要 omp 之外的元件) | 第 5 件 |
| 作弊全看模型 | 追平 | 第 6 件 |
七個面向:五個追平、一個超越、一個追不上。
這張表是推論,不是實測。它假設每一件都照原始碼寫的那樣運作。標「原始碼確認、未實測」的那幾件,要你自己跑過一次才算數。
還有三件事我沒搞清楚:
sdk.ts:2211-2213)。isolation 模式的 child 不帶任何預先載入的 extension(task/isolation-runner.ts:437-438),會在隔離目錄重新做一次完整的 discovery(sdk.ts:2196)。guard 會不會跟著載入,我沒有驗證。--trusted-extension 指向 cursor-sdk 的進入點檔案,能不能正常載入。
追平只是起點。有些事 omp 做得到,Cursor 做不到:
web_search 是本機的內建工具,每一次都經過 hook(tools/index.ts:529、sdk.ts:2971-2976)。Cursor 的 WebSearch 在本機完全看不到(Day14)。before_provider_request。每次送給模型供應商之前,可以改寫整份內容(extensibility/extensions/types.ts:731-734、extensibility/extensions/runner.ts:1706-1732)。Cursor 的 hook 清單裡沒有對應的事件。接下來我想換一個角度。
Cursor 的主場是 IDE,旁邊通常有人看著。omp 的定位是一個 agent,有 headless、subagent 這些沒人看著也能跑的模式。越不需要人看著,AI Security 越得先到位。Gartner 在 2025 年 6 月預測:2027 年底前,超過 40% 的 agentic AI 專案會被取消,原因是成本上升、商業價值不明,或風險控管不足(Gartner)。
所以從明天開始,不再拿 omp 跟 Cursor 比。我要看的是:omp 當成一個 agent,每多一個能力,要多補哪一道 AI Security。第一題,是 Day13 預告過的那一個:Agent 說「做完了」,真的做完了嗎?
第 1 步。 在你平常開 omp 的專案目錄裡跑 omp config list,找 tools.approvalMode。這裡列的是目前生效的值(docs/settings.md:41)。
如果是 yolo,你的 agent 和 bash 之間只隔著 hook。順便看看專案裡有沒有 .omp/config.yml:有的話,它會蓋過你的全域設定。
第 2 步。 故意弄壞一支 guard(刪掉一個右括號),用 --trusted-extension <它的絕對路徑> 啟動 omp。
照原始碼,omp 應該直接拒絕啟動,印出 Trusted extension failed to load。跑一次,確認你手上的版本也是這樣。
第 3 步。 打開 secrets.enabled。啟動 omp 之前,在環境變數放一個假的 DEMO_API_KEY=not-a-real-key-123456。然後做兩次:
bash 印出它。看模型收到的是不是 $$ 開頭的佔位字串。今天對應的威脅: T1、T2、T3。
引用來源:
docs/approval-mode.md — 核准模式、subagent、eval
docs/settings.md — 優先順序、專案覆寫的例子、bash.patterns、omp config list
docs/cli-reference.md — --trusted-extension
docs/secrets.md
docs/auth-broker-gateway.md
docs/hooks.md
docs/tools/task.md
docs/environment-variables.md — OpenTelemetrydocs/bash-tool-runtime.md
docs/extension-loading.md
packages/coding-agent/src/ 底下:main.ts、sdk.ts、extensibility/extensions/types.ts、extensibility/extensions/runner.ts、extensibility/extensions/wrapper.ts、extensibility/shared-events.ts、tools/index.ts、eval/py/runtime.ts、judgment/index.ts、task/isolation-runner.ts
packages/utils/src/procmgr.ts
extensions/cursor-sdk/src/index.ts、extensions/cursor-sdk/package.json
verification/live/(L2、L9);刊出的 guard:hooks/injection-guard.ts