阅读需 8 分钟

审批审计轨迹能证明谁批准了代理操作吗?

构建审批审计轨迹,将点击或 Touch ID 决定关联到用户上下文、代理会话、凭据使用和已执行的调用。

审批审计轨迹能证明谁批准了代理操作吗?

一条只写着“已批准”的审批记录,无法回答代理接触生产环境后最重要的问题:谁允许了哪项操作,依据什么权限,以及接下来发生了什么?它只记录了用户界面中一个令人安心的瞬间,剩下的内容还得由调查人员自行推断。

审批审计轨迹必须保留从代理进程到人工决定、从这个决定到凭据使用,再从凭据使用到已完成的 HTTP 请求或 SSH 命令的完整链条。如果这些内容只是彼此独立、没有持久关联的记录,审查人员或许能讲出一个合理的故事,却无法证明这个故事是真的。

当操作已经成功,却没人记得批准过它;当代理执行任务到一半重启;或者有人问点击确认和 Touch ID 确认是否具有相同含义时,这种区别就会变得格外棘手。它们并不相同。把两者视为同一种审批,日志在第一次严肃审查前看起来或许完整,之后就会暴露问题。

审批事件不能只回答“是”

一条有用的审批事件应说明用户批准了什么、系统为何发出请求、决定是如何作出的,以及授权到哪里为止。可见的按钮点击只是这条事件中的一个字段。

在决定发生时,记录以下事实:

  • 唯一的审批标识符,以及带时区偏移的事件时间戳。
  • 审批方式,例如 clicktouch_id
  • 本地账户或其他已知审批人身份,以及用来建立这一归属的证据。
  • 授权范围,是一个会话,还是一次调用中的一次凭据使用。
  • 触发提示的请求,并用稳定的请求或调用标识符标记。

不要仅仅因为机器上有一个名为 Alex 的账户,就写入 user=alex。这可能是现有条件下最好的归属信息,仍然值得记录,但应准确称为本地账户上下文。如果生物识别提示通过了,应记录“该设备上登记的生物特征授权了这次事件”。这样的表述比“某个叫 Alex 的人批准了它”更严谨,因为它没有假装审计日志知道超出实际范围的事实。

NIST SP 800-171 Rev. 3 为审计内容提供了一个合理起点:时间戳、源地址和目标地址、用户或进程标识符、事件描述、适用的访问控制以及结果。它还指出,详细记录可以包含特权命令,以及共享账户背后的个人身份。这是代理操作应达到的基础线,并不是完整设计。代理审批流程还需要把决定、凭据和调用之间的关系作为一等数据保存。

一个常见错误,是把审批记录成最终操作的属性,例如 approved=true。这样会把一个事件压扁成一个标签。请求和决定之间的时间、决定来源、同意范围以及之后的撤销都会丢失。你也无法区分用户审批、默认允许、缓存授权和自动化规则。

即使答案是否定的,决定也应拥有自己的记录。被拒绝的提示可以解释代理为何部署失败。过期的提示可以解释代理为何重试。保险库锁定可以解释系统为何根本没有发起网络调用。这些是性质不同的事件,之后的审查人员不应从缺少成功记录这一点反向猜测它们的区别。

五种身份让时间线保持真实

完整的时间线需要五种彼此分开的身份。把它们合并起来,虽然能节省表格中的几列,却会破坏调查中的含义。

第一种是代理进程。记录进程或运行标识符、启动它的可执行文件或代码签名方、开始时间和结束时间。用户应该能回答:“哪个正在运行的程序发出了这个请求?”项目名称或聊天记录不够用。两个相同的编程代理可能同时运行,其中一个正常,另一个却指向不同的代码仓库。

第二种是会话。会话是一个代理进程与网关之间有边界的关系。它必须拥有自己的标识符,因为一个进程可能发起多次调用,而且会话授权通常适用于多次调用。进程退出时,会话也应结束。如果之后启动了新进程,即使它使用相同的可执行文件、本地账户和任务描述,也应创建新会话。

