跳轉到主要內容
有些工具呼叫太具影響而不能盲目允許,又太有用而不能直接禁止——一次 生產資料庫寫入、一次電匯、一次對真實資料的 *.delete。對那些,你 想要一個人在迴圈中:保留呼叫、讓一個人查看,然後只在一個「是」上 才繼續。那正是 pending_approval 裁決所做的。 本頁端到端涵蓋人工介入的代理審批流程:一個被保留的呼叫如何 浮現、一位審查者如何從主控台或一個 webhook 解決它,以及代理如何 重新提交被批准的呼叫。關於該裁決在規則文法中的位置,參見 防火牆規則;關於圍繞它的政策模型, 參見防火牆總覽

1. 一個被保留的呼叫的樣子

當一條規則解析為 pending_approval 時,引擎會排入一筆審批記錄, 而該呼叫不會抵達工具。中繼會傳回 HTTP 400,帶有 error.code firewall_approval_pending;代理將輪詢的審批 id 攜帶在人類可讀的 error.message 中:
{
  "error": {
    "code": "firewall_approval_pending",
    "message": "tool \"db.write\" held for approval (…) — resolve approval 507f1f77bcf86cd799439011 and retry with header X-OrcaRouter-Firewall-Approval"
  }
}
結構化的 error.metadata(存在時)攜帶該裁決的原因細節—— reason_codefactorsrisk_score——而非審批 id。從訊息中解析出 該 id,或從下方的 SDK 輔助函式取得它。 保留是立即的——沒有一個內聯的 long-poll 封鎖你的請求。代理拿回 那個 id,呼叫在伺服器端被停泊在 pending 狀態,而解決發生在頻道外。
一個被保留的呼叫會被記錄為一個裁決為 pending_approval 的防火牆 事件,所以它能在事件記錄中與 deny 事件並排篩選——你總是能看見什麼被保留,以及透過該審批記錄, 什麼被解決。

2. 一個具體範例

撰寫一條規則,為一個人類保留任何對一個生產連線的寫入:
{
  "label": "hold prod db writes",
  "tool_name_glob": "db.write",
  "verdict": "pending_approval",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.connection\",\"op\":\"eq\",\"value\":\"prod\"}]}"
}
現在來看生命週期:
1

代理呼叫工具

代理對著 prod 發出 db.write。規則匹配,引擎保留呼叫,而中繼 傳回 400 firewall_approval_pending,帶有一個 approval_id
2

一個人類(或你的系統)審查

一位審查者解決該審批——在主控台中或透過一個簽署的 webhook 回呼 (參見 §3)。
3

代理輪詢直到被解決

代理輪詢該審批 id,直到它的狀態不再是 pending(參見 §4)。
4

代理帶著審批標頭重新提交

approved 時,代理帶著一個一次性的 X-OrcaRouter-Firewall-Approval 標頭,重新發出完全相同的呼叫一次。 引擎認領該審批,並讓那一個呼叫通過。

3. 解決一個審批

有兩種方式把一個 pending 審批變成 approvedrejected。兩者 都共享一個先決定者勝出的保證——第一個落地的解決會被原子性地 套用,而任何稍後的解決(或一個重複的)是一個冪等的 no-op,傳回 200
Approvals 分頁以最舊在前列出待處理的保留,每一個都帶有工具名稱 與一行指名觸發的政策與規則子句的「Held because…」。(原始呼叫 引數不儲存在審批記錄上——只有工具名稱、來源,與一個 args 雜湊—— 所以審查者從工具加上匹配到的子句做決定。)一位審查者用以下方式 解決一個:
PATCH /api/workspace/firewall/approvals/:id
{ "decision": "approved", "reason": "verified change ticket #4821" }
decision 必須是 approvedrejected。這個路由是 UserAuth(審查者的主控台工作階段)且把關到 Developer+—— 你審查者的身份就是授權,所以不涉及任何共享密鑰。解決會被寫入 工作區稽核記錄。
要把審批接進一個外部系統(一個 Slack 審批、一個工單工作流程), 為工作區設定一個審批 webhook 密鑰,然後把決定 POST 回來:
POST /api/v1/firewall/approvals/:id/callback
{ "decision": "approved", "reason": "auto-approved by change-control bot" }
該回呼以 HMAC-SHA256 驗證:將 X-Orca-Signature: sha256=<hex> 標頭設為 <approval_id>\n<raw_body> 以你工作區審批 webhook 密鑰 設鍵的 HMAC。該 id 是被簽署材料的一部分,所以一個被擷取的簽章 無法被重放到一個不同的審批上。沒有設定密鑰時,回呼驅動的解決 會被拒絕——改透過主控台 PATCH 解決。
為無人值守的執行設定一個審批 webhook 拒絕路徑是安全的預設:如果 沒有人類解決一個保留,該呼叫就停泊著,而代理繼續輪詢。一個被保留的 呼叫絕不會靜默地變成一個 allow。

