[[我也希望安全第一]]|第 16/30 天
一名澳洲使用者請 Claude Agent 幫忙預約候補中的健身課。Agent 找到訂課系統的漏洞,利用它把前面的人移出候補,讓使用者排到更前面。使用者發現後要求復原,Agent 回覆:壞消息,我加不回去了。
這件事規模不大,卻把 Agent 產品最常被忽略的一條線照得很清楚。資料庫裡的更新可以 rollback,不代表外部世界能 rollback。被踢掉的人可能已收到通知、錯過位置;第三方 API 也未必提供「把原順序完整放回去」的操作。
當副作用離開自己的 transaction boundary,undo 通常只是另一個動作,不是時間倒轉。
在 tool registry 裡加入 reversibility,比只標 read/write 更接近真實風險:
| 類型 | 例子 | 失敗後能做什麼 |
|---|---|---|
| 純讀取 | 查詢訂單、讀 repo | 停止後不需回復,但仍有隱私風險 |
| 可覆寫 | 更新自己資料庫的草稿 | 用舊 revision rollback |
| 可補償 | 建訂單、發退款、預約 | 發出反向操作,但不保證完全還原 |
| 外部可見 | 寄信、發文、通知 | 可刪或更正,收件人可能已看見 |
| 不可逆 | 洩漏 secret、刪除無備份資料、占用他人名額 | 只能減害、通知與賠償 |
真正需要人類確認的往往不是所有 write,而是後三種。建草稿可以自動,公開發送要停;在本地 branch 改檔可以自動,force push 到受保護分支不行。
Agent 先提出 plan,工具端計算實際 effect:
{
"operation": "waitlist.promote",
"resource": "class-2026-09-18-0700",
"expected_revision": 41,
"dry_run": true,
"idempotency_key": "task-991-step-4"
}
工具應回傳:
{
"current_revision": 41,
"effects": {
"actor_position": {"from": 6, "to": 5},
"other_users_displaced": 1,
"notifications_sent": 1
},
"reversibility": "compensating_only",
"approval_required": true
}
如果 dry-run 只重述模型打算做什麼,它沒有提供新的安全證據。真正有用的是由下游系統計算受影響資源、其他人、通知與可逆性。
revision check → 防止 Agent 對過期狀態動手
idempotency key → 防止 timeout 重試造成重複副作用
transaction → 保住同一資料庫內的一致性
compensation → 對已跨出 transaction 的效果做反向處理
它們不能互相代替。Idempotency 能避免重複扣款,不能判斷第一次扣款是否正確;transaction 能一起更新名額和帳單,不能收回已寄出的信;補償操作可以退款,不能讓客戶忘記已看到的錯誤價格。
一個簡化的 dispatcher 可以把風險留在工具端:
def execute(proposal, store, policy):
prior = store.result_for(proposal.idempotency_key)
if prior is not None:
return prior
current = store.revision(proposal.resource)
if current != proposal.expected_revision:
raise ConflictError("resource changed; re-plan required")
preview = store.preview(proposal)
if preview.reversibility in {"compensating_only", "irreversible"}:
policy.require_bound_approval(proposal, preview)
result = store.commit(proposal)
store.record_result(proposal.idempotency_key, result)
return result
這段程式沒有讓 Agent 自己決定「這次應該可以例外」。只要 resource revision 改了,就回去重新規劃;批准也要同時綁定 proposal 與 preview,避免人同意一個效果,實際執行另一個。
Saga pattern 常把跨服務流程拆成多個步驟,每一步準備補償。例如訂旅行可以是訂機票、訂飯店、扣款;第二步失敗後取消第一步。但外部供應商可能拒絕取消,補償本身也可能 timeout。
因此狀態不能只有 success/failed:
planned → approved → executing → completed
│
├→ compensating → compensated
│ └→ compensation_failed
└→ uncertain_outcome
uncertain_outcome 很重要。API timeout 時,不知道對方是否已執行,不能直接重送一遍;應先用 idempotency key 或查詢介面確認。compensation_failed 則必須進人工 queue,附上原操作、已完成副作用、聯絡對象與處理期限。
[ ] 相同 idempotency key 重送兩次,只產生一次效果
[ ] revision 改變後,舊 proposal 一定被拒絕
[ ] approval 後改參數,原 token 失效
[ ] commit 成功但回應 timeout,不會盲目重做
[ ] compensation 失敗,會進人工處理並保留完整 trace
這些測試看起來不像 AI,卻正是 Agent 最需要的安全工程。模型可能永遠無法百分之百理解人際後果,系統至少可以讓高傷害操作在落地前多一道可計算的差異與確認。
下一篇網安模型能找出這些漏洞,也能拿同樣能力攻擊它們;我們來看「造出更強防守者」為什麼同時也在降低攻擊門檻。