# 经得起审核的智能体操作工单引用

能够调用生产 API 或打开 SSH 会话的智能体，需要的不只是「它执行过操作」这样的记录。审核人员还需要知道，为什么当时允许执行这项操作。变更工单引用可以让他们从观察到的命令或 API 调用，追溯到已获批准的意图。

但这条追溯路径只有在执行前就把引用纳入操作证据时才有效。事件开始后再把工单编号粘贴到评论里，只是补文档，不是控制措施。把两者混为一谈的团队最终会发现，每个高风险操作都挂着一个工单，却没有一个工单能解释操作本身。

## 工单引用连接意图与执行

变更工单回答的问题，和审计事件不同。工单说明某人请求了什么、为什么请求、哪些系统可能受到影响，以及谁接受了风险。事件说明智能体实际上对某个具体目标、以某个具体身份尝试了什么，以及结果如何。

让两类记录各自独立，再用稳定的引用将它们关联起来。不要把完整工单正文放进每条事件。工单文本会变化，也经常包含机密内容，还会降低事件搜索质量。保存标准工单标识符，以及调度时真正重要的少量授权事实快照。

例如，对于一次生产数据库迁移，快照可以包含工单引用、修订版本或更新时间戳、获批的维护窗口和变更负责人。对于一次停用账户的 API 调用，快照可以包含引用、请求的账户标识符，以及接受请求的人。操作记录仍然需要保存自己的请求详情和结果。

这种区分可以发现一种常见故障。团队为计划中的缓存配置变更创建了 CHG-418。后来，工程师告诉智能体「清理与这次变更有关的资源」，智能体却使用 CHG-418 删除了一个存储桶。工单确实存在，智能体操作也确实有引用。然而，这个引用并不能证明目标或操作合理。审核人员需要足够的结构化上下文，才能无需阅读聊天记录就看出不匹配。

OWASP Logging Cheat Sheet 建议记录安全相关事件的时间、地点、主体和内容。这条建议在这里同样适用，但智能体操作还多出第五项：声明的授权上下文。单独一个工单引用过于单薄，完整复制工单又过于庞杂。应记录引用，以及用于判断操作是否匹配工单的有限事实。

## 敏感工作需要书面边界

对于后果难以逆转、难以发现，或日后解释成本很高的操作，要求提供工单引用。不要让智能体在每次无害读取前都申请工单。一个会打断日常排查的规则很快就会被人绕开，真正重要的操作反而会消失在例外中。

先定义操作类别，不要试图预测每一条危险命令。大多数团队都应包含以下操作：

- 修改生产基础设施、应用配置或已部署代码
- 创建、撤销或修改用户和服务访问权限
- 导出、删除或移动受监管数据或客户数据
- 修改支付、计费、通知或面向公众的行为
- 使用紧急路径，或使用权限范围很广的凭据

SSH 连接本身在一个环境中可能很敏感，在另一个环境中却很普通。分类应依据连接能够执行什么，以及它最终连接到哪里。只读的生产诊断会话可能只需要会话记录，不需要工单。修改防火墙规则的 SSH 命令即使只有一行，也需要引用。

不要只给某个类别贴上「高风险」标签就结束。要在控制点定义可观察的判断条件。例如，任何使用生产凭据并发送非 GET HTTP 方法的请求都必须提供引用；任何针对生产主机组的 SSH 调用都必须提供引用，除非命令匹配已记录的诊断允许列表。具体规则各不相同，但程序和审核人员必须能够一致地应用这个判断。

有人常建议每个操作都必须有工单，因为听起来更有纪律。实际结果通常是一堆笼统的工单、复制来的引用，以及没人阅读的审批。应在工单要求真正能形成决策点的地方使用它。其他操作仍要保留完整日志，因为缺少工单不能变成缺少事件。

## 在请求发出前捕获引用

敏感请求如果缺少有效引用，执行控制点必须在发送 HTTP 调用或启动 SSH 命令之前拒绝它。事后的日志补充任务无法弥补这个缺口。外部系统一旦接受请求，本地记录可能在调查人员最需要重建故障的时刻延迟、被修改，或根本不存在。

请求流程应有清晰的顺序：

1. 智能体提出一项操作，并提供目标、操作类型和工单引用。
2. 控制点检查引用的格式是否符合要求，并获取所需的工单事实。
3. 如果需要人工审批，审批必须同时覆盖拟执行的操作和引用。
4. 控制点写入意图事件，调度操作，然后写入结果事件。

写入意图事件很重要。假设 API 提供方已经收到 `DELETE /v1/projects/acme-prod`，随后却发生了网络超时。如果只记录成功响应，操作日志就会错误地暗示什么都没有发生。意图记录能告诉审核人员系统曾尝试发送请求。在有人检查目标系统之前，结果记为 `unknown` 并不是审计轨迹的缺陷，而是诚实的结果。

