# AI 代理活动记录保留：调查人员信赖的证据

AI 代理让原本熟悉的日志问题变成了证据问题。人类可能花一下午点击应用，而代理可能在开发者阅读摘要时发出许多外部调用。如果记录只写着“代理已完成任务”，那么在误删数据、生产环境出现意外变更或某次传输受到质疑之后，它无法回答调查人员提出的问题。

好的保留规则会保存还原操作所需的事实，同时避免为代理接触过的每个密钥和客户记录再建立一份保护不佳的副本。这个区别决定了你收集什么、保存多久、谁可以修改，以及何时必须停止删除。

## 保留应从调查人员必须回答的问题开始

只有在能够回答一个可以预见的调查问题时，活动记录才值得占用存储空间。应从问题开始，而不是照搬另一个产品默认的天数。

对于能够调用 HTTP API 或打开 SSH 会话的代理，调查人员通常需要确认五件事：

- 哪个执行身份发出了请求，哪个人或服务批准了这次运行？
- 它尝试使用什么能力，目标是什么？
- 它要求目标执行什么操作？
- 结果如何，包括拒绝、超时、部分成功或远程错误？
- 团队能否证明记录在事件发生后没有被悄悄修改？

这些问题把操作证据和运行噪声区分开来。CPU 图表可能有助于解释超时，但无法证明代理是否发出了 `DELETE /customers/42`。提示词记录可能解释代理为什么认为删除是合理的，但不能证明请求确实到达了远程服务。

NIST Special Publication 800-92《Guide to Computer Security Log Management》将日志管理描述为一个生命周期：生成、传输、存储、分析和处置。这里最有用的并不是某个神奇的保留期限，而是它要求组织明确日志的用途，并考虑存储、访问和处置。代理团队常常直接跳到收集，因为收集很容易。真正决定这些记录日后有益还是有害的，是处置和访问纪律。

为每一类记录写一条调查说明。例如：“我们保留操作结果，时间要足以在正常发现和分流之后识别并还原一次未经授权的外部变更。”这句话会迫使团队认真讨论期限和字段。“永久保存所有代理日志”只是回避了讨论，并制造出一堆永久存在的敏感材料。

一条记录可以支持多个用途，但应分别写明。安全调查、事件响应、客户支持、发布调试、账单核对和合规可能需要不同的事实和期限。如果某个价值很低的调试用途让完整载荷保存数年，保留策略就失败了。

## 操作记录需要上下文，而不是完整记录

AI 代理活动的保留首先取决于事件结构。应捕获足够少、但能让合格调查人员还原操作并将其关联到会话的事实，同时不保存凭据材料或无关内容。

我通常将信息分成四层。

1. **身份和授权上下文。**记录不可变的事件标识符、带时区的时间戳、代理进程标识符、可执行文件路径或软件包身份、操作系统提供时的代码签名机构、会话标识符，以及与会话关联的审批者或服务身份。记录授权决定和所采用的审批模式。
2. **请求的操作。**记录通道、目标主机、重要时记录端口、HTTP 方法和规范化路由或 SSH 命令类别、凭据别名或内部引用，以及对请求操作的安全描述。
3. **观测到的结果。**记录成功、拒绝、取消、超时、传输失败、适用时的远程状态码、响应分类、相关的收发字节数，以及经过脱敏的错误摘要。
4. **证据绑定。**记录前一条记录的哈希、当前记录哈希、结构版本，以及用于关联相关尝试、重试和后续操作的标识符。

区分目标和请求很重要。`api.example.internal` 是目标，`PATCH /v1/users/42` 是操作。只保存主机的日志无法区分凭据清单查询和禁用账户。只保存路径的日志可能遗漏目标环境，而目标环境往往决定这是无害测试还是安全事件。

在存储前先规范化。将可变标识符放入结构化字段，不要让调查人员从自由文本中解析。若标识符很重要，可以记录 `route_template: "/v1/users/{user_id}"`，并在旁边放置受保护的 `target_identifier` 字段。对于 SSH，如果执行路径能够观察到两者，应区分代理提交的命令和 shell 展开后的远程命令。不要声称自己并不具备的确定性。

一个实用的 JSON 记录可以是这样：

