你已經把 AI Agent 關進 sandbox,也把 API key 藏進 Secret Manager;但它若拿著 Alice 的有效權限呼叫行事曆、付款或資料庫 API,後端仍可能只看見「憑證有效」。Agent Runtime Controls 要補的,正是模型做出決定之後、真實副作用發生之前的那一層。
這篇專為第一次設計 Agent 權限的讀者寫。我們會先畫出使用者、Agent、工具與資源的威脅模型,再用一個不需安裝套件的 Python lab,實際驗證錯誤身分、越權、重放、執行中撤銷與收據竄改。你不必先懂 OAuth;每個術語都會換成白話。
先說結論:每一次副作用都要回答四個問題
Agent Runtime Controls =誰在做(身分)+能做什麼(權限)+此刻還有效嗎(撤銷)+事後如何驗證(歸因)。
- 身分:同時保留委派使用者
subject與真正執行的 Agentactor,不能只記一個共用 service account。 - 權限:每個 run 只拿到需要的 action、resource、風險上限與額度;高影響動作再綁一張人工批准收據。
- 撤銷:不是只等 token 到期,而是每次副作用前查 live state,並在真正 commit 前再查一次。
- 歸因:把請求、政策版本、決策與結果串成可驗證 receipt;它要能揭露竄改,也要誠實標示自己證明不了什麼。
把四道閘門想成機場:護照確認你是誰,登機證限制航班,登機門可在最後一刻撤銷,行李與登機紀錄則留下可查的事件鏈。只有監視器而沒有門鎖,叫觀測;只有門鎖而沒有登機紀錄,出事後仍很難追查。
Agent Runtime Controls 是什麼?它不等於 sandbox
Agent sandbox主要隔離檔案、程序、網路與主機;Secret 安全層降低憑證被模型或 log 看見的機會;Prompt Injection 回歸測試檢查敵對內容能否改變行為。三者都重要,卻沒有單獨回答:「這個 Agent 現在能不能代表這個人,對這個資源做這個動作?」
2026 年 2 月的 NIST NCCoE Agent 身分與授權概念文件把問題拆成 agent identification、authentication、dynamic authorization、least privilege、on-behalf-of delegation、auditing 與 non-repudiation。它仍是概念文件,不是已定稿的 Agent 安全標準;但這份問題清單很適合拿來驗收架構。
OWASP LLM06:2025 Excessive Agency則把常見根因歸到功能、權限與自主性過大,並建議細粒度工具、下游最小權限、使用者脈絡、高影響動作批准,以及所有請求都經過 policy 驗證。白話說:不要要求模型自己守規矩,要讓它沒有繞過規則的執行路徑。
先畫威脅模型:誰代表誰,最後改了什麼?