SSH 也遵循同一原则。启动进程前记录目标主机身份、命令或获批命令摘要、工单引用和执行开始时间。之后记录退出状态、捕获输出的策略和完成时间。如果进程失去连接，应保留这个结果，不要把它转换成一个看似干净的失败。

不要允许客户端提交一个可变的自由文本字段 `change_note`，然后就认为工作完成了。结构化的 `ticket_ref` 字段可以支持验证、报告和核对。自由文本会给智能体留下一个地方，让它在段落中藏入一个看似合理的编号。

## 工单编号必须匹配请求范围

一个看起来有效的引用，并不能证明操作符合获批工作。只要有相关信息，控制点就应比较工单已知范围与请求；如果信息不足，则应要求人工做出决定。

至少要验证工单存在、未被取消，并且处于组织允许执行的状态。许多团队还会要求当前变更窗口和已批准的负责人。这些检查可以阻止滥用旧工单，但无法发现目标漂移。

目标漂移发生在工单描述一个服务、账户、环境或区域，而请求影响了另一个对象时。最好的防线是工单系统自身提供结构化范围。如果工单包含环境、服务、代码库、账户或维护窗口等机器可读字段，就将它们与请求属性比较。不要试图从正文推断范围。自然语言描述对人很有用，但解析器可能会以令人印象深刻的自信批准一项毫无意义的操作。

如果工单只有正文，就把操作和工单引用一起展示给审批人。审批人应能用清楚的文字看到目标和动作：「在 CHG-418 下，为生产服务 `billing-api` 应用配置更新。」不要只显示「批准 CHG-418 下的智能体操作」。这种措辞隐藏了要求对方做出的确切决定。

不要为了处理工单的每个细节而建立庞大的规则语言。先从一组能够在事件期间解释清楚的比较开始：环境、目标标识符、请求时间，以及请求人或负责人。将不确定的情况交给明确审批。一个范围有限、默认拒绝的检查，比没人能够审计的聪明解释层更可靠。

## 使用便于查询的事件契约

操作事件需要稳定字段，而不是从终端输出拼成的一段叙述。下面是一次操作尝试的通用 JSON 记录。它有意将声明的引用与执行期间观察到的事实分开。

```json
{
  "event_id": "act_01J8M7FQ6F2Y3K9D",
  "event_type": "action.intent",
  "occurred_at": "2025-03-08T14:32:11Z",
  "agent_run_id": "run_7e9d2",
  "actor": {
    "agent_process": "release-agent",
    "human_requester": "ops-204"
  },
  "ticket": {
    "system": "changes",
    "reference": "CHG-418",
    "observed_state": "approved",
    "observed_at": "2025-03-08T14:31:58Z",
    "scope_digest": "sha256:4ea4..."
  },
  "action": {
    "channel": "http",
    "operation": "PATCH",
    "target": "prod/billing-api/config",
    "request_digest": "sha256:35b9...",
    "idempotency_id": "chg-418-billing-01"
  },
  "decision": {
    "reference_required": true,
    "authorized_by": "ops-204",
    "decision_at": "2025-03-08T14:32:07Z"
  }
}
```

完成事件可以将 `event_id` 作为父级引用，也可以使用单独的 `attempt_id`。它应记录 HTTP 状态、SSH 退出码、可用时的服务商请求 ID，以及 `succeeded`、`failed` 或 `unknown` 等结果。不要在这条记录中放入原始授权请求头、会话 Cookie、包含密钥的请求正文或 SSH 私密材料。

当你可以在不让所有日志读取者都能搜索敏感内容的前提下，保留确切负载的证明时，摘要才有价值。哈希前要先对数据进行规范化。定义字段顺序、编码和脱敏规则，否则两个等价请求会产生不同摘要，比较也就变成了表面功夫。

在事件进入长期存储前，可以通过换行分隔的 JSON 发现缺少引用。这个 `jq` 检查会针对缺少引用的敏感操作返回非零退出状态：

```sh
jq -e '
  select(.event_type == "action.intent")
  | select(.decision.reference_required == true)
  | select((.ticket.reference // "") | length == 0)
  | error("sensitive action has no ticket reference")
' actions.ndjson
```

再运行一个互补查询，找出没有完成事件的引用。只有当日志能显示请求的执行是否发生，工单链接才真正有用。

## 不要让智能体自行制造证据

智能体可以提出工单引用，但不能由它自己声明引用已获批准且符合范围。这和让一个进程自行证明其凭据适用，是同一个错误。

为智能体提供两条路径中的一条。第一条路径是由人工在分派工作时提供引用，智能体收到一个短期有效的工作上下文，其中包含该引用和允许的目标范围。第二条路径是智能体向可信的工单查询服务按引用查询工单，控制点再独立检查返回结果后才调度。智能体只能看到构造请求所需的事实。

签名工作上下文可以概念性地写成这样：