```json
{
  "event_id": "01J8...",
  "occurred_at": "2025-03-08T14:22:31.482Z",
  "session_id": "ses_7f...",
  "actor": {
    "process_id": 18422,
    "signing_authority": "Developer ID Application: Example Developer"
  },
  "authorization": {
    "decision": "approved",
    "mode": "session"
  },
  "action": {
    "channel": "http",
    "destination": "billing.internal:443",
    "operation": "POST /v2/invoices/{invoice_id}/void",
    "credential_ref": "billing-production"
  },
  "outcome": {
    "status": "remote_rejected",
    "http_status": 403,
    "error_class": "authorization"
  },
  "previous_hash": "...",
  "record_hash": "..."
}
```

这条记录没有包含 bearer token、授权请求头或发票正文，但它仍然告诉调查人员：某个已签名进程在一次获得批准的会话中，使用指定的凭据引用尝试对生产环境发起作废操作，并收到 403 响应。

不要把高风险字段藏在 `details` blob 中。自由格式 blob 很快会变成提示词、请求头、个人数据和错误堆栈的垃圾桶，也让按类别执行保留变得不可能。如果一个字段有存在的理由，就为它指定名称、分类、访问组和处置规则。

## 让不同证据类别采用不同期限

为每条代理记录设定同一个期限，解释起来简单，实际运行中通常是错的。紧凑的操作证据应比丰富的诊断内容保存得久，因为紧凑记录可以在不扩大暴露面的情况下说明发生了什么。

使用符合实际调查工作的类别。一个可行的起点是：

| 记录类别 | 典型内容 | 保留决定 |
|---|---|---|
| 会话证据 | 进程身份、审批、开始和结束时间、撤销 | 按关联操作中的最长期限保存 |
| 操作台账 | 目标、操作、凭据引用、结果、完整性字段 | 保存至安全调查期限结束 |
| 诊断详情 | 有界错误文本、耗时、选定的请求元数据 | 保存较短的排障期限 |
| 受保护的载荷证据 | 特定案件所需的脱敏片段或加密捕获 | 只有在有理由时保存，并按自己的计划删除 |
| 策略和配置历史 | 授权设置变更、保留规则版本、导出事件 | 与操作台账同时或更久保存，以满足问责要求 |

这张表是一种方法，并不是说每个团队都需要所有类别。如果代理只读取构建状态，受保护载荷类别可能没有存在的理由。如果代理会修改财务记录，缺少精确目标标识符的操作台账可能过于单薄。

避免“以防万一”保留原始请求和响应的常见建议。它之所以流行，是因为能让早期调试变得轻松，也因为存储看起来很便宜。真正昂贵的不是存储，而是搜索访问、泄露范围、数据主体访问请求、删除保证以及意外捕获密钥。

如果需要证明某个特定正文曾被使用，但不需要保留正文内容，可以保存其加密摘要。摘要本身无法解释含义，如果原始正文在其他地方也不存在，它也无法提供帮助。应将摘要用于关联和日后比较，而不是把它当作操作描述的替代品。

保留失败和被拒绝的尝试。拒绝可能暴露代理尝试错误能力、审批路径损坏或遭入侵的进程正在测试边界。如果这类记录数量远高于成功操作，它们可以采用不同期限，但不要因为“什么也没发生”就优先删除它们。成功尝试发生后，调查人员往往正需要这些上下文。

## 根据发现和响应设置期限，而不是根据存储成本

应从事件仍可能被调查的最晚时间倒推保留期限。计算过程并不漂亮，但能让假设变得清晰。

对于一类记录，应加上：

- 最长的可信发现延迟；
- 启动、界定范围并分配调查所需的时间；
- 从目标系统或供应商获得相关记录所需的时间；
- 适用于该类别的合同、监管或法律义务；
- 为延迟报告和时钟差异预留的适度余量。

假设团队在每月审查中发现一项可疑的生产操作，需要两周确认范围，并可能需要一个月才能取得远程服务的历史记录。30 天的操作记录保留期在调查开始前就已经失败。正确答案并不自动意味着保存数年。团队应选择覆盖实际发现和响应流程的期限，再根据适用司法管辖区和合同中的义务进行检查。

将常规规则与例外分开。普通计划应自动删除记录。案件保全则因为正在进行的事件、争议、审计或法律指示而保存确定范围内的记录。保全结束后，删除应根据策略恢复，而不是因为没人想起这些记录就无限期保留。

不要把备份保留和日志保留混为一谈。包含已删除活动记录的数据库备份，可能在应用声称已经删除记录很久之后，仍让这些记录保持可用。应记录备份是否加密、谁可以恢复、备份持续多久，以及恢复是否会重新引入删除流程已经移除的记录。如果无法从不可变备份中清除单条记录，应在策略中明确说明，并据此设置备份期限。