第三种是审批人上下文。它包括设备账户、经过身份验证的应用用户和审批方式。不要让审批人字段承载它无法支持的事实。local_account=mayamethod=touch_iddevice_id=... 都很清楚,human=maya 则提出了更强的身份断言。在某些环境中这一断言合理,在另一些环境中,共享工作站或已解锁的桌面就会立即推翻它。

第四种是凭据引用。它标识网关使用的权限,而不是秘密本身。稳定的不透明凭据 ID、便于人理解的标签、通道和凭据类型,通常足以支持活动审查。承载令牌不是审计字段。SSH 私钥指纹本身也可能成为敏感上下文,因此在把它传播到普通日志之前,应先确定调查人员是否确实需要它。

第五种是已执行的操作。对于 HTTP,它包括解析后的目标身份、请求方法、规范化路径、选定的非敏感请求信息、响应状态和耗时。对于 SSH,它包括主机身份、远程账户、命令或已批准命令的摘要、退出状态和耗时。事件必须说明实际运行了什么,而不仅仅是请求了什么。

这些身份构成的是一张图,而不是一行扁平记录:

agent_process
  -> session
    -> approval_decision
      -> credential_use
        -> executed_call

当用户需要快速查看时,扁平的活动界面可以把这张图显示成一行。但底层关联仍应保留。界面是给人浏览一天的工作用的,标识符则是给六周后必须解释某一次调用的人用的。

点击和 Touch ID 是不同的证据

点击记录的是用户在当前界面中与审批控件的交互。Touch ID 记录的是操作系统完成了一次成功的生物识别授权,同时也记录了触发它的交互。两者都可以授权操作,但不应共享一个含糊的值,例如 approved_manually

使用明确的方式字段,并限定可用值。例如:

{
  "approval_id": "apr_01J8K4VY5Q",
  "occurred_at": "2026-07-22T14:18:06.184Z",
  "decision": "approved",
  "method": "touch_id",
  "approver": {
    "local_account": "maya",
    "identity_assurance": "device_account_and_biometric"
  },
  "scope": "credential_use",
  "session_id": "ses_01J8K4TE0M",
  "requested_call_id": "call_01J8K4VPM2"
}

字段名称本身不是关键,分离才是。method 告诉你审批是如何完成的。identity_assurance 告诉你系统可以负责任地对这个人作出什么断言。scope 告诉你决定授权了什么。requested_call_id 将审批关联到一个在人看到提示之前就已经存在的请求。

对于低摩擦确认,点击可以是合适的选择,尤其是用户已经在观察代理运行时。Touch ID 会为敏感操作增加更强的本地确认步骤,但它不会自动提供企业身份、决定原因,也不会代表用户认可之后的每一次调用。如果团队需要通过外部身份提供商确认具体员工,就需要一个能记录该身份提供商断言的流程。不要把企业身份保证悄悄借自本地生物识别事件。

反过来的错误同样严重:把 Touch ID 当成装饰。如果某项操作要求生物识别审批,而日志只剩下 approved=true,记录就无法证明更严格的控制确实运行过。审查人员会失去一项重要证据,也无法区分用户有意确认和误点了范围很大的会话提示。

谨慎记录失败的生物识别尝试。审计轨迹通常需要知道请求的操作没有获得批准,但很少需要记录操作系统级别的每一次身份验证失败。一个有用的事件可以是 decision=denied_or_cancelledmethod=touch_id,并在平台提供这一信息时记录 user_cancelled 等原因。不要把操作网关变成生物识别遥测收集器。

会话同意和单次调用同意的范围不同

会话授权允许一个有边界的代理进程在用户审查进程身份后继续运行。单次调用审批则针对一次凭据使用和一项操作授予同意。如果不记录范围,却把两者都叫作“审批”,之后的时间线就会产生误导。

设想一个在 09:00 启动的代理进程。网关显示一张授权卡,说明该进程的代码签名方。开发者点击批准。09:20,代理使用一个不要求每次调用确认的凭据发起 HTTP 请求。由于会话仍处于授权状态,这次调用可能被允许。正确的时间线应显示两个独立事实:

  1. 09:00,开发者批准了会话 ses_...,授权期限为该进程的生命周期。
  2. 09:20,该会话使用凭据 cred_... 发起了调用 call_...

不应虚构一条 09:20 的用户审批。开发者没有看到并批准这一次具体调用,早先的授权已经覆盖了它。

现在改变一个设置:该凭据要求每次使用都审批。09:20,网关再次发出请求,开发者使用 Touch ID 批准。新的事件必须指向 call_...,标明 scope=credential_use,并包含 method=touch_id。会话审批仍然重要,因为它解释了代理为何能访问凭据请求,但它不能替代第二个决定。

当代理在会话后段发起一项令人意外的调用时,这个区别尤其重要。如果调用旁边只写着“已批准”,审查人员需要知道它代表的是半小时前有人批准了这个可执行文件,还是三秒前有人批准了这次具体的凭据使用。两种情况对提示设计、凭据设置和事件响应的影响完全不同。

不要通过让每次调用都需要审批来解决含糊问题。这个建议听起来很安全,也会产生大量令人安心的记录,但它同样会训练用户不阅读内容就批准重复提示,之后也无法分辨异常调用和日常调用。应让使用时需要最新人工确认的凭据启用单次调用审批,默认保留会话授权,把代理进程绑定到明确、可追责的审批边界。

撤销也需要范围。如果操作员撤销一个会话,应针对该会话写入撤销事件,并记录生效时间。不要覆盖旧的审批。如果用户停用或删除凭据,应单独记录这一变化。审计时间线应解释后续调用为何被拒绝,而不是改写历史,让之前的授权凭空消失。

凭据记录必须标识权限,但不能暴露权限本身

要求每次重新确认
每次调用密钥都要求对单次凭据使用进行点击或 Touch ID 确认。

凭据使用记录是许多团队做出危险取舍的地方:为了让调查更方便,把秘密材料加入日志。这是一笔糟糕的交易。日志会被复制、索引、导出,保存时间也往往比生成它的进程更长。活动日志中的秘密会让每个能读取日志的人都成为凭据持有者。

为每个存储的凭据分配一个不透明且不可变的标识符,例如 cred_01J8K...。再配上便于人理解用途的标签,例如 payments-readonlystaging-deploy。记录通道和注入方式,例如 http_bearerhttp_custom_headerssh_key。这样调查人员就有足够上下文提出正确问题,而不必把密钥复制进记录。

实际的凭据使用事件可以如下:

{
  "credential_use_id": "use_01J8K4WHD7",
  "occurred_at": "2026-07-22T14:18:06.221Z",
  "credential": {
    "id": "cred_01J7ZB7F8P",
    "label": "inventory-production",
    "channel": "http",
    "injection": "bearer"
  },
  "session_id": "ses_01J8K4TE0M",
  "approval_id": "apr_01J8K4VY5Q",
  "call_id": "call_01J8K4VPM2",
  "secret_exposed_to_agent": false
}

当网关设计已经保证这一点时,secret_exposed_to_agent 字段看起来似乎多余。如果时间线可能包含多条执行路径或迁移路径,保留它仍有价值。它让安全属性和操作记录出现在同一条可检查的记录中。如果所有受支持的路径都具备相同保证,也可以把这一字段作为系统设计中的隐含属性,并在其他地方统一说明。

把凭据选择和凭据使用分开。代理可以按标签请求凭据,但只有网关开始执行出站操作时,凭据才算真正被使用。这对拒绝事件很重要。如果用户在请求离开设备前取消了 Touch ID,应写入一次尝试调用和一条被拒绝的审批事件,但不要写入成功的凭据使用事件。否则审计统计会声称生产凭据已被使用,实际却并非如此。

对于 SSH,不要把主机别名视为完整的目标身份。prod-db 便于阅读,但别名可能发生变化。应记录配置中的目标,以及连接流程验证过的主机身份证据。如果代理请求的是 prod-db,但解析后的目标不同,这个差异必须进入执行记录。错误部署发生后,这类细节往往非常关键。

已执行的调用才是操作发生的证据

审批证明了同意,凭据选择证明了预期使用的权限。只有执行记录能告诉你网关是否尝试执行了面向外部世界的操作,以及返回了什么结果。

对于 HTTP 调用,以规范化形式记录操作。保留请求方法、目标源或服务身份、规范化路径、必要时选定的查询字段名称、响应状态、开始和结束时间戳,以及结果引用。应有意识地决定哪些请求和响应字段可以安全保留。授权请求头、Cookie、类似令牌的值、完整请求正文和原始响应正文,都不应进入通用活动时间线。

请求摘要有助于证明已批准的请求载荷与实际执行的载荷一致,但前提是必须精确定义摘要的输入。对 JSON 正文进行哈希时,如果不先规范化字段顺序,就会产生错误不匹配。即使正文中只有一个短小且可预测的值,哈希也可能帮助攻击者确认猜测。应在载荷已由其他机制保护时使用摘要进行完整性关联,不要把它当成处理内容的通用替代方案。

对于 SSH,记录远程账户、目标身份、命令表示、退出状态以及开始和结束时间。完整命令行可能在环境变量、临时 URL 或参数中包含秘密。一种合理的折中方案是为日常审查保存安全的命令呈现,为调查保存受保护的完整表示或摘要。不要声称摘要是可读证据,它只能告诉你两个值是否一致,不能告诉审查人员命令做了什么。

RFC 5424 将时间戳和消息身份与结构化数据分开,因为解析器需要可靠字段,而不是必须从自然语言中猜测的信息。它的时间戳格式还携带时区偏移,并允许小数秒。你不必输出 syslog,但其中的设计经验仍然适用:让事件类型和关联字段保持结构化,把说明留给人类可读文本。

使用不同的事件类型。call.requestedcall.dispatchedcall.completedcall.failed_before_dispatch 比一个含义随状态变化的 call 事件更清楚。额外的记录可以回答网络超时是否发生在凭据注入之后、本地验证是否先拦截了请求,以及远程服务是否返回了响应。

仅凭时间无法确定不同机器之间的顺序。使用带偏移的 UTC 时间戳,并在每份本地审计日志中保留单调递增的序列号。如果远程 API 返回自己的请求 ID,将它作为远程关联值保存。这样调查人员可以把本地时间线与供应商记录进行比较,而不必假设两边时钟完全一致。

普通的成功日志也可能隐藏断裂的时间线

撤销运行中的会话
当代理运行不应继续获得授权时,可以立即从会话日志中撤销它。

设想一个部署代理在 10:02 获得会话审批。它读取代码仓库、准备发布,然后在 10:17 调用生产部署端点。端点接受了请求。10:18,开发者发现选错了环境。

一份薄弱的日志可能是这样:

10:02 approved agent
10:17 deployment API call succeeded

这份日志几乎没有回答任何问题。10:17 的调用是否受到 10:02 审批的覆盖?凭据是否要求第二次提示?是哪一个进程发起了调用?代理使用的是预期的部署凭据,还是权限更广的令牌?请求究竟发往生产环境,还是因为重定向或配置错误才到达了那里?用户是点击批准、使用 Touch ID,还是根本没有看到针对这项操作的提示?

有用的时间线应当这样记录:

10:02:11  session.opened       ses_71  process=proc_44 signer=known_authority
10:02:14  approval.approved    apr_02  method=click scope=session session=ses_71 account=maya
10:17:03  call.requested       call_88 POST deploy.example/release target=production session=ses_71
10:17:04  credential.selected  use_53  credential=cred_prod_deploy call=call_88
10:17:04  call.dispatched      call_88 destination=deploy.example
10:17:06  call.completed       call_88 status=202 remote_request=req_914