```json
{
  "ticket_ref": "CHG-418",
  "allowed_targets": ["prod/billing-api/config"],
  "allowed_operations": ["PATCH"],
  "expires_at": "2025-03-08T15:00:00Z",
  "issued_for_run": "run_7e9d2"
}
```

签发方对序列化后的上下文进行签名。控制点验证签名、有效期、目标、操作和运行身份。智能体无法在不使签名失效的情况下延长有效期或添加第二个目标。如果工单系统无法签发签名上下文，就在服务端保留同样的逻辑，并将查询响应中的事实记录到事件里。

将上下文绑定到特定的智能体运行。没有这个绑定，被攻陷的进程就可以把一次任务获批的引用复制到另一次任务中。如果环境能够提供，还应记录发起调用的代码身份或进程身份。工单中的人名和日志中的匿名本地进程，不能共同建立可信链条。

## 重试和批处理需要逐项证据

一个工单可以授权有限范围内的变更窗口，但绝不能把许多操作压缩成一条含糊的审计记录。审核人员需要区分计划中的操作序列、重复失败、部分完成，以及智能体超出范围的行为。

为每次尝试分配自己的操作 ID。每次尝试都附加共享的工单引用，并用运行 ID 或变更执行 ID 将相关尝试归组。如果目标系统支持幂等性，就记录幂等标识符。幂等标识符有助于判断重试是否造成了第二次变更，但不能取代尝试记录。

设想一个智能体更新十个服务配置。前七次调用成功，第八次超时，智能体又重试了两次。一份有用的日志应准确写出这些事实：十个预期目标、七个已确认结果、一个未知结果和两次重试。一条写着「配置更新已完成」的工单评论，会掩盖唯一一个可能需要人工检查的服务。

对于批处理，要求工单列出有限的对象集合，或附带清单。将清单摘要与运行记录一起保存。如果变更开始后智能体发现第十一个目标，应停止并请求新的范围决策。把发现当成许可，维护工作就会变成未经审核的迁移。

紧急工作同样需要引用，即使引用是在第一次保护性操作之后才创建。记录紧急标记、原因、批准人，以及正常工单创建的时间。不要因为创建事件记录感觉麻烦，就悄悄重复使用普通工单。紧急情况需要更多证据，而不是更少。

## 双向核对工单和操作

每周列出日志中提到的工单，并不足够。需要进行两项独立检查。第一，找出所有没有有效工单引用的敏感操作。第二，找出所有声称已执行、却没有匹配操作证据的已完成或已批准工单。

第一项检查可以发现绕过控制的情况。第二项可以发现虚假的完成说明、智能体路径之外的人工工作，以及集成故障。任何一份报告都不能证明存在违规行为，但两者都能告诉操作人员应该在哪里提出一个具体问题，而且此时上下文仍然可用。

使用稳定的关联规则。如果变更系统包含多个项目，就同时保存系统名称和引用。如果归档后引用可能被重新使用，就加入不可变的工单记录 ID 或快照修订版本。如果工单可以在执行后编辑，就保留操作获批时观察到的状态和范围摘要。否则，后续编辑可能让旧的执行证据看起来像是已获批准，尽管当时并不是这样。

把例外作为正式记录处理。例外记录应说明由谁接受、正常关联为什么失败、发生了什么操作，以及例外何时到期。几个月后，一张记录非正式例外的电子表格会变成一个更弱的第二变更系统。

Sallyport 的 Activity journal 会记录单独的调用，但在出现正式的工单引用字段之前，团队应将工单关联保存在自己的变更记录或配套的不可变事件中。不要声称自由文本备注与调度前捕获并验证的字段具有同等证据价值。

## 保存证据，但不要暴露工单内容

工单系统通常包含客户名称、事件详情、架构说明和访问信息。操作日志在运维和审核期间往往有更广泛的读者。应保存引用和授权快照，需要完整正文时，再让有权限的审核人员打开工单系统查看。

在计算可搜索的表示形式前，先对请求数据进行脱敏。只有在调查需求确实需要时，才保留一个受访问控制的原始版本。摘要可以确认保存的负载未被改动，但无法帮助审核人员理解他们无法取回的负载。应有意识地决定由哪个系统保存受保护的原始内容，以及谁可以取回它。

即使工单系统删除或归档了源记录，也要保留工单引用。引用能为调查人员提供起点，而捕获的状态、目标、决策和结果则让操作记录本身仍然易于理解。如果保留规则要求删除，应记录这一事实，不要留下一个看起来像错误的失效链接。

最初只需要一项简单控制，就能暴露主要缺口：没有结构化引用就拒绝敏感操作，在调度前写入意图，在调度后保存结果。完成这些后，再加入范围比较和双向核对。工单编号应让审核人员更快、更有把握地完成工作，而不是给智能体一个可以附加到高风险操作上的装饰性字符串。
