跳转到内容

审计链 100% 与 HITL 100%:两个数字怎么落到机制上

审计链完整率 100% 与 HITL 覆盖率 100% 各由一套机制支撑:已写入的审计记录能否被独立重算校验;高风险动作是否经过人工门禁,以及门禁主体是本人放行还是他人批准。

常规审计记录操作主体、时间与内容,可用于故障排查时的时间线还原。

追责还要求记录在写入之后仍可被独立重算校验。负责写日志的运行时通常也能改日志;存储中存在一条记录,只说明当前可查询,不代表该记录自写入起未被改动。

因此审计需要可重算结构:同一批进入链的字段必须得到同一摘要;任一记录被改,其后整条链的校验失败。事后改写与正常追加由此在结果上可区分。

每条审计记录携带两个摘要字段:

  • prev_hash:上一条的摘要;链首为固定创世值(约定长度的零串)
  • hash:由 prev_hash 与本条规范化正文共同计算
hash = sha256(prev_hash + "\n" + canon(body))

body 含租户、会话、事件类型、载荷、时间等进入链的字段。canon 将正文收成稳定、可复现的序列,避免同内容因序列化差异得到不同摘要。

写入按时间顺序接到链尾。校验按时间顺序重算:当前记录的 prev_hash 须等于上一条的 hash,当前 hash 须等于按现正文重算的结果。任一步失败即停止,并返回断点记录标识。

对外校验接口为 GET /audit/verify。响应形态示例:

{ "ok": true, "checked": 50, "broken_at": null }
{ "ok": false, "checked": 12, "broken_at": "aud_…" }

checked 为已重算条数;broken_at 非空时指向断点。部署验收脚本调用同一接口,要求完成链扫描。

能力边界: 哈希链约束写入完成之后对历史的改写。若写入路径已被控制,仍可先写入虚假内容再生成匹配摘要。在写入路径可信的前提下,事后改写旧记录会被校验发现。该项能力覆盖写入后的完整性,不覆盖写入瞬间的防伪。

操作按外部副作用分层。仅 mutating(会产生外部副作用的动作)强制审计,并按规则强制审批:

层例子审计审批
Read读文件、查日志保留否
Write改工作区、生成补丁保留视规则
mutating推送、发邮件、生产变更、付款必须视规则强制

读、查类操作若一律强制人工审批,可用性下降;推送、变更、付款类操作若允许漏记,事故调查会缺少关键片段。因此门禁集中在 mutating。

此处 100% 表示 mutating 路径必须持续落链,不允许间歇性缺失。审计覆盖是二元条件:路径始终落链,或在关键时刻出现空白。

复核口径: GET /audit/verify 的 checked 统计当前租户内已落链且可重算的记录是否完整,不等于业务侧发生过的全部 mutating 次数。后者还取决于动作是否进入落链路径。两套数字须分开使用。

常见假阳性:校验报告断链,但存储未被人工改动。根因是计算摘要时的正文形态,与落盘后再读出的正文形态不一致。

字段顺序。 同一内容在序列化时若键序不同,摘要不同,校验会误报篡改。进入哈希前须规范化键序:

const keys = Object.keys(obj)
.filter((k) => obj[k] !== undefined)
.sort()

空值字段。 内存表示可保留空值字段;写入 JSON 等存储后,这类字段常被省略。若按写入前形态计算摘要、按读出后形态校验,正文已变化,校验失败。正确写入顺序:先将正文收敛为与落盘一致的形态,再计算摘要,再入库。

上述问题在仅测试写入时不易暴露,完整校验才会出现。确认属于序列化缺陷导致的假阳性后,可按时间序重算并写回摘要字段;该路径不用于掩盖真实内容篡改,执行前须先导出留存。

审计链回答记录是否被改写。HITL 回答高风险动作是否经过人工门禁,以及门禁主体是谁。二者对应不同流程与事件:

流程条件审计事件是否进入审批待办
本人确认执行者在会话内允许本次操作permission.*否
他人审批另一角色批准approval.*(含 requester_id / decided_by)是

两类动作若共用待办表,HITL 覆盖率统计会把自批计入人工审批,覆盖率失真。

规则:执行者不能在审批中心批准自己发起的工单。自批不属于审批流程。追责时分别检索 permission.* 与 approval.*。

会话内放行属于既有权限能力;跨人审批与可重算审计属于额外信任边界,须单独度量与复核。


相关阅读: