第14章 审计记录决定事故能否被解释
日志数量多不等于可审计
一次退款发生后,团队需要回答:谁代表谁,哪个租户,哪个业务对象,什么动作,使用了哪次审批,在哪条策略下允许,执行结果如何。几十条包含提示词和 HTTP 请求体的日志,可能仍然无法回答这些问题,还会泄漏客户内容与访问令牌。
本书审计采用字段白名单:请求 ID、subject、actor、tenant、对象引用、动作、allow/deny、原因以及审批引用摘要。它不保存原始 Authorization、父令牌、子令牌、开发服务密钥或 note。审批引用只保留截短 SHA-256,便于关联而不把可使用的 approval ID 复制到日志。生产环境应根据碰撞概率和检索需求选择足够长度,并对敏感对象引用进行分级处理。
对象 ID 本身也可能敏感。订单编号、客户标识和工单路径在某些环境会暴露业务关系。因此“只记录元数据”不自动意味着可公开。审计应有访问控制、保留期限、导出审批与查询记录;书中测试输出不包含完整业务数据库,也不把审计当作公开调试页面。
允许与拒绝都应留下可解释证据
只记录成功,无法看见持续的越权尝试;只记录失败,又无法重建真实副作用。审计需要稳定原因类别,如 credential、tenant、scope、approval_binding、state_conflict 和 executed。原因不能完全依赖异常文本,因为异常文本会随着库升级变化,也可能包含机密。
失败案例是安全团队看到“403”很多,却不知道是服务故障、用户被撤权还是跨租户攻击。另一个案例是所有请求都记录为 Alice,actor 被代理抹掉,最终无法定位有缺陷的 Agent 版本。身份与决策字段要在授权代码旁边明确生成,不能从后来拼接的字符串中猜测。
本书业务审计与成功修改在同一内存临界区追加,说明它们应具备事务关系。生产可在数据库事务中写 audit/outbox,再异步输送到独立存储。只在业务成功后写远程日志,若网络失败可能丢失记录;先写“成功”再提交业务,又可能产生并未发生的成功事件。应区分意图事件、决策事件和提交事件,并为它们建立稳定关联。
防篡改与可追责的边界
本书没有实现不可变存储、签名审计链或外部见证。拥有进程内存或管理员密钥的人可以修改实验状态,重启会清空。它证明审计字段与脱敏规则,不证明法律级不可抵赖。生产系统若需要更强证据,应采用独立权限域、追加式存储、保留锁定、时间同步和受控导出。
审计也不能替代业务对账。退款是否真正到达支付渠道,需要渠道流水和账务记录;Agent 说“已退款”或本地 API 返回成功都不是最终财务证据。责任链应连接业务请求、审批、内部提交和外部流水,避免事故时只剩聊天截图。
实验与练习
运行 go test -race -run TestTenantScopeAndUntrustedToolInput -v ./lab,测试通过受控审计接口读取记录,并断言原令牌、业务令牌、恶意 note 中的敏感标记和内网地址都不存在。这个检查直接搜索实际响应,而不是只看日志结构体定义。
练习:设计一次“用户否认批准退款”的调查清单。你需要审批展示内容、审批人认证证据、执行参数摘要、主体与 actor、授权版本、提交时间和外部流水。再标注哪些字段普通开发者可查、哪些需要专门审批、何时删除。若所有信息都只存在模型对话里,审计链还不完整。
审计接口本身必须授权
审计通常能跨用户、跨对象聚合行为,因此查询接口可能比单笔业务接口更敏感。实验 /audit 只接受开发管理员密钥;生产应按角色、租户和调查范围控制查询,记录谁读取和导出了哪些记录。不能因为日志不含 token,就让所有开发者访问全部租户的行动轨迹。
查询结果还可能包含攻击者可控对象标识。展示时需要正确转义,导出 CSV 时注意公式解释风险,搜索系统应限制字段长度和索引基数。审计脱敏只是一部分,数据从采集到检索再到导出的每一步都可能引入新的解释器。
保留期限应与调查和法律需求一致,并考虑主体删除请求。可以对稳定身份使用受控映射,让必要历史责任链保留,同时删除不必要的个人展示信息。具体政策取决于组织和地区,本书不提供法律结论;工程上要保证删除、保留和导出都可执行,而不是让日志永远增长。
未解析的对象输入也可能夹带机密,所以实验不会把所有请求 ID 原样当成可信业务引用。只有在服务端对象表中确认的合法对象标识才直接进入审计;不存在或结构非法的对象引用改存摘要,未知动作改为固定类别。测试还把业务令牌放进非法对象字段,再检查审计响应没有原值。这能减少恶意或错误输入被白名单字段间接带入日志的风险,但真实对象标识的敏感级别仍需业务自行评估。