Skip to content

审批请求的可见性是「按租户」而非「按参与者」——getRequest 用 SYSTEM_CTX 读、只按 organization_id 收窄 #3590

Description

@os-zhuang

在为 #3580 做浏览器验证时顺带发现的。不是 #3580 引入的,是既有行为。


✅ 已由 #3599 修复(已合并)

答案是第 3 种:租户级可见不是有意的设计,而是「绕过 RLS 之后只实现了一半」。

buildRequestWhere 的注释自己点明了这一点:

we deliberately query with SYSTEM_CTX to bypass RLS on the engine (the approver-visibility rule spans three identity forms, which RLS can't model cleanly)

——注释点名了审批人可见性规则,但代码只写了租户那一半,参与者那一半从未落地。

修复后的规则

一条请求对它的参与者可见:提交人 / 当前审批人(走归一化审批人索引,覆盖写路径记录的每种身份形态)/ 已经在其上行动过的人(槽位已流转的历史审批人、评论者)。具备 override 权限的管理员保留不受限视图;无 token 的上下文什么都看不到。

approverId 明确定性为过滤器,不是授权

为什么用具体 user id 就够

这点值得记下来,因为这里授权不足会静默隐藏别人必须处理的审批——比泄漏更难发现:

  • position / team / manager / field 类审批人在开单时就已解析成具体 user id
  • type:value 字面量只是解析到「无人」时的兜底,那种槽位任何人都无法行动(can_act 就是对已解析 id 的朴素成员测试)

所以这条规则不可能把请求从真正能处理它的人眼前藏起来。

一个实现上的坑(值得记录)

每个写操作都通过 getRequest 回读它刚改过的请求作为返回值。第一版实现把这条路径也判了权,结果对不带 userId 的上下文(flow 驱动的 resume、服务间调用)会把一次成功的写入变成 null。4 个既有测试正好抓到了它 —— 回读现在走不判权的 readBackRequest,因为操作在写之前已经用自己的规则授过权

浏览器验证(app-showcase)

场景 修复前 修复后
Ada(非管理员,只是 expense 的审批人)不带过滤器列请求 3 条(整个租户) 1 条(只有她的)
Dev Admin 列请求 3 条 3 条(控制台视图保留)
Ada 下载自己经手请求的附件 200 200,真实 PDF(#3580 正常复合)
Ada 读她无关请求的时间线 200,2 行

第 2 个问题的答案

「直接读被 PERMISSION_DENIED、经服务读却放行」这个不一致依然存在,但现在方向是对的:服务侧的规则比对象权限更精确(它能表达 RLS 表达不了的审批人身份),而不是更松。这是有意的分工,已在 content/docs/automation/approvals.mdx 中写明谁能看到什么。

相关:#3580(文件读权限委托)、#3505(决策附件的名称/下载)。

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