# 事件响应代理：不混乱地访问生产环境

生产事件响应代理应该先调查，只有在人批准一组少量、明确命名的操作后才执行恢复。这条边界是实际需要，而不是形式主义。发生故障时，代理收集零散证据的速度可能比疲惫的响应人员更快，但它不能独自决定改变线上状态。

糟糕的设计会给代理一个生产 shell、一个权限过大的云角色，再在运行手册里写一句“自行判断”。这种设计看起来很快，直到代理重启了错误的工作节点池、扩容了有问题的部署、轮换了某个依赖仍在使用的凭据，或把孤立故障扩大成更广泛的中断。好的设计会给代理一张诊断地图，并把恢复操作控制在足够少的范围内，让人能够理解每一项。

## 诊断权限和恢复权限是两种不同的权限

诊断工作是在询问系统发生了什么。恢复工作则是在要求系统变成另一种状态。团队之所以混淆两者，是因为 `restart` 这样的命令看起来很普通，也因为许多控制台允许用户在同一个页面上检查和修改同一资源。把两者视为同一种权限，实质上就是让一个有用的事件助手变成责任边界不清的生产操作员。

诊断操作不应改变服务行为。它们可以读取指标，在规定的时间范围内查询日志，获取部署元数据，检查健康端点，比较配置版本，并收集范围受限的数据库信息。诊断操作仍可能暴露敏感信息，因此同样需要范围限制和审计记录。但它不需要改变应用状态的权限。

恢复操作会改变运行中系统的某个部分，包括回滚、重启、流量切换、扩容、功能开关变更、凭据轮换、队列重放、故障转移、数据库修复和禁用集成。有些操作可以撤销，但默认情况下没有哪一项是无害的。重启可能杀掉唯一持有租约的进程。扩容可能耗尽共享依赖。重放队列可能造成客户消息重复。

分类操作时可以使用这个判断：如果在同一时间重复执行完全相同的操作，结果可能因为状态已改变而不同，就应将它归入恢复操作。如果操作会修改缓存、创建支持工单、发送消息、改变告警静默状态，或写入其他自动化会读取的注释，它同样改变了状态。不要因为它没有接触主数据库，就把它称为读取操作。

NIST SP 800-61 Revision 2 将遏制、根除和恢复与检测、分析分开。这一划分在这里很有帮助。代理可以大幅缩短检测和分析时间。一旦它提出遏制或恢复方案，就需要由负责的人员选择操作并承担后果。这份文件并不能替你解决授权模型，但它的事件阶段可以避免一种危险的假象：调查和干预是同一项工作。

只读角色并不会自动变得安全。日志查询可能包含访问令牌。链路追踪属性可能暴露账户标识符。配置端点可能返回凭据。应将脱敏和字段限制直接设计进诊断接口，而不是让代理能够获取任意原始数据。生产可见性本身也需要边界。

## 给代理提供问题，而不是通用生产 shell

在压力下，如果工具直接表达事件问题，代理通常会表现得更好。通用 shell 会迫使代理在时间紧迫时同时摸清系统和安全操作流程。它也让审查几乎无法进行，因为 `kubectl`、云 CLI、数据库客户端和 SSH 每个都能做远超当前事件需要的事情。

围绕响应人员在最初几分钟会请求的证据，提供诊断操作：

- 获取指定服务和时间范围内的错误率、延迟、饱和度和可用性
- 查找首次错误附近的部署、配置版本和依赖变更
- 使用允许的字段集合搜索结构化日志，并限制结果数量
- 检查指定组件的健康状态和近期事件
- 将金丝雀或某个区域与已知健康的对等对象进行比较

每个操作都需要狭窄的输入契约。`get_service_errors(service, start, end, group_by)` 能让审查人员知道代理请求了什么。`run_query(text)` 几乎没有提供信息，还会诱发意外的全量扫描、不安全的谓词，或被提示注入的查询文本。

应将硬性边界放进工具，而不是要求模型记住它们。指标工具应拒绝过宽的时间范围。日志工具应限制返回记录数，并在数据进入模型前脱敏配置字段。部署查询应接受来自已知资产清单的应用标识符，而不是接受工单评论里提供的任意 URL。这些限制既能降低成本，也能阻止事件调查变成数据外泄演练。