对于处理个人数据的系统，在确定期限前咨询法律顾问和隐私负责人。隐私法律通常不会为代理活动提供一个普遍适用的数字，但会要求目的限制、数据最小化和可辩护的删除。含糊的安全理由不能成为永久保留完整内容的依据。

## 防篡改需要独立的信任边界

如果一条记录只能由同一个管理员写入和删除，那么在严重事件之后，它提供的保证并不多。防止修改既需要预防措施，也需要留下后来发生变更的证据。

NIST SP 800-53 控制项 AU-9 要求保护审计信息，防止未经授权的访问、修改和删除。这种表述很重要，因为看起来不可变的存储并不能解决访问控制问题，而访问控制也不能揭示每一次不当变更。应同时构建这两层保护。

首先，将操作执行与审计管理分开。记录事件的进程应拥有追加权限，而不是重写或清除历史的广泛权限。管理保留规则的管理员不应随意编辑单条事件内容。删除和修正应使用独立且可审计的流程。

其次，将记录绑定成有序链。每条记录包含前一条记录的哈希，以及自身规范化内容的哈希。编辑旧事件会从该事件开始破坏整条链的后续链接，除非编辑者能够重新生成受影响的序列。哈希链很有用，但存在局限：如果攻击者控制写入者和每一份存储副本，就可以重写整条链。应将签名检查点导出到独立位置，或安排一个在写入者控制范围之外的审查流程来比较检查点。

第三，保护时间信息。系统时钟会漂移，攻击者也可能修改时钟。在采集器中记录接收时间，尽可能记录单调递增的序号，并将时间戳视为需要与外部记录进行比较的证据，而不是唯一真相。RFC 3161 定义了可信时间戳协议。它可以增强某个摘要在特定时间已经存在的证明，但无法证明请求内容正确或获得授权。

第四，不要只声称完整性，要实际验证。验证命令应报告第一个错误序列、预期的前一条哈希、观测到的前一条哈希以及记录标识符。这样的输出才能让操作人员采取行动：

```text
$ audit verify activity.log
records_checked: 18427
chain_status: valid
first_error: none
checkpoint_status: matched
```

验证失败时，在任何人“修复”之前保护受影响的存储。针对复制的数据集运行验证，保存输出，确定最后一个有效检查点，并与独立导出进行比较。先修复链可能会破坏有关篡改本身的证据。

## 一个可信的失败场景能揭示单薄日志隐藏的问题

设想一个编程代理获得了开发任务权限，后来尝试向生产环境的账单服务发出 HTTP 调用。操作网关拒绝了该调用，因为会话授权覆盖的是另一个进程，而不是实际发出请求的进程。几分钟后，一名开发者从另一次代理运行中重试，并没有注意到目标是生产环境。

远程服务返回 200。代理的聊天记录只说它“解决了发票问题”。团队在三周后才从客户投诉中得知此事。

如果记录很薄弱，团队只能找到一个未知“代理”账户发出的成功调用和一个笼统的时间戳。他们无法确定第一次被拒绝的调用是否来自同一个可执行程序，审批是否发生在同一个会话中，使用了哪个凭据，受影响的是哪张发票，或者有人是否在投诉发生后修改了记录。最后，他们只能搜索 shell 历史、聊天导出和远程服务日志，而这些记录的保留期限可能比自己的系统更短。

如果记录有用，调查人员可以还原以下顺序：

1. 一个已签名进程启动会话，并获得与该次运行绑定的审批。
2. 一个身份不同的进程尝试执行生产操作，但遭到拒绝。
3. 后来的会话使用特定凭据引用，针对生产主机，请求对受保护的标识符执行发票作废操作，并收到 200 结果。
4. 活动链可以通过投诉发生前创建的检查点验证。

这并不能证明开发者是否有意执行该操作，但确实能确定哪个进程获得了谁的审批、执行路径做了什么，以及后续调查应从哪里展开。审计记录不应试图讲述动机，而应保存事实，让人们日后评估动机。

在重试之间保存关联标识符，但不要把多次尝试合并为一条最终成功记录。重试可能显示目标变更、凭据切换，或在获准操作前发生的边界测试。最终请求造成损害时，这些细节很重要。

## 记录进入台账前就必须脱敏

加密的审计存储可以保护静态数据，但不能让记录密码、访问令牌、会话 Cookie、私钥或完整客户对象变得合理。一旦原始内容进入长期保留的台账，每一位未来的调查人员、恢复操作员和泄露响应人员都要承担这份暴露风险。