本文用一個排程 Agent 當完整例子。Alice 說:「幫我在團隊行事曆建立 30 分鐘會議。」系統裡有四個角色:
- Alice(subject):權限的原始擁有者,也是 Agent 代表的人。
- Scheduler Agent(actor):真正提出 API action 的軟體工作負載。
- Action Gateway(PEP):唯一能把提議轉成副作用的 enforcement point。
- Calendar API(resource):最終接受建立、修改或刪除事件的下游系統。
要防的不是只有惡意 Agent。正常模型也可能因幻覺、間接 prompt injection 或參數組裝錯誤而要求 calendar.delete_all。更麻煩的是 confused deputy:低權限使用者借用高權限服務身分,讓下游只看見 Agent,卻看不見原始授權者。這也是為什麼 OAuth Token Exchange RFC 8693會分開 subject 與 actor,讓 delegation 與 impersonation 不再混成一件事。
Agent Runtime Controls 的 4 道閘門
閘門 1:短效 capability,把「誰代表誰」綁在一起
第一道閘門不是讓 Agent 自報姓名,而是由可信 issuer 發一張短效 capability ticket。最小欄位包含 subject、actor、run_id、audience、expires_at 與唯一 grant_id。Gateway 還要把票上的 actor,和 mTLS、DPoP 或 SPIFFE 等外部機制已驗證的 workload identity 比對;若 actor 只是函式參數,任何呼叫者都能冒名。
Ticket 越窄越好。RFC 8707 Resource Indicators提供 resource/audience 綁定,RFC 9396 Rich Authorization Requests則能表達 action、location 與其他 API-specific 限制。短效 bearer token 只縮短被偷後的利用時間;若要限制換機重放,還要考慮 DPoP或 mTLS sender constraint,而且 token 與綁定私鑰一起失竊時仍守不住。
閘門 2:Action Gateway 逐次做權限與風險決策
第二道閘門把模型輸出的自然語言,轉成可以精確比較的結構化請求:
{
"action_id": "act-001",
"intent": "替 Alice 建立 30 分鐘專案會議",
"action": "calendar.create_event",
"resource": "calendar:alice:team",
"risk": "medium",
"cost": 1
}
Gateway 依序比對 action allowlist、resource allowlist、風險上限、額度、時間與 idempotency key。高風險動作不能只傳一個 approved=true;批准收據至少要綁 approver、canonical request hash、action ID、有效期與一次性 nonce,參數改變就重新批准。把這一步寫進 Agent Harness 的工具迴圈,比在 system prompt 寫「不要刪資料」更接近真正的強制控制。
「所有動作必經」比 policy 語法更重要。若 Agent 還能用 shell、另一組憑證、IPv6、內網 socket 或 provider-side tool 直接連後端,Gateway 只是可選的觀測點。後端應拒絕所有不帶 Gateway/sidecar workload identity 的流量,網路 egress 也採預設拒絕。
閘門 3:撤銷要在 commit 前再查一次
最容易犯的錯,是 run 開始時驗一次 token,之後就一路相信到結束。正確流程是 propose → authorize → prepare → re-check → commit:先檢查權限,準備好確定的 canonical request,在真正寫入行事曆前再檢查 run、grant、policy 與 kill switch。
RFC 7009 Token Revocation定義撤銷端點,RFC 7662 Introspection可讓 resource server 在 call time 查詢 token 是否 active;但多副本 cache、離線驗 JWT、網路分割與已交換的 descendant token 都會形成延遲窗。RFC 8693 也明說 token exchange 不會自動建立撤銷傳播鏈。因此要量測的是「按下 revoke 後,最後一個仍被接受的動作發生在何時」,不是控制面回了 200 就宣告完成。
Kill switch 也不是時光機。它至少要分開顯示:撤銷訊號已接受、所有 worker/queue/retry 已停止、先前副作用已確認或已補償。外部 API 若早已接受付款或刪除,取消通常不會自動回滾;這時要靠 Agentic Transaction 的冪等、交易與補償處理。
閘門 4:Receipt 記 authority chain,不只記「成功」
一張有用的 receipt 至少記錄 subject、actor、run、grant、action、intent、canonical request hash、policy version、decision、reason、result hash、時間、sequence 與 previous hash。OPA decision log提供 decision ID、input/result 與 bundle revision 等好材料;但 decision log 本身不自動等於防竄改 ledger,也可能受 drop rule 或傳輸失敗影響。
本文 lab 用 HMAC 加 hash chain,能在「持有共享密鑰、取得完整鏈」的假設下發現事件被修改或重排。它不能證明被刪掉的整條尾端,也不能向不信任密鑰持有者的第三方提供 non-repudiation。正式環境要把 head hash、事件總數與時間戳送到獨立 checkpoint,並讓 audit collector 與執行器分權;即使簽章有效,也只證明 issuer 寫過那句話,不保證真實副作用一定發生。
動手做:零套件的 Agent Runtime Controls 最小 lab
把下面程式存成 runtime_controls_lab.py,再執行 python3 runtime_controls_lab.py。程式不會連網、不會呼叫真實行事曆;它用記憶體模擬一條 action gateway,目的只是讓四個概念變得可觀察。
import copy, hashlib, hmac, json
TOKEN_KEY = b"replace-in-production"
LOG_KEY = b"separate-log-key"
revoked_runs, used_actions, receipts, committed = set(), set(), [], []
def canonical(v):
return json.dumps(v, sort_keys=True, separators=(",", ":")).encode()
def mac(key, v):
return hmac.new(key, canonical(v), hashlib.sha256).hexdigest()
def sha(v):
return hashlib.sha256(canonical(v)).hexdigest()
def add_receipt(cap, req, decision, reason, result, identity_verified):
p = cap.get("payload", {})
event = {
"seq": len(receipts) + 1,
"prev_hash": receipts[-1]["hash"] if receipts else "GENESIS",
"authn_status": "verified" if identity_verified else "unverified",
"subject": p.get("subject") if identity_verified else None,
"actor": p.get("actor") if identity_verified else None,
"claimed_subject": None if identity_verified else p.get("subject"),
"claimed_actor": None if identity_verified else p.get("actor"),
"run_id": p.get("run_id"), "grant_id": p.get("grant_id"),
"action_id": req.get("action_id"), "intent": req.get("intent"),
"request_hash": sha(req), "policy_version": "runtime-v1",
"decision": decision, "reason": reason, "result_hash": sha(result),
}
signature = mac(LOG_KEY, event)
row = {**event, "signature": signature,
"hash": sha({"event": event, "signature": signature})}
receipts.append(row)
def verify_log(items):
previous = "GENESIS"
for seq, row in enumerate(items, 1):
event = {k: v for k, v in row.items() if k not in {"signature", "hash"}}
signature = mac(LOG_KEY, event)
expected_hash = sha({"event": event, "signature": signature})
if (event["seq"] != seq or event["prev_hash"] != previous
or not hmac.compare_digest(row["signature"], signature)
or not hmac.compare_digest(row["hash"], expected_hash)):
return False
previous = row["hash"]
return True
def issue(grant_id, run_id, now):
payload = {
"grant_id": grant_id, "subject": "user:alice",
"actor": "agent:scheduler:v3", "run_id": run_id,
"audience": "calendar-gateway",
"actions": ["calendar.create_event"],
"resources": ["calendar:alice:team"],
"max_risk": 2, "expires_at": now + 60,
}
return {"payload": payload, "signature": mac(TOKEN_KEY, payload)}
def execute(cap, req, verified_actor, now, before_commit=lambda: None):
p = cap.get("payload", {})
identity_verified = False
def deny(reason):
result = {"ok": False, "committed": False, "reason": reason}
add_receipt(cap, req, "deny", reason, result, identity_verified)
return result
# Gate 1:verified_actor 必須由外部 workload authenticator 提供。
if not hmac.compare_digest(cap.get("signature", ""), mac(TOKEN_KEY, p)):
return deny("invalid capability")
if verified_actor != p["actor"] or p["audience"] != "calendar-gateway":
return deny("wrong actor or audience")
identity_verified = True
if now >= p["expires_at"]:
return deny("expired")
# Gate 2 + 第一次 Gate 3。
if p["run_id"] in revoked_runs:
return deny("run revoked")
if req["action"] not in p["actions"]:
return deny("action outside scope")
if req["resource"] not in p["resources"] or req["risk"] > p["max_risk"]:
return deny("resource or risk denied")
if req["action_id"] in used_actions:
return deny("replay detected")
used_actions.add(req["action_id"])
prepared = {"action_id": req["action_id"], "status": "prepared"}
before_commit()
# 第二次 Gate 3:緊貼 side effect 再驗證。
if p["run_id"] in revoked_runs:
return deny("run revoked before commit")
result = {**prepared, "status": "committed"}
committed.append(result)
add_receipt(cap, req, "allow", "four gates passed", result, identity_verified)
return {"ok": True, "committed": True}
def req(action_id, action="calendar.create_event"):
return {"action_id": action_id, "intent": "替 Alice 建立專案會議",
"action": action, "resource": "calendar:alice:team", "risk": 2}
NOW = 2_000_000_000
grant = issue("grant-001", "run-001", NOW)
assert not execute(grant, req("a0"), "agent:unknown", NOW + 1)["ok"]
print("PASS 1/6: wrong actor denied")
assert execute(grant, req("a1"), "agent:scheduler:v3", NOW + 2)["ok"]
print("PASS 2/6: scoped action committed")
assert not execute(grant, req("a2", "calendar.delete_all"),
"agent:scheduler:v3", NOW + 3)["ok"]
print("PASS 3/6: over-privileged action denied")
assert not execute(grant, req("a1"), "agent:scheduler:v3", NOW + 4)["ok"]
print("PASS 4/6: replay denied")
grant2 = issue("grant-002", "run-002", NOW)
assert not execute(grant2, req("a3"), "agent:scheduler:v3", NOW + 5,
lambda: revoked_runs.add("run-002"))["ok"]
assert all(row["action_id"] != "a3" for row in committed)
print("PASS 5/6: mid-run revocation blocked commit")
assert verify_log(receipts)
tampered = copy.deepcopy(receipts); tampered[0]["decision"] = "allow"
assert not verify_log(tampered)
print("PASS 6/6: receipt tampering detected")
正常輸出會依序出現 6 行 PASS。第 5 行最關鍵:請求先進入 prepared,回呼隨即撤銷 run-002,第二次檢查發現權限已失效,因此 a3 沒有進入 committed。這就是「run 開始時有權,不代表 commit 時仍有權」。
這份 lab 刻意省略正式系統最難的部分:issuer 與 workload attestation、schema/大小限制、一次性且綁請求的人工批准、跨節點鎖與唯一索引、budget 原子扣除、transactional outbox、外部 checkpoint、密鑰輪替,以及 downstream rollback。risk 與成本也應由可信 tool registry/policy 依正規化參數計算,不能相信模型自報;此處 resource 只是 opaque demo ID。集合與串列都是單程序記憶體,兩個 worker 同時檢查時仍可能穿透額度或重放檢查。因此 6/6 只證明這六個合成斷言在這份單程序模型通過,不代表企業級 zero-trust 已完成。
把 lab 升級成正式系統:用攻擊驗收,不用功能清單
- 錯 actor:拿 Alice 的 ticket,從另一個 workload 呼叫;後端應以外部驗證身分不符拒絕,而且 denial receipt 要把未驗證欄位標成
claimed_*,不能誣指 Alice。 - 越權與 confused deputy:把
create_event改成delete_all,再改 resource、audience 與 subject;每一種都必須 fail closed。 - 重放:相同 action ID 與 payload 重送時回原結果或拒絕;相同 ID 換 payload 必須衝突。DPoP 的
jti不能取代 business idempotency。 - TOCTOU:在 allow 後替換參數、policy revision 或 resource version;executor 必須確認 commit 的仍是同一份 canonical request。
- 中途撤銷:對所有 replica 持續送請求,記錄 revoke 後最後一個 accepted timestamp;另外測 introspection cache、clock skew、網路分割與 descendant token。
- Gateway bypass:測 direct IP、DNS、alternate port、IPv6、本機 socket、舊憑證與 server-side call;任何一條能碰到後端都算 gate 失敗。
- Audit 竄改:修改、重排、刪除尾端與整條 log;verifier 必須拿獨立 head hash 與 count 才能發現截尾。
- 故障模式:分別關掉 policy、revocation 與 audit service。讀取類動作或許可依設計降級,高風險副作用應明確 fail closed,而不是由 timeout 偶然決定。
若採用 OPA,bundle 更新可動態載入,但遠端散佈是 eventual consistency;receipt 要記實際執行節點的 bundle revision。若用其他 policy engine,也沿用同一原則:Policy Decision Point 給答案,Action Gateway 才負責攔截;別把 policy library 誤當完整控制平面。
AI-Infra-Guard 放哪裡?把它當紅隊,不當閘門
Tencent A.I.G(AI-Infra-Guard)官方把產品定位為 AI red-teaming platform,涵蓋基礎設施、Agent、MCP、Skill 與越獄評估。Agent Scan 會對運行中的 Agent 平台送入測試提示並分析回應,authorization bypass、indirect injection 與 tool abuse 是它的測試面;測試授權繞過,不等於自己就是每次 tool call 必經的授權點。
截至 2026 年 8 月 22 日,本次檢視的官方 README、Agent Scan README 與 API 目錄,描述的是啟動掃描/評估任務、查詢狀態與報告,未把 A.I.G 描述為逐次 grant/deny/revoke 的 runtime control plane。因此本文只把它放在 assessment/red-team 層:用它產生攻擊案例,再由四道閘門證明案例在真實執行路徑上被擋下。這是具名文件的有界觀察,不是宣稱產品其他地方絕對沒有某功能。
部署時還有一條明確邊界:官方 SECURITY.md採單一操作者模型,WebUI 沒有使用者帳號、登入、per-user session 或 RBAC;文件要求只綁本機,若 Docker 暴露 8088,需用防火牆、隔離網路或帶認證的 reverse proxy 保護。不要把掃描控制台直接公開成多人共用的治理入口。
8 個新手最常問的問題
1. 有 sandbox,還要 Agent Runtime Controls 嗎?
要。Sandbox 限制 Agent 對主機環境的能力;runtime controls 限制它代表特定人,對真實 API 做特定副作用。兩層處理的邊界不同。
2. 只用短效 JWT 可以嗎?
不夠。短 TTL 能縮短暴露窗,但離線驗證的自包含 token 不會因控制面狀態改變而自動失效。撤銷敏感動作仍要 call-time state、內省或等價機制。
3. Human-in-the-loop 就一定安全嗎?
不一定。如果批准的是模糊意圖,Agent 可以在批准後換參數。Approval 必須綁 canonical request、actor、approver、有效期與 nonce,執行前再次比對。
4. MCP 已經處理授權了嗎?
只處理一部分。2026-07-28 的 MCP authorization 規格在 HTTP transport 提供 OAuth-based 授權框架,且 authorization 是 optional;它不替你的應用完成 live policy、kill switch、business idempotency 或可驗證 receipt。
5. Kill switch 能立刻停止所有工作嗎?
不能直接保證。Queue、retry、region 與外部 API 可能已接手工作;取消通常是合作式訊號。介面要分別顯示 signal accepted、descendants stopped 與 prior effects handled。
6. 有完整 log 就能歸責嗎?
還不行。你還需要可信 issuer、明確 subject/actor、完整性驗證、外部 checkpoint 與 retention policy。簽章也無法證明一開始就如實記錄。
7. Policy engine 要選 OPA、Cedar 還是 OpenFGA?
先按決策模型選,不要按名氣選。OPA 適合通用 policy-as-code,Cedar 強調 principal/action/resource/context,OpenFGA 偏 relationship-based authorization;不論選哪個,都仍要 PEP、身分來源、撤銷狀態與 audit pipeline。
8. 最小可上線版本應先做哪一件事?
先收斂 side-effect path。列出所有會改外部世界的工具,讓它們只經一個 Gateway,再做「越權拒絕」與「撤銷後不得 commit」兩個測試。路徑仍可繞過時,漂亮的 policy 與 dashboard 都只是旁觀者。
給新手的 5 個重點
- 同時記 subject 與 actor,並用外部 workload identity 驗證 actor。
- Capability 要短效、單一 audience、單一 run,而且 action/resource 足夠細。
- 所有副作用只走 Action Gateway,並在 commit 前重查撤銷與 request hash。
- Receipt 要含 authority chain、policy version、decision 與 outcome;hash chain 還要外部 checkpoint 才能抓截尾。
- 用錯 actor、越權、重放、TOCTOU、撤銷延遲、gateway bypass 與 log truncation 做驗收。
如果你正從零搭建 Agent,可先讀 AI Agent Harness 的基本迴圈,再把本文 Gateway 接在 tool proposal 與 execution 之間;想把整套 AI 工作流做成可重複的能力,也可到 AlphaLab 課程繼續練習。
接著閱讀
左右滑動查看更多推薦
結語:先讓一個真實副作用穿過四道閘門
現在回到那句錨點:誰在做、能做什麼、此刻還有效嗎、事後如何驗證。今天先挑一個低風險但真的會改變外部世界的動作,例如建立測試行事曆事件;為它發一張單一 run 的 capability,經 Gateway 逐次驗證,在 prepare 後撤銷,再確認 commit 沒發生、receipt 仍完整。當這條路徑不能繞過,你才真正擁有第一個 Agent Runtime Control。