下面的操作目录刻意保持单调。故障期间，单调是优点。

```yaml
incident_actions:
  diagnostics:
    - name: service_summary
      inputs: [service, start_time, end_time]
      limits: {max_window_minutes: 180}
    - name: recent_deployments
      inputs: [service, since_time]
    - name: log_sample
      inputs: [service, start_time, end_time, error_code]
      limits: {max_records: 200, redact_fields: [authorization, cookie, token]}
    - name: dependency_health
      inputs: [dependency, region]
  recovery:
    - name: rollback_release
    - name: set_traffic_weight
    - name: restart_component
    - name: disable_feature_flag
```

这个片段可以防止内部工具中经常出现的一种失败：一个本应只读的代理，因为首个演示方便，就拿到了通用查询端点。后来人们发现，它可以获取所有服务的每一行日志，甚至调用隐藏的写入路径。工具名称不会带来安全性，输入验证、允许列表、受限输出以及不能写入的凭据才会。

不要仅仅因为 SSH 便于检查，就给诊断代理生产 SSH。shell 访问会捆绑文件读取、进程控制、网络访问，并且通常还提供获取凭据的路径。如果必须暴露主机信息，应提供进程状态、磁盘使用率、选定的 journal 条目或受控命令包装器等聚焦操作。包装器应拒绝管道、重定向、命令替换和任意标志。自然语言指令无法让 shell 变得安全。

## 恢复目录必须短到可以演练

人无法对一份无穷无尽的生产修改菜单进行有意义的审批。应为每类服务定义一份简短的恢复目录，其中包含通俗描述、尽可能固定的参数，以及明确的负责人。如果一项恢复操作无法在一张审批卡片中解释清楚，就应先拆分它，再让代理请求执行。

合理的目录可以包括：回滚到紧邻的上一个已批准版本；将某个不健康版本的流量权重设为零；重启一个指定的无状态组件；禁用一个预先存在的功能开关；或暂停一个指定的消费者。目录不应包含“运行任意修复”、任意 SQL、宽泛的 IAM 修改，或从代理对话中复制来的临时脚本。

在事件迫使你讨论这些问题之前，先为目录中的每项操作写清五件事：

1. 准确说明会改变什么，包括环境和资源范围。
2. 列出代理必须收集的前置条件，例如已确认的版本标识符或健康的备用版本。
3. 说明执行后应观察到什么，以及升级处理前最多等待多久。
4. 指出回滚或补偿操作，如果存在的话。
5. 指定可以批准它的人类角色。

目录还需要参数限制。“设置流量权重”太宽泛。“在稳定版本健康后，将 `eu-west` 中的 `orders-v184` 版本设为零百分比”才是一项可审批的请求。请求不应允许模型在没有约束的情况下自行选择区域、版本和百分比。

不要让操作列表看起来很完整。它应该主动排除需要专门判断的操作。数据库迁移修复、数据删除、客户沟通、权限授予和凭据轮换，往往包含通用事件代理无法推断的信息。对于未列出的操作，正确结果应是明确拒绝，并保留截至目前收集到的证据。

短目录也让演练成为可能。运行一次测试事件，询问指定审批人能否区分“暂停消费者”和“删除其积压消息”这两个请求。如果答案取决于阅读源代码或相信代理的总结，审批文本就不够清楚。

## 审批必须将确切请求绑定到具体人员和时刻

只有在授权某个具体操作，而不是授权一个模糊的事件会话时，人工审批才有价值。“批准 payments 的修复”会给代理留下空间，让它在人停止关注后再自行选择修改操作。审批应绑定操作类型、目标、参数、事件标识符、调用者身份和较短的过期时间。

审批请求应展示支持该操作的证据，但要把证据与所请求的修改分开。事件响应人员需要看到：版本 `184` 发布后错误率上升；之前的版本仍可用；选定区域有健康容量。他们还需要准确知道点击批准后会发生什么。

使用包含不可变字段的请求对象，如果任何已批准字段发生变化，就拒绝执行：

