AI Agent 高风险动作审批:批准目标不等于批准最终参数

用户说“可以发”,Agent 随后又重新生成了收件人、正文或附件。系统继续沿用刚才的确认,把变化后的内容直接发出——这类事故表面上有人工审批,实际上批准人与最终执行内容并没有绑定。
据 Google Cloud 官方文档,2026 年 8 月 24 日发布的 Agent 安全治理文章把 human-in-the-loop 描述为:用明确规则识别关键动作,并在动作继续之前要求人工批准。工程上还要再补一层:批准的必须是即将执行的最终参数,而不是一个模糊目标。
把“我要做什么”和“马上执行什么”分开
一次高风险动作通常经历四个对象:
intent:用户想达到的目标,例如“把周报发给项目组”;plan:Agent 计划调用哪些工具;payload:工具执行前的最终收件人、正文、附件和权限范围;receipt:外部系统返回的业务结果。
很多系统在 intent 或 plan 阶段弹一次确认框,然后允许 Agent 继续补参数。这只能证明用户同意了方向,不能证明用户看过最终内容。
适合审批的对象应当是已经归一化的 payload:排序稳定、默认值已展开、附件已确定、敏感字段已遮盖但仍能被批准人识别。
type SendPayload = {
action: "send_email";
recipients: string[];
subject: string;
body: string;
attachmentHashes: string[];
};
function normalizePayload(p: SendPayload): SendPayload {
return {
...p,
recipients: [...new Set(p.recipients.map(x => x.trim().toLowerCase()))].sort(),
attachmentHashes: [...p.attachmentHashes].sort(),
};
}
归一化规则本身也要固定版本。比如 undefined 是否删除、时间是否统一成 UTC、金额是否转成最小货币单位、对象键如何排序,都必须在审批服务和执行器中得到完全相同的结果。否则两边即使处理同一份业务参数,也可能算出不同哈希;更糟的是为了绕过误报而关闭校验。应使用同一份 canonical JSON 实现,并用包含中文、空值、数组顺序和小数的测试向量锁定行为。
审批凭证至少绑定五样东西
一个可执行的审批凭证至少要回答:谁批准、批准了什么、允许哪个动作、在什么时间范围有效、是否已经使用。
{
"approval_id": "apr_01...",
"actor_id": "operator_ref",
"action": "send_email",
"payload_sha256": "<normalized-payload-hash>",
"policy_version": "send-policy-17",
"expires_at": "<iso-time>",
"status": "approved",
"used_at": null
}
payload_sha256 绑定最终参数;policy_version 防止审批后权限规则被替换;expires_at 限制旧批准长期有效;used_at 防止同一凭证被重复执行。
如果团队正在梳理哪些动作可以自动做、哪些必须停下来,可以先按Agent 上线前的分级放权检查项核对只读、建议、需确认和禁止四个层级,再把审批凭证放到真正有副作用的路径上。
执行器必须重新计算哈希
审批服务不能把 approved=true 直接交给工具。执行器拿到凭证后,应根据当前参数重新计算哈希,并逐项验证:
async function executeApproved(
payload: SendPayload,
approval: Approval,
) {
const normalized = normalizePayload(payload);
const digest = sha256(canonicalJson(normalized));
if (approval.status !== "approved") throw new Error("not approved");
if (approval.action !== normalized.action) throw new Error("action mismatch");
if (approval.payloadSha256 !== digest) throw new Error("payload changed");
if (approval.usedAt) throw new Error("approval already used");
if (Date.parse(approval.expiresAt) <= Date.now()) throw new Error("approval expired");
const claimed = await approvalStore.claimOnce(approval.id, digest);
if (!claimed) throw new Error("approval replayed");
return mailTool.send(normalized);
}
claimOnce 必须是原子操作。否则两个 worker 同时拿到同一审批凭证,都可能在 used_at 写入前执行。
回显页面不要只显示一句摘要
批准人需要看到能改变后果的字段。发邮件应显示收件人、抄送人、主题、附件名与哈希;改数据库应显示对象、字段、旧值、新值和影响范围;发布内容应显示账号、标题、正文摘要、图片数量、可见范围和 AI 内容声明。
对敏感数据可以脱敏,但不能脱敏到失去判断能力。例如手机号可以显示末四位,收件人域名应保留;附件不能只显示“文件 1”,至少要有文件名、大小和摘要哈希。
一个实用的回显结构如下:
action: publish_article
account: csdn_account_ref
visibility: public
title: "AI Agent 高风险动作审批"
body_sha256: "..."
assets:
- name: approval-flow.png
sha256: "..."
ai_content_disclosure: true
changed_after_last_review: false
如果 changed_after_last_review 为真,页面应让旧批准失效,而不是提示一个黄色小字后继续。
计划变化后必须重新批准
以下变化都应触发重新审批:
- 新增、删除或替换收件人;
- 正文或关键参数变化;
- 附件内容变化,即使文件名没变;
- 目标账号、可见范围或发布时间变化;
- 工具从只读操作切换为写操作;
- 权限策略或执行器版本发生不兼容变化。
而纯展示字段的变化,例如界面排序或本地化文案,可以由策略明确声明不影响批准。关键是规则必须可执行,不能靠每个调用方自己判断“变化大不大”。
六个反向用例比成功截图更有价值
- 批准后替换附件内容,执行必须被
payload changed阻断; - 两个 worker 同时使用同一凭证,只能有一个成功领取;
- 凭证过期后即使哈希相同也不能执行;
- 从测试账号切到生产账号,旧批准必须失效;
- 工具超时导致结果未知时,不允许拿同一凭证盲目重试;
- 执行失败后,审计记录能同时找到批准参数和外部回执。
这些用例通过后,“有人点过确认”才升级为“最终动作经过可核对批准”。人类审批不是流程里的一个按钮,而是一份有对象、有期限、不可重放、能与业务结果对账的执行授权。
更多推荐



所有评论(0)