观察类发现(#5492 复现过程中量到,范围外,不在该单修)。
事实
packages/plugins/plugin-security/src/vama-write-path-convergence.test.ts 的测试替身把一次 by-id delete 构造成:
const opCtx: any = {
object: 'crm_contract',
operation: 'delete',
options: { id: recordId }, // ← 引擎不接受这个形状
};
而真实引擎的 by-id delete 判定(metadata-core/src/engine-delete-dispatch.ts 的 scalarDeleteId / resolveEngineDeleteDispatch)只认 options.where.id:options.id 既不是 by-id 也不是 multi,落到 reject 分支。
后果是这一半的覆盖被悄悄掏空:SecurityPlugin 中间件的 extractSingleId(security-plugin.ts:2980)读的是 data.id 与 options.where.id,对 options.id 返回 null,于是 #1994 前像行级写门整段跳过;该用例里 write('delete', …) 实际只经过了 plugin-sharing 中间件的 canDelete 门。用例名声称的是「explain 与它所解释的那次写,答案一致」,而写的那一侧在 delete 上只跑了两个门中的一个。
实测(#5492 的复现装置里把同一形状换成 options: { where: { id } } 后):同一个 manager 的 delete 从 ok: true 变成 403 (row-level security) —— 前像门这才被执行到。UPDATE 一侧不受影响(data.id 是引擎与安全侧一致认可的 by-id 形状)。
为什么按 finding 归档而不是缺陷
生产路径没有这个形状(引擎会拒绝),所以今天没有用户会碰到;这是测试装置的保真度问题,不是运行时漏洞。但它属于「绿得不是因为逻辑对,而是因为什么都没执行」的那一类,值得在下一次动这个文件时一起修正。
建议修法
测试替身的 delete 分支改用引擎的规范形状 options: { where: { id } };更稳的做法是让替身像其他 fake engine 那样用 assertEngineDeleteDispatch(options) 把自己钉在生产者的拒绝面上(scripts/check-engine-double-contract.mjs 的既有纪律)。
观察类发现(#5492 复现过程中量到,范围外,不在该单修)。
事实
packages/plugins/plugin-security/src/vama-write-path-convergence.test.ts的测试替身把一次 by-id delete 构造成:而真实引擎的 by-id delete 判定(
metadata-core/src/engine-delete-dispatch.ts的scalarDeleteId/resolveEngineDeleteDispatch)只认options.where.id:options.id既不是by-id也不是multi,落到reject分支。后果是这一半的覆盖被悄悄掏空:
SecurityPlugin中间件的extractSingleId(security-plugin.ts:2980)读的是data.id与options.where.id,对options.id返回null,于是 #1994 前像行级写门整段跳过;该用例里write('delete', …)实际只经过了 plugin-sharing 中间件的canDelete门。用例名声称的是「explain 与它所解释的那次写,答案一致」,而写的那一侧在 delete 上只跑了两个门中的一个。实测(#5492 的复现装置里把同一形状换成
options: { where: { id } }后):同一个 manager 的 delete 从ok: true变成 403(row-level security)—— 前像门这才被执行到。UPDATE 一侧不受影响(data.id是引擎与安全侧一致认可的 by-id 形状)。为什么按
finding归档而不是缺陷生产路径没有这个形状(引擎会拒绝),所以今天没有用户会碰到;这是测试装置的保真度问题,不是运行时漏洞。但它属于「绿得不是因为逻辑对,而是因为什么都没执行」的那一类,值得在下一次动这个文件时一起修正。
建议修法
测试替身的 delete 分支改用引擎的规范形状
options: { where: { id } };更稳的做法是让替身像其他 fake engine 那样用assertEngineDeleteDispatch(options)把自己钉在生产者的拒绝面上(scripts/check-engine-double-contract.mjs的既有纪律)。