```json
{
  "incident_id": "inc-2025-041",
  "action": "rollback_release",
  "target": {"service": "orders", "environment": "production", "region": "eu-west"},
  "parameters": {"from_release": "184", "to_release": "183"},
  "evidence_refs": ["metric:err-17", "deploy:184", "health:183"],
  "requested_by": {"agent_session": "sess-8f2a", "process_identity": "signed-agent-build"},
  "expires_at": "2025-03-08T14:35:00Z"
}
```

执行器返回的结果应保留请求标识符，并记录操作是已开始、已完成、失败还是超时。“回滚完成”这样的状态消息对事件时间线来说过于模糊。代理应读取结果，然后再次运行诊断检查。它不应假设 API 返回成功就代表服务已经恢复。

不要对之后的每项操作使用一次早期审批。审批疲劳确实存在，但一揽子授权只是用歧义替代疲劳。只有目标、预期效果和风险都相同的操作，才可以合并。比如，如果流量移除和某个版本的回滚都已提前固定，一个人可以将它们作为同一恢复包批准。但这不代表同时批准了数据库变更、凭据轮换或另一个区域的操作。

当代理的假设发生变化时，要求新的审批。这可以捕捉一种常见的失败过程：代理最初怀疑是部署问题，获准回滚，随后发现数据库错误，却决定在旧授权下执行另一项操作。即使时钟显示授权尚未过期，它在实质上也已经失效。

## 事件会话可以避免常驻生产权限

事件代理需要一个与人类操作员不同、也与聊天记录不同的身份。记录哪个可执行文件或远程进程请求了访问，它属于哪个事件，调用了哪些诊断操作，以及谁批准了每项恢复请求。没有这种分离，事后复盘就会变成搜索散文式对话，而不是追踪权限来源。

会话开始时应包含事件引用、声明的环境、明确的诊断范围和过期时间。代理进程退出、期限到达或操作员撤销权限时，会话应结束。撤销必须在下一次操作前生效，包括读取操作。在怀疑凭据泄露或提示注入时，持续的读取权限也可能让损害扩大。

与其把每次诊断调用都视为孤立的提示，不如默认采用按会话授权。新代理进程发起第一次请求时，操作员可以检查请求来源，以及它为什么需要生产可见性。之后代理便可以收集证据，而不必为每张图表或每个日志样本都请求审批。恢复操作仍需要逐次调用审批。

在共享机器和类似 CI 的环境中，代理身份与用户身份的区别很重要。人类账户可能有权响应事件，但复制出来的代理进程或恶意工具包装器不一定有权。只要运行环境支持，就应捕获代码签名权限或其他可验证的进程身份。窗口标题和用户自行填写的标签都不是身份。

Sallyport 使用绝对保险库门、针对新代理进程的会话授权，以及每次单独使用的逐密钥选项。这种安排适合事件响应：人可以允许一次范围受限的诊断运行，同时把敏感的恢复凭据留给新的决定。

不要将凭据传入代理上下文，即使只是暂时传入。粘贴到代理提示词中的凭据无法收回，代理还可能在命令、对话记录、日志或外部请求中再次输出它。执行器应持有凭据，执行获准的 API 或 SSH 操作，并只返回调查所需的结果。

## 审计记录必须说明意图和执行情况

事件笔记通常会记录人们认为发生了什么，却很少记录代理发出的确切请求、允许该请求的权限，以及下游系统返回的结果。这四项都需要记录。只写“代理回滚了 orders”这样的时间线，无法回答代理是否真的请求了回滚、人员是否批准了版本 `183`，或部署系统是否实际接受了命令。

应为会话创建、诊断调用、恢复提议、批准或拒绝、执行尝试、结果、撤销和会话结束分别记录持久事件。每条事件都应包含时间戳、关联标识符、进程身份、操作名称、目标、规范化参数和结果代码。敏感请求内容需要谨慎保存：审计应保留含义，但不能变成另一个失控的秘密存储。

