AI 開發

AI Coding Agent 為什麼需要權限控制?從 JSON Tool Call 到 Shell、Git、檔案系統權限的完整安全模型

閱讀約 16 分鐘

安全篇寫過攻擊面。本篇只寫權限模型:工具呼叫是一張 JSON,執行權在宿主。Shell、Git、檔案系統是三道門。點「允許」不等於沙箱已經關上。

閱讀前速覽

門 鎖什麼 誰執行
Shell 命令字串、複合命令 allow / ask / deny;沙箱另算
Git status、diff、commit、push;hooks / config 工作區可寫 ≠ 倉庫中繼資料可寫
文件系统 src、.env、工作區外路徑 工具規則 + OS 沙箱
网络 網域、出站 預設關;單獨開名單

策略在宿主裡評。模型填 JSON,不改門。

權限控制,是因為執行權不在模型手裡

Coding Agent 調工具時,模型只產出一段 JSON:工具名加參數。真正跑 git status、改 src/orders/A-1042.json 的是宿主。Schema 管形狀。權限策略管「過了形狀,還準不準跑」。沒有第二道門,第一道門只是禮貌。

  • Schema:鎖參數。命令必須是字串,路徑必須落在約定前綴。
  • 權限策略:allow / ask / deny。先 deny,再 ask,再 allow。官方順序如此,窄 allow 救不了寬 deny。
  • 沙箱:OS 再擋一層。提示注入繞過模型判斷時,行程仍出不去工作區。點批准 ≠ 沙箱已開。

工具 JSON 怎麼填,見 AI Coding Agent 與 JSON 工具呼叫。攻擊面怎麼收,見 Agent 安全危機與 JSON 驗證。Schema 為什麼是門,見 AI 為什麼需要 JSON Schema。本篇不再複述那三條,只寫 Shell / Git / 檔案怎麼落成物件。

權限物件:allow、ask、deny

Claude Code 把規則寫進 settings.json 的 permissions。規則由宿主評,不是模型評。CLAUDE.md 只能改它想做什麼,不能改宿主允不允許。虛構倉庫,不要貼真實金鑰:

{
  "permissions": {
    "allow": [
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(npm test *)",
      "Read(./src/**)",
      "Edit(./src/**)"
    ],
    "ask": [
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)",
      "Read(./.env)",
      "Edit(./.git/hooks/**)",
      "Edit(./.git/config)"
    ]
  }
}

光桿工具名(如 Bash)作 deny,會把工具從上下文摘掉。帶括號的規則擋住匹配呼叫,工具仍在。複合命令按子命令分別匹配:允許 git status,不等於允許它後面再接別的命令。Codex 用權限檔案(:workspace / :read-only),不要和舊的 sandbox_mode 混寫。Cursor 用 permissions.json 的前綴名單,再加 sandbox.json。

第一道門:Shell

模型交出的是命令字串。宿主先對策略,再決定跑不跑。唯讀查詢通常過門:

{
  "type": "tool_use",
  "id": "call_7f2a",
  "name": "Bash",
  "input": {
    "command": "git status"
  }
}

同一套策略裡,推遠端被 deny。物件形狀合法,執行權仍在宿主。Cursor 的終端名單按前綴匹配:git 能罩住 git status。官方也寫明,Run Mode 是盡力護欄,不是硬邊界。要硬邊界,開沙箱。

{
  "type": "tool_use",
  "id": "call_8a01",
  "name": "Bash",
  "input": {
    "command": "git push origin main"
  }
}

第二道門:Git 不是普通寫盤

工作區可寫,不等於 .git 可寫。Codex 在可寫根下把 .git、.agents、.codex 預設收成唯讀;.git 若是 gitdir: 指標,解析後的目錄同樣保護。Claude Code 沙箱給 worktree 放行共享 .git 的提交所需寫入,但 hooks/ 和 config 仍拒絕。Cursor 同樣保護 .git/hooks 和 .git/config。推遠端這一跳,宿主應當留下物件:

{
  "tool": "Bash",
  "command": "git push origin main",
  "eval_order": ["deny", "ask", "allow"],
  "matched": "Bash(git push *)",
  "rule": "deny",
  "sandbox": "workspace-write",
  "execute": false
}

失敗就回 tool_result,寫明命中哪條規則。不要靜默改遠端。commit 用 ask:人看見 diff 再過。push 用 deny,除非這條會話明確要發布。

{
  "type": "tool_result",
  "tool_use_id": "call_8a01",
  "is_error": true,
  "content": "denied by policy: Bash(git push *)"
}

第三道門:檔案系統要兩層

一層是工具規則。一層是 OS 沙箱。官方把兩層分開寫,是因為它們擋的不是同一種呼叫:

  • 工具規則:Claude 的 Read / Edit deny 管內建檔案工具,以及它認出來的 cat / head 一類命令。任意語言指令碼自己開啟檔案,這層罩不住。
  • OS 沙箱:行程級邊界。預設只寫目前工作區和會話臨時目錄。工作區外要另開路徑。網路預設關。
  • 憑證檔:.env 進 deny。不要只靠「別讀金鑰」這句提示。

MCP 工具是另一張表,每個工具仍要單獨過門。協議對比見 MCP Tool Schema vs Function Calling。本篇不寫怎麼調 MCP,只要求:接進來的工具,權限物件裡要有名字。

五步把權限收成物件

不要從「請小心執行」開始。按門寫:

  1. 先寫 deny。push、hooks、config、.env、工作區外路徑一次列清。
  2. 唯讀命令進 allow。status、diff、test。複合命令按子命令各寫一條。
  3. 寫盤和提交進 ask。人看見路徑和 diff。會話結束,編輯批准不自動變成永久規則。
  4. 打開 OS 沙箱。Codex 用 :workspace,不要隨手 :danger-full-access。Cursor 不要用 Run Everything 當預設。Claude 的 bypassPermissions 只留在隔離容器。
  5. 策略本身當 JSON 審。allow 和 deny 攤開對比。規則檔進版本庫,本機覆蓋進 local,不要混成一份說不清的名單。

Schema 怎麼當門,見 Agents API + JSON Schema。本篇只要求:Coding Agent 上,每道門都有物件,而且由宿主評。

複盤權限,先攤開兩段 JSON

一次越門,多半是規則沒對齊,不是模型突然變野。

  • 把策略和那次 tool_use 格式化,看命中的是 deny 還是漏網的 allow。
  • 用 JSONPath 提取 抽出 $.permissions.deny、$.input.command。
  • 一次過門和一次被拒 JSON Diff。先改規則,再改提示。
  • Git 中繼資料規則和 src 寫盤規則分開審。能改 A-1042.json,不表示能改 hooks。
  • 策略檔、倉庫路徑、會話日誌不要貼進公共格式化站。JSONSort 在瀏覽器本地跑。

兩段軌跡都能按規則重建,你才是在收權限,不是在收「我點過允許」。

常見誤區

  • 以為 Schema 就是權限。Schema 管形狀。合法的 git push 仍然要被 deny。
  • 只寫 allow,不寫 deny。官方順序是 deny 先評。沒有 deny,名單只是加速點批准。
  • 把工作區可寫當成 Git 可寫。hooks 和 config 是另一道門。官方預設保護它們。
  • 只用工具 deny,不開沙箱。指令碼自己開啟檔案,工具規則罩不住。要行程邊界。
  • 在筆電上開 bypass / full-access / Run Everything。官方留給隔離環境。日常會話用 workspace + ask。
  • Codex 新舊兩套一起寫。出現 sandbox_mode,權限檔案不生效。選一套。

FAQ

點「允許且不再詢問」等於永久授權嗎?

不一定。Claude Code 裡,Bash 的永久批准寫進倉庫根的 settings.local.json;檔案編輯的批准只活到會話結束。Cursor 的名單在 permissions.json。先看寫下的物件,不要看按鈕文案。

沙箱開了,還要 deny 嗎?

要。沙箱擋行程摸到工作區外。deny 擋工作區裡不該跑的命令,比如 push。兩層鎖的不是同一扇門。

Git 為什麼單獨列?

因為倉庫中繼資料不是普通檔案。能改 src,不該能改 hooks 和 config。push 是出站,不是寫盤。官方把這幾條從「工作區可寫」裡摘出來。

唯讀模式夠不夠?

夠用來看程式碼。不夠用來改 A-1042。Codex 的 :read-only、Claude 的 plan、Cursor 的唯讀沙箱,是探路檔,不是發布檔。

策略能進 Git 嗎?

能,而且應該。組織共用的 deny / allow 進版本庫。本機放寬進 local,不要提交。託管設定用來關掉 bypass 和 auto。

結論

Coding Agent 需要權限控制,因為模型只填 JSON。Shell、Git、檔案系統是三道門。Schema 過了,宿主還要再評一遍。

先寫清 git push 會撞上哪一條 deny。能指到路徑,權限才算可驗證。模型會換。門應該先穩定。

延伸阅读

更新紀錄: 首次發布