# AI 代理操作批准人：按班次明确所有权

**AI 代理操作的批准人**应当是在相关班次内对受影响系统负责的人，而不应默认是那个碰巧打开编码会话的开发者。这两者经常不是同一个人。把他们混为一谈，批准记录看上去可能合法，直到生产环境出现问题，才发现没人能回答是谁接受了这项风险。

我见过这种情况以很普通的方式失败，并不是因为发生了什么戏剧性的攻击。开发者让代理诊断构建问题。代理找到一个相关的生产端点，请求写入操作来调整设置，开发者因为提示出现在自己的终端里就批准了。服务负责人在之后的值班期间才发现这项变更，既没有上下文，也无法得到一个有用的答案来解释“是谁接受了这项风险？”

所有权必须跟随系统、系统当前状态以及正在值班的人。好的批准设计会在操作执行前，让这一点清楚可见。

## 启动会话的人很少真正承担后果

启动代理的人对自己输入的请求负责。但这并不意味着他们自动负责代理可能触及的数据库、供应商账户、部署目标或客户数据。

当编码任务越过系统边界时，这一区分就很重要。一个代码仓库可能包含部署脚本、运维凭据、迁移工具，以及指向由多个团队维护的系统的链接。代理沿着这些路径前进的速度，可能比熟悉该仓库的人类还快。启动者熟悉代码库，并不代表他拥有对每个可到达系统的运营权限。

在设计中将以下三个角色分开：

- 请求者要求代理调查、修改或部署某项内容。
- 系统负责人在指定班次内为目标服务承担运营风险。
- 执行者在获得授权后，拥有执行调用或命令的能力。

在小团队中，一个人可以同时承担这三个角色。只要这一点明确，就没有问题。真正的错误，是因为代理运行在某位开发者的电脑上，就默默把这些角色合并起来。

这也能解决一个常见争论：“开发者要为自己的代理负责。”开发者确实要为指导代理以及提交的代码负责。值班负责人则要为服务行为、数据处理、回滚决策和客户影响负责。权限提示应该交给能够做出后一类决策的人。

NIST SP 800-53 Rev. 5 的 AC-2 控制要求指定账户管理人员，并建立账户管理流程。它没有规定代理批准界面应该如何设计，但其中的管理原则可以直接应用：应明确访问责任，而不是把访问当作当前登录用户天然拥有的属性。对于代理操作，相关账户管理人往往是当前服务负责人，而不是当前使用工作站的人。

## 服务所有权必须包含班次边界

可靠的所有权记录应同时写明服务和当前负责人。在凌晨 2 点、有人休假或事件正在处理中时，只写一个静态团队名称是不够的。

对于代理能够影响的每个系统，都应维护一份包含四项内容的小型名单：主要负责团队、当前班次负责人、备用负责人和升级路径。这份名单可以放在值班系统、代码仓库或内部目录中。存放位置没有那么重要，关键是有人持续更新它，并且批准系统能够查询它。

使用运维人员收到告警时所采用的服务边界。“生产环境的 Payments API”是有用的目标，“后端”则不是。过于宽泛的团队标签会掩盖不同的数据库、供应商、数据分类和回滚流程。

最小记录可以写成这样：

```yaml
service: billing-api-production
active_owner: billing-oncall
backup_owner: payments-duty-manager
escalation: incident-commander
approval_rules:
  read_customer_records: session
  change_remote_configuration: per_call
  production_database_write: per_call
  create_vendor_credentials: prohibited
```

这不是让代理解释的策略语言，而是由人维护的声明，用来说明谁可以做决定，以及某项操作需要多大程度的审查。它要防止的是一种常见失败：批准请求发到一个泛工程频道，有人认出了仓库名称，却没有人认出仓库背后的生产系统。

把这份名单当作运营数据维护。团队重组、新增托管服务或值班轮换变化，都可能让它失效。如果批准路由依赖一份只有某位经理可以编辑的电子表格，那就制造了一个不易察觉的单点故障。

## 操作风险取决于目标，而不是动词

“读取”和“写入”太粗略，无法用来分配批准权限。读取公开状态端点，与读取客户导出文件、部署密钥或完整内部主机列表完全不同。创建临时分支的写入，也不同于修改支付服务商设置的写入。

应根据成功结果可能造成的后果对操作分类。先看目标系统和数据，再考虑可逆性与影响范围。这样，负责人才能有依据地选择批准范围。

可以从少量实用类别开始：

- 常规运营读取不会返回敏感材料，也不会改变状态。
- 范围明确的变更只影响一个已知资源，并且有记录在案的回滚方案。
- 高影响变更会影响生产配置、客户数据、访问权限或外部承诺。
- 禁止操作不应通过自主代理通道执行。