这份记录或许能证明代理拥有有效的会话授权,但没有获得针对该调用的单独审批。这并不能证明部署符合用户意图,它只能说明控制机制是如何运行的。团队可以据此决定生产凭据是否应要求单次调用审批、提示是否应更清楚地显示目标环境,或者代理是否根本不应访问该凭据。

现在加入单次调用确认。正确的新增记录不是另一行笼统的 approved,而应写明它批准的调用和范围:

10:17:04  approval.approved    apr_03  method=touch_id scope=credential_use
          session=ses_71 call=call_88 credential=cred_prod_deploy account=maya

如果代理在超时后重试,应为重试分配新的调用 ID。它可以复用已有的会话授权,但如果凭据策略要求单次调用审批,重试就应产生新的审批要求。把重试记录成原始调用,会错误地让人以为一次确认覆盖了两次外部操作。

审计完整性需要单独声明

明确审批范围
会话授权默认开启,选定的密钥也可以设置为每次使用都需要审批。

审计日志可以足以解释一段流程,却仍然很容易被修改。它可以在设计上是只追加的,但拥有本地访问权限的管理员或恶意软件仍可能删除不利记录。应把内容完整性和记录完整性视为两种不同属性。

哈希链式日志通过加密摘要把每条记录与前一条记录关联起来。修改旧记录后,后续链就无法通过验证。这很有用,因为导出的活动界面可以与底层日志进行比对,而不是只能被动相信。它不能证明原始系统记录了每一个事件,也不能证明被攻破的写入方没有伪造记录,更不能证明一次有效审批就是正确决定。这些是不同的断言,需要不同的控制措施。

应在证据离开系统的边界进行验证。调查人员应能取得加密记录流,离线运行完整性检查,并在不暴露凭据的情况下确认序列是否完整。验证输出应标识检查范围、链状态,以及验证失败时第一个失败的序列号。

Sallyport 从同一份不可写、加密、哈希链式审计日志生成 Sessions 和 Activity 日志,sp audit verify 可以在没有保险库密钥的情况下,对密文离线验证链条。这种安排很重要,因为会话决定和单次操作仍然是同一份证据的不同视图,而不是两套可以分别修改的故事。

不要因为能够验证完整性,就无限期保留过量数据。保留期限、访问控制和脱敏仍然重要。一份充满秘密、保存完好的日志,只是在等待某个方便的搜索查询触发事故。应明确谁可以查看原始记录、谁可以导出记录、记录保留多久,以及哪些字段可以安全地出现在日常视图中。

围绕关联构建时间线,然后测试最棘手的情况

审查数据结构时,应先问一个直接的问题:调查人员能否从任意一项已执行操作开始,不靠猜测就回溯到审批?如果不能,在美化活动界面之前先补上缺失的标识符。

针对实现运行一组小型测试矩阵。不需要大规模模拟,只需要能暴露范围和顺序错误的案例:

  • 启动一个新的代理进程,用点击批准其会话,然后发起一次低风险调用。
  • 使用要求单次调用审批的凭据,通过 Touch ID 授权,并确认调用指向这次审批。
  • 取消生物识别提示,确认不会出现成功的凭据使用记录。
  • 结束代理进程,再次启动它,确认新进程不能继承旧的会话授权。
  • 在调用发出后强制远程操作失败,检查时间线是否区分了发送和完成。

从两个方向检查结果。先从审批开始,列出所有依赖它的操作。再从已执行的调用开始,追踪进程、会话、决定和凭据。第一种视角能找出范围过大或有效时间过长的授权,第二种视角能找出证据断裂或含义不清的调用。

界面用语也应和数据模型一样严格。“通过点击批准的会话”很清楚。“使用 Touch ID 批准了这次调用的生产部署凭据”也很清楚。“已批准”只是一个装饰性状态词,让读者自行补出最重要的细节。

第一次有人问“谁批准了这项代理操作”时,不要给他一张带绿色徽章的截图。给他一条时间线,显示进程、决定方式、范围、凭据权限、具体调用和结果。少了任何一项,在演示时或许很方便,但在真正重要的操作面前经不起审查。

常见问题

AI 代理审批日志应包含哪些内容?

它应显示请求、决定、可用于确认审批人身份的证据、所选凭据、实际操作、结果,以及这些记录之间的关联。单独的时间戳无法证明完整的先后关系。如果各条记录无法通过稳定标识符关联起来,你拥有的是几份日志,而不是一条审计轨迹。

Touch ID 能证明是哪位用户批准了操作吗?

只有在系统拥有足以支持这一结论的明确身份绑定时,才能证明具体是谁批准了操作。通常,Touch ID 只能证明已登记的生物特征授权了设备使用;本地账户和设备上下文则标识了会话。应分别记录这些事实,不要把一次生物识别事件夸大成未经支持的个人身份认定。

会话审批等同于批准代理的每一次调用吗?

不能。会话审批表示某个特定代理进程可以继续使用已批准的会话,直到进程退出。单次调用审批则表示用户确认了这一次具体的凭据使用和操作。两者的范围、有效期和审计含义都不同。

如何在不记录秘密的情况下审计凭据使用?

记录稳定的凭据引用、凭据类型、使用通道、触发审批的策略状态,以及密钥是否由网关注入。不要记录 API 密钥、私钥、授权请求头,也不要记录那些经过处理但仍保留足够结构、可能帮助攻击者猜测的值。

如何正确记录一次已执行的 HTTP 或 SSH 调用?

记录请求方法、目标身份、已批准的请求摘要、响应状态、执行时间戳和结果引用。对于 SSH,还应记录主机身份、账户、命令或已批准命令的摘要、退出状态以及相关输出的引用。除非调查确有需要,不要把敏感请求正文和输出放进广泛可见的活动视图。

被拒绝的代理操作也应该出现在审计轨迹中吗?

被拒绝的请求仍然代表一次访问尝试,通常也能解释代理为何没有完成任务。记录请求的操作、发起请求的会话和进程、拒绝原因,以及用户是主动拒绝还是从未收到提示。如果实际上没有使用凭据,不要创建成功的凭据使用记录。

代理重启后,审计记录应如何处理?

每次代理进程运行都应获得新的会话标识,即使可执行文件、用户和项目都相同。会话审批只属于一个仍在运行的进程上下文,进程退出后就应结束。复用宽泛标识符会把有边界的授权变成含糊的权限历史。

哈希链式日志能让审计轨迹绝对防篡改吗?

不能。哈希链能在验证者拥有完整日志序列和可信上下文时发现记录被修改,但不能证明所有事件都被记录,也不能证明已批准的操作就是正确的。它提供的是记录连续性和篡改证据,这一结论范围更窄,但依然很有用。

如何调查一次可疑的代理操作?

从已执行的调用开始,沿着凭据使用标识符找到审批事件,再通过会话标识符找到进程记录。检查时间戳、审批范围,以及实际调用是否始终符合已批准的摘要。如果任何关联缺失,就应明确说明证据缺口,不要根据相邻记录拼出确定结论。

如何测试审批记录是否完整?

最快的有效测试是:让一个全新的代理进程发起一次低风险 HTTP 请求,先用点击批准一次,然后开启单次调用审批,再次发起请求并使用 Touch ID 批准。检查时间线,确认它能区分会话授权和调用授权,记录审批方式,并将两种情况都关联到实际执行的请求。

Sallyport

Sallyport 替你的 AI 智能体执行 API 调用和 SSH 命令。密钥留在你 Mac 上的本地密钥库里;每次运行由你批准,每个操作都落入一份密封的审计日志。

© 2026 Sallyport · 依据 Apache-2.0 开源 · Oleg Sotnikov