从 #3878 的同类扫查里出来的:请求体里的一个键,能把一次"按 id 删除"改写成"按调用者可见范围全删"。
代码
packages/metadata-protocol/src/protocol.ts:3808:
async deleteManyData(request: DeleteManyDataRequest): Promise<any> {
this.assertObjectRegistered(request.object); // [#3770]
// This expects deleting by IDs.
return this.engine.delete(request.object, {
where: { id: { $in: request.ids } },
...request.options // ← 展开在 where 之后,可以把它替换掉
});
}
而 request.options 是调用者给的:packages/rest/src/rest-server.ts:6814 把整个 body 原样摊进请求对象 ——
const result = await p.deleteManyData!({
object: req.params.object,
...req.body,
...
});
所以:
POST /api/v1/data/:object/deleteMany
{"ids":["a"], "options":{"multi":true,"where":{}}}
到达 engine.delete 时 where 已经是 {}。注释里那句 "This expects deleting by IDs" 描述的是意图,不是它实际保证的东西。
影响范围
写路径仍然过引擎中间件(RLS/sharing 会往 AST 上叠谓词),所以炸的不是"删全表",而是把一次范围明确的删除放大成"这个调用者能删的一切"。对一个已经有 delete 权限的普通用户来说,这是 3 条 → 全部可见记录的差别。我没有实跑确认最终行数 —— 中间件的组合结果需要真实 RLS 策略才能定量 —— 但 body 能覆盖 where 这一点在源码上没有歧义。
修法:接一行 parse 就没了
DeleteManyDataRequestSchema 已经存在(packages/spec/src/api/protocol.zod.ts:538),其 options 是 BatchOptionsSchema,只允许 atomic / returnRecords / continueOnError / validateOnly(packages/spec/src/api/batch.zod.ts:60-65)。Zod 默认对象模式会剥掉未知键,所以在入口 .parse() 之后 options.where 根本到不了协议层。
更彻底一点:把展开顺序倒过来({ ...request.options, where: {...} }),让谓词无论如何都是最后写入的 —— 两处都做更好,一处防越权、一处防将来又有新入口绕过校验。
顺带:happy path 本身也是坏的
一个格式完全正确的 {"ids":[...]} 会走到 engine.ts:3524 抛 'Delete requires an ID or options.multi=true' —— 因为 deleteManyData 从不设置 multi。也就是说这个端点的正常用法目前是不通的,只有传了 options.multi 的(即触发上述覆盖的)请求才走得通。这两件事应该一起修。
核对于 origin/main @ 93f267f。未实跑验证行数,已在上文标注。
从 #3878 的同类扫查里出来的:请求体里的一个键,能把一次"按 id 删除"改写成"按调用者可见范围全删"。
代码
packages/metadata-protocol/src/protocol.ts:3808:而
request.options是调用者给的:packages/rest/src/rest-server.ts:6814把整个 body 原样摊进请求对象 ——所以:
到达
engine.delete时where已经是{}。注释里那句 "This expects deleting by IDs" 描述的是意图,不是它实际保证的东西。影响范围
写路径仍然过引擎中间件(RLS/sharing 会往 AST 上叠谓词),所以炸的不是"删全表",而是把一次范围明确的删除放大成"这个调用者能删的一切"。对一个已经有 delete 权限的普通用户来说,这是 3 条 → 全部可见记录的差别。我没有实跑确认最终行数 —— 中间件的组合结果需要真实 RLS 策略才能定量 —— 但 body 能覆盖
where这一点在源码上没有歧义。修法:接一行 parse 就没了
DeleteManyDataRequestSchema已经存在(packages/spec/src/api/protocol.zod.ts:538),其options是BatchOptionsSchema,只允许atomic/returnRecords/continueOnError/validateOnly(packages/spec/src/api/batch.zod.ts:60-65)。Zod 默认对象模式会剥掉未知键,所以在入口.parse()之后options.where根本到不了协议层。更彻底一点:把展开顺序倒过来(
{ ...request.options, where: {...} }),让谓词无论如何都是最后写入的 —— 两处都做更好,一处防越权、一处防将来又有新入口绕过校验。顺带:happy path 本身也是坏的
一个格式完全正确的
{"ids":[...]}会走到engine.ts:3524抛'Delete requires an ID or options.multi=true'—— 因为deleteManyData从不设置multi。也就是说这个端点的正常用法目前是不通的,只有传了options.multi的(即触发上述覆盖的)请求才走得通。这两件事应该一起修。核对于
origin/main@ 93f267f。未实跑验证行数,已在上文标注。