4. 輪詢,然後重新提交

代理那一側是一個輪詢迴圈,後面跟著一次重新提交。 以一個防火牆閘道範圍的權杖輪詢審批狀態:
GET /api/v1/firewall/approvals/:id
這個路由需要一個帶有防火牆閘道範圍的權杖(用於 /evaluate 與 MCP 閘道的同一個專用閘道金鑰); 一般中繼金鑰會得到 403。它傳回審批文件——等到 stateapprovedrejected 而非 pending。一個跨工作區或未知的 id 會 傳回 404,絕不向另一個租戶揭露它存在。 一旦狀態是 approved重新提交:重新發出同一個工具呼叫,把 審批 id 攜帶在一個一次性的標頭中:
X-OrcaRouter-Firewall-Approval: 507f1f77bcf86cd799439011
引擎會原子性地認領該審批——一次性。攜帶它的第一次重新提交會被 允許通過那一次;同一個標頭的一次重放會發現該審批已被消耗,而被 再次保留,不被允許。一個 rejected 的審批永遠不可被認領,所以 代理應該把拒絕當作一個終端的 deny 處理並挑選另一條路徑。
OrcaRouter MCP SDK 的 HITL 輔助函式會為你執行這個輪詢後重新提交的 迴圈:當 evaluate 傳回 pending_approval 時,它會輪詢 GET /api/v1/firewall/approvals/:id,並在批准時帶著審批標頭重新 提交——你只需撰寫規則並安排審查者。

5. 狀態與角色一覽

狀態意義代理動作
pending已保留,等待一個決定持續輪詢
approved審查者說是帶著標頭重新提交一次
rejected審查者說否當作一個 deny 處理
動作路由Auth · 角色
列出佇列GET /api/workspace/firewall/approvalsUserAuth · Developer+
解決PATCH /api/workspace/firewall/approvals/:idUserAuth · Developer+
Webhook 回呼POST /api/v1/firewall/approvals/:id/callbackHMAC 簽署
輪詢狀態GET /api/v1/firewall/approvals/:id閘道權杖

6. 審批的定位

一個 pending_approval 裁決是防火牆裁決之一 ——它與一個政策中的其他一切組合。兩個值得知道的互動:
  • 技能隔離升級為一個保留。 如果一個被保留的工具呼叫由一個 被隔離的技能擁有,任何未達 deny 的東西都會被自動升級為 pending_approval——隔離與審批是同一個審查 閘門的兩個方向。
  • 影子模式會壓平它。影子模式下, 一個 pending_approval 裁決會被降級為 audit 並記錄為 [shadow] would …,所以你能在一個保留開始把關真實流量之前,衡量 它多常觸發。
這是針對危險的工具呼叫過度代理權的正確控制——在 那些情況下,「問一個人類」的裁決勝過 allow 與 deny 兩者。

接下來去哪裡

裁決

所有六個防火牆裁決與預設裁決。

閘道金鑰

鑄造用於輪詢審批的防火牆閘道權杖。

影子模式

在一個保留把關真實流量之前衡量它。

規則參考

撰寫產生一個 pending_approval 裁決的規則。