为每个通道建立字段允许列表。对于 HTTP，可以允许方法、目标、路由模板、选定的安全查询参数名称、内容长度、状态和错误类别。明确丢弃 `Authorization`、`Cookie`、`Set-Cookie`、API 令牌、客户端密钥和已知敏感请求头。将请求和响应正文默认视为禁止收集。

只依赖模式匹配的脱敏会漏掉新型密钥格式，也可能破坏证据。首先使用结构化控制：不摄入本就不应出现的字段。然后将模式脱敏作为错误文本或远程服务提供数据的第二道屏障。在事件中保存脱敏规则版本，让调查人员知道它经过了哪些规则处理。

个人数据同样需要这种纪律。账户标识符可能是识别受影响对象所必需的，但完整的个人资料、文档或支持记录通常不属于通用操作台账。假名化可以降低日常暴露，但不要把稳定且可逆的标识符称为匿名。如果有人借助另一张表就能恢复身份，从治理角度看，它仍然是个人数据。

当案件确实需要内容时，创建范围狭窄的证据捕获，并附上案件标识符、指定访问组、到期时间和复核日期。在操作台账中记录该捕获存在，但不要把内容复制到每份下游报告中。这样既能支持日常调查，又不会让所有读者接触最敏感的材料。

## 删除、保全和修正本身也必须留下证据

保留是一个运行流程，而不是安全策略中的一段文字。没有经过测试的计划最终会变成意外的永久存储，或者在事件期间触发自动清除。

通过有记录的任务执行删除。每次运行都应保存策略版本、记录类别、选定的时间范围、已删除记录数量、因保全而跳过的数量、执行者身份和结果。删除任务应采用与操作记录相同的只追加纪律。操作人员不应需要直接访问数据库来“清理”记录。

保全必须有机器可以应用的范围。可以按案件标识符加事件时间范围、会话标识符、目标、行为主体身份或其他稳定选择器定义。不要把保全写成工单中的一句话，因为保留任务无法评估一句话。记录发起保全的人、保全依据、复核日期和解除事件。

修正也应遵循类似原则。系统偶尔会记录错误的解析路由、延迟的时间戳或误导性的分类。保留原始事件，追加一条修正事件，指出原始事件、说明变更的解释、给出原因，并标明执行修正的人或进程。报告应显示修正，但不能隐藏源记录。

在非生产副本上测试完整流程：在每个类别中创建记录，对部分范围设置保全，运行保留任务，验证预期删除，解除保全后再次运行保留任务。然后恢复一个备份，确认它不会制造一个隐藏的、日常可访问的归档，从而与声明的计划相矛盾。

## 易执行的简短策略胜过完美文档

保留策略应适合真正执行它的系统。下面的模板刻意写得直白，因为每句话都对应一个负责人、字段、任务或验证活动。

```text
Purpose: reconstruct externally executed agent actions and investigate misuse.

Action ledger: retain for [period].
Fields: actor identity, session, authorization decision, destination,
operation, credential reference, protected target identifier, outcome,
integrity fields. Exclude secrets and raw bodies.

Diagnostic detail: retain for [shorter period].
Fields: bounded error text and timing. Apply allowlist and redaction rules.

Protected evidence capture: case-only. Require case identifier, expiry,
access group, and documented approval.

Integrity: append records; verify chain [cadence]; export or compare
checkpoints [cadence]. Record verification failures.

Deletion: execute [cadence]. Record policy version, range, result, and holds.
Holds: suspend deletion for defined selectors. Review [cadence].
Corrections: append a correction record; never overwrite an action record.
```

为记录结构、保留计划、隐私审查、事件保全和完整性检查指定负责人。在小团队中，一个人可以承担多个角色，但每项职责仍然需要明确归属。否则，操作代理的人也会成为决定事件发生后哪些证据消失的人。

Sallyport 通过同一份加密且由哈希链保护的审计日志生成 Sessions journal 和 Activity journal，`sp audit verify` 可以在密文上离线验证这条链。只有团队提前决定哪些字段应进入记录，以及保留任务、保全和导出如何处理这些字段，这种设计才有价值。

在第一次真实事件或一次有意设计的桌面演练之后复查策略。请调查人员仅使用按计划本应保留下来的记录还原一次操作。如果他们需要密钥、原始记录或管理员的记忆才能回答基本问题，就修改结构。如果他们能够回答问题，但每条记录都带有客户内容，就在它变成永久负担前缩减结构。
