Skip to content

plugin-audit 每次单条写都产出幻影 diff:before 走读路径、after 走裸写结果,两侧视图不同源(并把 secret ref 写进 sys_audit_log) #6965

Description

@os-zhuang

Blocked-by: #6656 (同一个裁决决定两边的实现;#6656 的 A/A+ 方案本身就修掉 update 侧的一半)

#6656 的开发座位实测发现,属 Prime Directive #10 的范围外发现,单独开单不夹带。

事实(origin/main @ 68feaadd6 实测)

packages/plugins/plugin-audit/src/audit-writers.tswriteAudit 里,diff 的两侧来自两条不同的管线

  • before = ctx.__previous,由 captureBeforeql.findOne 产出 —— 走引擎读路径
  • after = ctx.result —— 是裸写结果driver.update 的返回)。

引擎读路径比裸写结果多做三件事,而且都只在 engine.find/findOne 里做(packages/objectql/src/engine.ts:5916:6047):

  1. maskSecretFields —— secret 字段被换成 ••••••••
  2. applyFormulaPlan —— formula 虚拟字段被求值;
  3. resolveFileReferences(ADR-0104 D3)—— 文件字段的 id 引用被解析成对象。

于是每一个属于这三类的字段,在 diff 里恒不相等,无论用户有没有改它。

实测复现

真实 ObjectQL + 计数驱动,对象含 boolean / secret / formula 字段,单条 update() 的 payload 只有 { status: 'in_progress' }

old_value = {"status":"todo","is_done":0,"api_key":"••••••••"}
new_value = {"status":"in_progress","is_done":false,"api_key":"secret:..."}

is_doneapi_key 都没有被写入,却都进了审计账本。

两个后果

  1. 噪音,且用户可见。 objectui 记录 History 页会在每次编辑后多出一条"API Key 变更"/"附件变更"。这与 COMPUTED_FIELD_TYPES 上方注释记录的"原始症状"是同一族缺陷 —— 那次是 formula 字段(before 有值、after 缺键),靠按字段 类型 排除计算字段挡住了;secret 与文件引用没有对应的排除,因为它们不是派生字段,问题出在视图而不是字段类型
  2. secret ref 落进 sys_audit_log new_value 侧是裸写结果,所以存的是 secret 的存储引用secret:...),而不是掩码。这不只发生在 update:action === 'create'new_value = {...ctx.result},同样是未掩码的;谓词 delete 的 old_valuectx.previous(同样是裸预映像),也是未掩码的。也就是说账本里今天已经普遍存在 secret ref,只有单条 update / 单条 delete 的 old_value 是掩码 —— 这块不一致正是 plugin-audit captureBefore still fetches its own pre-image — retire the second read once the engine binds ctx.previous before every before* dispatch #6656 的裁决要处理的。

sys_secret 的明文需要特权的 engine.resolveSecret 才能取到,所以这是"引用泄漏"而非"明文泄漏";但审计账本与业务表的 ACL 不同,值不值得存 ref 是个需要明确的契约。

建议方向(与 #6656 同一个裁决)

让 diff 两侧同源。最自然的是 #6656 的 A+:before 改用 ctx.previous(与 after 同为裸视图),并在 audit writer 里对 masked-read 字段两侧都掩码。这样幻影行消失、ref 不再入账本,同时把 #6656 想退役的那次冗余读一并退掉。

不建议的方向:在 diff() 里按字段类型再排除一批(secret / file)—— 那只是把症状按类型逐个补,视图不同源这个根因还在,下一个新增的读路径后处理会再来一次。

范围

packages/plugins/plugin-audit。不涉及 packages/objectql

Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions