fix(analytics): scope the dimension-label lookup to the referenced object's RLS (#3602 residual 1)#3639
Conversation
…'s RLS (#3602) A dataset that groups by a lookup/master_detail dimension resolves the grouped FK ids to the related record's display name via a per-record read (`group by id`) dressed as an aggregate. That read carried no read scope, so it revealed related-record display names whenever the referenced object's RLS is stricter than the base object whose rows carry the id — a user could read a name the referenced object's own RLS would hide. (Same-object and looser-referenced cases were already safe: the ids come from the post-#3597 scoped aggregate. This closes the stricter-referenced case, the one residual flagged in #3602.) The label lookup now applies the REFERENCED object's own read scope, bound to the request via the same getReadScope provider the aggregate path uses, composed with `$and` (never key-merge) so the id predicate can't displace it. Fail-closed: if that object's scope can't be resolved, the dimension's labels are skipped (raw id renders) instead of fetched unscoped. No change when no read-scope provider is configured. `DimensionLabelDeps.fetchRecordLabels` gains an optional `scope` arg and `resolveDimensionLabels` an optional `resolveScope` resolver — both service-analytics-internal, no spec/contract change. Tests: dimension-labels unit cases prove the referenced-object scope is threaded and that resolution failure fails closed; a query-dataset integration case proves AnalyticsService.queryDataset binds the referenced object's scope (not the base object's) — reverting the wiring turns it red. Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Boots the real stack (HTTP → SecurityPlugin → AnalyticsServicePlugin auto-bridges → the plugin's real fetchRecordLabels closure) with a two-object owner-scoped fixture: a member owns a deal pointing at a vendor OWNED BY THE ADMIN. Grouping deals by vendor, resolving the grouped id to a NAME reads the vendor object — which the member cannot read. Asserts the member gets the vendor id RAW (the name does not leak) while the admin, who owns the vendor, still gets the resolved name — proving the fix scopes rather than blanks. Reverting the plugin's scope application reproduces the leak (member receives "Admin-Owned Vendor"). This is the only gate that exercises the plugin's real label closure; the service-analytics unit/integration tests fake fetchRecordLabels. Co-Authored-By: Claude <noreply@anthropic.com>
|
补充:加了一个真栈 e2e 门 两对象 owner-scoped fixture:member 拥有一个指向admin 名下 vendor 的 deal。按 vendor 分组、把 id 解析成名字时会读 vendor 对象,而 member 读不到它。断言 member 拿到 vendor 原始 id(名字不泄露),admin(拥有该 vendor)仍拿到解析后的名字 —— 证明是加 scope 而非一律清空。回退插件的 scope 应用后,member 直接拿到 完整 dogfood 门 62 files / 357 tests 全绿。 |
#3602 残留 1。写 #3597 端到端门时定性的那个更窄的真实泄露,单独修。
泄露
数据集按 lookup/master_detail 维度分组时,analytics 要把分组的 FK id 解析成关联记录的显示名 —— 走的是一次"按 id 分组"的 per-record 读,套在 aggregate 桥上(
plugin.ts的fetchRecordLabels)。这次读不带任何 read scope。修法(自足,不碰 spec)
取名读现在应用被引用对象自己的 read scope —— 用 aggregate 路径同一个
getReadScopeprovider 绑定到本请求,$and组合(非 key 合并,id 谓词顶不掉它)。fail-closed:该对象 scope 解析失败则跳过这一维的取名(渲染原始 id),绝不无 scope 取。无 provider 时行为不变。改动全在 service-analytics 内:
DimensionLabelDeps.fetchRecordLabels加可选scope、resolveDimensionLabels加可选resolveScope,都是包内部类型,不动 spec 契约。测试
fetchRecordLabels;resolveScope抛错时fail-closed 跳过取名(不发生无 scope 读);select 维度不受影响;无 resolver 时维持旧行为。getReadScope按对象返回不同 scope,断言取名收到的是被引用对象crm_account的 scope 而非基对象的 —— 回退analytics-service.ts接线即变红(已验)。tsc无新增错误(2 个存量在未触文件)。一并做的诚实更正(见 #3602 评论)
引擎侧独立追查确认
engine.aggregate根本没有 join/lookup 遍历能力。因此:(null)静默塌桶),值得单独跟进。本 PR 只做残留 1。残留 2(
generateSql补 WHERE,纯展示)未做,优先级最低。🤖 Generated with Claude Code