哈希链会让后续篡改变得可检测，因为每条记录都包含上一条记录的哈希。但它不能证明收集器最初确实收到过每一条事件。应同时为这两项属性设计方案。让操作执行器难以改写事件写入器，保留上游请求标识符，并定期在代理控制范围之外验证哈希链。

离线验证命令应返回简单、易检查的结果：

```text
$ sp audit verify
verified: 1842 records
first sequence: 2025-03-08T12:01:09Z
last sequence:  2025-03-08T14:42:31Z
chain: valid
```

Sallyport 会从一份加密哈希链日志中同时生成会话日志和单独的活动日志，`sp audit verify` 可以在密文上检查哈希链。这对事件证据很有用，因为验证历史是否被修改，不需要仅仅为了查看日志而打开保险库。

将审计复核排除在实时代理循环之外。代理可以引用自己记录的操作标识符，但事件负责人或审查人员应能独立检查记录。否则，一个错误描述自身行为的代理，也可能控制响应人员看到的证据。

## 一次失败的回滚说明为什么证据必须先于干预

设想某个服务在发布后不久错误率上升。代理看到时间上的关联，于是提出回滚。一个拥有广泛生产权限的代理可能会立即执行。这很快，但也可能是错的。

有纪律的代理会先获取按版本和区域划分的错误明细、近期部署记录、依赖健康状态和资源饱和度。证据显示只有一个区域发生故障，但新旧两个版本在该区域都失败了。回滚只会消耗时间、制造第二次发布事件，却无法解决依赖中断。

代理因此不提出恢复操作，而是报告故障具有区域性，且依赖的健康端点正在失败。如果容量和服务的数据规则允许，响应人员随后批准一个预定义的流量切换，将流量移出该区域。代理只在获得批准后执行，观察错误率变化，并记录请求和结果。

再改变一个细节：诊断操作因为请求的时间范围过宽而返回错误。代理必须报告证据不完整，不能悄悄用更广泛的日志导出重试。限制不是事件期间需要绕过的不便，它们可以阻止模型把不确定性转化成更大的访问请求。

另一种情况更令人不安：已批准的流量切换在 API 层面成功了，但错误率没有下降。代理不应自行升级到重启、回滚或凭据轮换。它应收集下一组允许的观察结果，并准备新的提议。人在事件中也会做出错误决定，但至少应该由人做出那个被记录的决定。

这就是为什么“只在事件期间授予广泛访问权限”这一流行建议会失败。事件会降低注意力、提高紧迫感，而且通常伴随不完整或误导性的遥测数据。在这些情况下，狭窄接口和明确审批更重要，而不是更不重要。

## 在下一次中断前，把拒绝路径写进运行手册

安全的事件代理必须清楚知道什么时候停止。运行手册应要求它拒绝未列出的修改、声明环境之外的操作、缺少前置条件的请求、已经过期的审批，以及会话撤销后的任何操作。每次拒绝都应说明条件，并保留它已经收集的证据。

拒绝路径需要和成功路径一样认真地测试。让代理调查一个生产事件，然后注入一条来自不可信工单的请求，要求获取秘密配置值。确认工具会拒绝它。在审批过期后请求回滚，确认即使代理逐字重复操作文本，执行器仍会拒绝。诊断序列运行期间撤销会话，确认下一次调用失败。

将恢复凭据与诊断凭据分开。如果同一个凭据既能读取日志又能删除队列，审批界面无法修复底层权限问题。执行器必须选择与目录中单项操作匹配的凭据。如果系统无法支持这种分离，就不要在添加更安全的控制点之前，把它放到自主代理之后。

这种模式第一次部署到生产时，应选择一个熟悉的故障和一项范围狭窄的修复。选择一个响应人员已经使用少量读取调用和一项明确恢复操作的服务。衡量代理的证据是否减少了整理事实的时间，审批人是否无需阅读对话记录就能理解请求，以及审计记录是否能重建事件。只有这些答案在演练中都经得起检验后，才扩大目录。

生产代理赢得信任，靠的是比人类响应人员做出更少的选择，而不是做出更大的选择。让它负责寻找事实，在状态发生改变的节点保留人的决定，并让从证据到行动的每一次转换都可见，直到寻呼停止。
