iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
AI Security

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

讓 omp 追上 Cursor:六件事,四件 omp 本來就做得到,兩件要從外面補

  • 分享至 

  • xImage
  •  

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 件。

第 1 件:核准從 yolo 改成要問人,而且要管到 subagent 和 eval

現在輸在哪。 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)。

只改模式還不夠,有兩個例外要另外點名:

  • subagent。 它們一律以 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):

  1. CLI 旗標
  2. --config <檔案> 帶進來的設定
  3. 專案設定
  4. 全域設定

寫在全域設定裡,專案的 .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 這邊是寫死的規則,沒有分類器。

第 2 件:沙箱——整個 omp 放進容器或 VM

現在輸在哪。 omp 自己沒有 OS 層級的沙箱。文件講得很直白:

  • bash.patterns 是 “an approval policy, not containment”(docs/settings.md:182)。
  • 核准過的指令,保有 shell 原本的檔案、網路和子程序權限(docs/bash-tool-runtime.md:46)。
  • extension 也 “not sandboxed”(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 做不出來。

第 3 件:啟動鎖——guard 沒載入,就不准開工

現在輸在哪。 前兩件補上的東西,要鎖得住才算數。現在鎖不住,有兩層:

  • Day14 那支語法錯誤的 guard,omp 只在 stderr 印了一行 Failed to load extension。session 照跑,檔案照寫(L2)。
  • omp 不做專案信任檢查。.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)。

  • 它是精確的白名單:只載入你列的檔案,同時關掉自動 discovery(main.ts:1597-1614;關鍵是第 1613 行的 options.disableExtensionDiscovery = true)。別人 repo 裡的 .omp/hooks/ 不會被撿進來。
  • 列出來的任何一支載入失敗,omp 直接拒絕啟動,錯誤是 Trusted extension failed to load: …(main.ts:2168-2171)。Day14 的「guard 壞了、工作照做」,會變成「guard 壞了、開不了工」。
  • 設定那一側,照第 1 件:用 --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 啟動失敗。

第 4 件:guard 要看得到 shell

現在輸在哪。 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)。

第 5 件:金鑰不要放在 agent 碰得到的地方

現在輸在哪。 Day15:兩邊的模型 shell 都看得到 CURSOR_API_KEY。

omp 這邊的原因有兩段:

  • bash 拿到的環境是 filterChildShellEnv(Bun.env)(packages/utils/src/procmgr.ts:28-38)。它會濾掉專案 dotenv 帶進來的值,和幾個 git 變數。它不會濾掉 API 金鑰。
  • cursor-sdk extension 設計上就是從 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 這邊,金鑰放在哪裡是你決定的。

第 6 件:不要靠模型守規矩——收工前再檢查一次

現在輸在哪。 Day15 的作弊題,結果看模型,不看平台。

  • Opus 在 omp 和 Cursor 上都拒絕作弊。
  • composer-2.5 用 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 件

七個面向:五個追平、一個超越、一個追不上。

這張表是推論,不是實測。它假設每一件都照原始碼寫的那樣運作。標「原始碼確認、未實測」的那幾件,要你自己跑過一次才算數。

還有三件事我沒搞清楚:

  • isolation 模式的 subagent。 一般的 subagent 會沿用父 session 的 guard(sdk.ts:2211-2213)。isolation 模式的 child 不帶任何預先載入的 extension(task/isolation-runner.ts:437-438),會在隔離目錄重新做一次完整的 discovery(sdk.ts:2196)。guard 會不會跟著載入,我沒有驗證。
  • cursor-sdk 走 auth-gateway 行不行。 這決定第 5 件對 cursor-sdk 的使用者成不成立。
  • --trusted-extension 指向 cursor-sdk 的進入點檔案,能不能正常載入。

追平之後

追平只是起點。有些事 omp 做得到,Cursor 做不到:

  • omp 的 web_search 是本機的內建工具,每一次都經過 hook(tools/index.ts:529、sdk.ts:2971-2976)。Cursor 的 WebSearch 在本機完全看不到(Day14)。
  • omp 還有 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 說「做完了」,真的做完了嗎?

今天的 5 分鐘小練習

第 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。然後做兩次:

  1. 請 agent 用 bash 印出它。看模型收到的是不是 $$ 開頭的佔位字串。
  2. 再請它把那個佔位字串寫進一個檔案。打開檔案看:寫進去的是佔位字串,還是原值?

今天對應的威脅: T1、T2、T3。

引用來源:

  • omp 18.2.8 文件
    • 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 — OpenTelemetry
    • docs/bash-tool-runtime.md
    • docs/extension-loading.md
  • omp 18.2.8 原始碼
    • 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
  • cursor-sdk extension:extensions/cursor-sdk/src/index.ts、extensions/cursor-sdk/package.json
  • Cursor 官方文件(2026-09-28 讀取):Agent Security、Run Modes、Hooks、permissions.json reference、Compliance and Monitoring
  • Gartner 新聞稿(2025-06-25):Gartner Predicts Over 40% of Agentic AI Projects Will Be Canceled by End of 2027
  • 本系列的紀錄:verification/live/(L2、L9);刊出的 guard:hooks/injection-guard.ts
  • 本系列 Day13、Day14、Day15

上一篇
同一個模型考兩個平台:六題只差一題、omp 贏——它輸的,是卷子外面的三個地方
下一篇
Agent 說「五件都做完了」,其中兩件根本做不到——Cursor 收工時只收到 completed,omp 可以把它退回去
系列文
一個 Agent 我查不動,一個我改得動:內部威脅偵測的 30 天 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言