不要因为 HTTP 方法是 GET，就把操作标为低风险。我见过诊断端点返回环境变量、签名链接和本不该交给编码代理的运营细节。必须由了解该端点的人进行分类。

同样，也不要为每一次无害的状态检查都要求人工确认。这样的设计会造成批准疲劳。人们最终会因为习惯了普通提示而直接点击批准，然后批准本不该批准的调用。对于凭据以及每次调用都值得认真决策的目标，才应保留逐次批准。

批准文本必须指出具体目标。“代理请求 API 访问权限”什么也没告诉负责人。“代理进程请求使用 billing-admin 凭据，对生产 billing 配置执行 PATCH”才提供了足够的信息，让负责人停下来提出正确的问题。

## 有边界的会话不是一张空白支票

会话批准应该覆盖一个可识别的代理进程和明确的时间段，而不是覆盖之后从同一仓库或同一用户账户启动的所有进程。

当终端在交接期间一直打开、开发者修改指令后重新启动代理，或者恶意本地进程伪装成熟悉的命令时，这一区分尤其重要。只绑定用户身份的批准范围太大。只绑定进程但不明确进程身份的批准又很容易被误读。

好的会话批准应当用简单语言回答五个问题：哪个进程提出了请求，谁签署或提供了这个进程，它可以使用哪个操作通道，适用哪个服务范围，以及权限何时结束。进程退出时会话也应结束。新进程必须重新获得决定。

Sallyport 的按会话授权遵循这一模式：它显示请求进程的代码签名权限，并且只批准该次运行，直到进程退出。这比因为一个终端标签看起来熟悉就信任它更可靠，因为终端标签不是身份边界。

高影响凭据不要包含在会话授权中。即使负责人十分钟前批准了代理的诊断会话，生产数据库写入或供应商访问变更仍应在每次使用时提示当前负责人。第一次批准的意思是：“这个进程可以在这个系统上工作。”后一次批准的意思是：“我接受这项具体的、不可逆或敏感的操作。”这两种判断不同。

避免使用“开发者工具”这类标签授予永久批准。它们会变成不可见的权限池，也会让事件复盘变得非常困难，因为没人能确定批准人是否预料到这个特定代理会使用该能力。

## 交接班必须转移权限，而不只是传递信息

如果昨天的会话和批准仍然在上一位负责人名下继续有效，那么一条“Alex 现在开始值班”的交接消息并不能解决代理批准问题。

离岗负责人应像交接一条尚未完全缓解的告警一样，交接正在运行的代理工作。记录进程身份、目标服务、请求范围、过期时间和等待确认的操作。接班负责人在接受班次前，必须能够看到这些记录。

可以采用以下交接顺序：

1. 结束或撤销离岗负责人不再愿意支持的会话。
2. 列出必须继续运行的活跃会话，并注明目标和过期时间。
3. 将服务名单移交给接班负责人，并确认其通知路径。
4. 对新的高影响调用，要求接班负责人重新做出决定。

不要仅仅因为工程任务还没有完成，就把上一班的宽泛批准交给下一班。接班人可能面对不同的事件背景、维护限制，或了解到正在发生的供应商问题。批准必须由接班人自己做出。

交接时最棘手的情况，是某项操作已经开始。如果操作可逆且可观察，可以让它在原有授权下完成，并让结果对新负责人可见。如果操作具有破坏性、会对外产生影响，或正在等待第二次调用，就应在边界处停止并重新询问。几分钟的延迟，总比让陌生人继承一项未经审查的生产变更成本低。

## 事件处理中需要更窄的权限，而不是更模糊的记忆

事件处理中，团队自然希望快速行动。他们常见的做法是授予代理一项宽泛且长期有效的权限，让它“帮助修复生产环境”。这项权限会在紧急状态结束后继续存在，最终变成一个没人能解释的漏洞。

应将批准责任交给事件指挥官，或由指挥官正式授权、负责受影响系统的人。条件允许时，服务的正常值班负责人仍应参与，但当多个团队都在接触同一依赖项时，事件处理需要一名统一的决策者。

将事件编号写入批准记录，并把权限限定在相关服务和修复操作上。设置与工作相匹配的短暂过期时间，在事件结束时关闭或撤销权限。

假设代理被要求缓解一个失控队列。它检查指标，提出配置变更，并请求执行清空消息的命令。事件指挥官在检查回滚方案后，可以批准临时调整并发数，但不应因为删除消息更快就直接批准。请求必须明确是哪一个队列、哪些消息、有哪些恢复路径，以及客户是否会丢失工作。

速度来自预先准备好的权限路径、清晰的负责人和易于理解的请求，而不是让每个响应者在一个下午内都成为生产管理员。

## 批准提示必须促成有用的决定

如果一名有能力的负责人无法在几秒内看懂自己批准的内容，提示就失败了。如果普通操作却要求负责人进行全新的安全分析，提示同样失败。提示应呈现负责人在日常运维中已经会考虑的决策点。

提示中应包含请求者身份、代理进程身份、操作通道、凭据标签、目标、操作和范围。对于命令，应显示完整命令和远程主机。对于 HTTP 调用，应显示方法、主机、路径以及对请求体的安全描述。不要为了证明凭据存在而显示密钥本身。

以下是有用提示和无用提示的区别：

```text
Request: production configuration change
Process: signed coding-agent process, session 8f3a
Owner: billing-oncall
Credential: billing-admin
Action: PATCH https://api.internal.example/v1/routing/default
Body: {"provider":"secondary"}
Scope: one call
Reason supplied: mitigate provider timeout during INC-482
```

只写“允许工具访问吗？”的提示，会把负责人推向仪式化批准。它既没有告诉负责人目标，也没有说明影响。如果工具无法提供足够上下文来做出决定，就应拒绝操作，直到请求者补充这些信息。

不要让自由填写的“理由”字段承担安全责任。代理可以轻易生成有说服力的文字。把理由当作给人的上下文，同时由系统强制检查目标、凭据和批准范围。

## 审计记录必须回答那些令人不舒服的问题

发生意外变更后，人们会问：谁批准了它，哪个进程执行了它，使用了哪项凭据，触达了什么目标，以及之后是否有人修改过记录。无法回答所有这些问题的审计轨迹，最多只能算调试辅助工具。

将会话决策与单个操作事件分开保存，但要把两者关联起来。会话记录说明进程和批准负责人。活动记录说明每次调用或命令及其结果。拒绝和撤销也要记录。失败尝试经常能解释后续的替代方案，也可能暴露某个进程正在试探边界。

让记录具备防篡改能力。哈希链日志可以在有人独立验证链条时，检测出删除和修改。它不能把错误的批准变成正确的批准，也不能取代访问控制，但能让调查人员检验历史是否仍与记录中的顺序一致。

Sallyport 将会话和活动日志写入一种写入方不可见的加密哈希链日志，`sp audit verify` 可以在离线状态下对密文验证哈希链。这种设计很有用，因为审核人员无需访问运行环境中的机密，就可以检查记录的完整性。

不要把代理操作埋在通用应用日志中。这类日志往往缺少人工决策，轮换很快，还会把无关噪音混入事件轨迹。应保留一份值班负责人、安全审核人员和事件指挥官都能直接阅读的记录，而不是让他们从六个系统中拼凑故事。

## 所有权失败通常始于一个方便的例外

危险模式通常从一个看似合理的捷径开始。高级开发者需要完成迁移，服务负责人却在另一个时区。于是有人加入一个通用批准组，授予可重复使用的凭据，或者让会话一直维持到周末。这个例外奏效了，于是变成了非正式流程。

之后，代理收到范围更大的任务。它能够触达超出原始请求所需范围的目标。原来的开发者可能已经睡着，负责人可能已经轮换，通用组则可能以为别人检查过提示。每个单独决定看起来都说得过去，但它们合在一起就消除了责任归属。

解决办法是让例外有结构，而不是保持非正式状态。例外应写明服务、批准人、理由、结束时间和复查节点，并生成清晰可见的记录。它不应默默扩大开发者的长期权限。

不要听从用庞大规则引擎解决所有问题的流行建议。规则看起来很有吸引力，因为团队会设想自己可以编码每个仓库、分支、端点、时间窗口和职位。但实际上，没有人能解释某次调用为什么匹配，过时规则最终会变成没人打算保留的权限。先从一个小型决策模型开始：保险库访问权限、范围明确的会话决定，以及对值得逐次审查的操作进行逐次确认。

这个模型会迫使团队用简单语言解决最困难的问题：现在谁负责这个系统，他们究竟愿意授权什么？

## 用真实的交接班测试所有权模型

在严重事件发生前，通过一次受控演练就能发现大多数设计缺陷。选择一个类似真实值班服务的非生产系统。在计划交接班前后启动代理会话，先请求一次常规读取，再请求一次需要单独确认的变更。

观察人们在哪些地方犹豫。离岗负责人能看到活跃会话吗？接班负责人知道自己现在负责哪些服务吗？批准请求标明了进程和目标吗？任何一方都能撤销会话吗？审计记录是否按顺序显示了被拒绝、获批准和已执行的操作？

不要接受“我们会在聊天里解决”这样的回答。聊天适合协调，但不能建立决策边界，也不能保存完整的操作记录。依赖记忆的交接流程，必然会在最忙的夜晚失败。

先为代理能够触达的第一项生产服务写下当前负责人和备用负责人。然后让代理针对该服务提出一项范围明确的请求。如果你必须四处询问才能确定谁应该批准这次调用，那么代理已经先于你的所有权模型进入了生产环境。
