Skip to content

security(analytics): read-scope 自动桥是插件顺序依赖的 — security 晚注册则 analytics RLS 静默全关 #3618

Description

@os-zhuang

#3597 的端到端门时发现的独立缺陷:analytics 的 read-scope 自动桥是插件注册顺序依赖的,顺序不利时整个 analytics RLS 静默关闭 —— 两条 strategy 都不加 scope。

代码与它自己的注释矛盾

packages/services/service-analytics/src/plugin.ts:277-306:

// ADR-0021 D-C — wire the read-scope provider. Prefer an explicit option;// otherwise auto-bridge to a registered `'security'` service that exposes// `getReadFilter(object, context)` (resolved at call time so plugin-init// order does not matter). ← 注释这么说
...
if(trySecurity()){但赋值卡在init时刻的探测上getReadScope=(object,context)=>trySecurity()?.getReadFilter(object,context);autoBridgedReadScope=true;}

闭包本身确实是懒的,但它只在 security 于 init 时刻已存在时才被赋值。security 晚注册 ⇒ getReadScope 永远是 undefinedAnalyticsService.callCtx 直接 return this.baseCtx(analytics-service.ts:332)⇒ 无论 NativeSQL 还是 ObjectQL 都不加 scope。

两个兄弟桥都是对的,只有这个不是:

  • executeAggregate(plugin.ts:181-182):"Probe now (warn if missing) but resolve at call time so plugin order does not matter"
  • executeRawSql(plugin.ts:229-231):"Always wire the bridge — resolution happens at call time... This way plugin-init order does not matter"

影响面

生产 CLI 路径不受影响。os serve 里 SecurityPlugin 在 packages/cli/src/commands/serve.ts:1658,AnalyticsServicePlugin 由 capability resolver 在其之后加载(静态表 serve.ts:303-305),顺序正确 ⇒ 桥接上。

受影响的是:

  1. 自建 kernel 的 embedder —— 框架是按库分发的,谁把 AnalyticsServicePlugin 排在 SecurityPlugin 前面,谁就静默拿到无 scope 的 analytics。唯一提示是一条 WARN,而且那条 WARN 的措辞还把问题说小了(见下)。
  2. 仓库自己的 bootStack harness —— packages/verify/src/harness.ts 里 AnalyticsServicePlugin 在 :159,SecurityPlugin 在 :232整个 dogfood/verify harness 的 analytics RLS 一直是关的,所以在那里写的任何 analytics RLS 断言都是空过的。这正是我写 security(analytics): ObjectQLStrategy 不消费 getReadScope — NativeSQL 回落后聚合查询无 RLS/租户谓词(#2852 修复未覆盖的另一半) #3597 端到端门时撞上的:门先红了,红的原因不是 security(analytics): ObjectQLStrategy 不消费 getReadScope — NativeSQL 回落后聚合查询无 RLS/租户谓词(#2852 修复未覆盖的另一半) #3597,而是这条。

WARN 措辞也是错的

plugin.ts:463-467 说"the raw-SQL analytics path will NOT enforce tenant/RLS scoping on joined objects"。实际上没有 provider 时,所有路径、所有对象都不加 scope,不只是 raw-SQL、不只是联表。

复现

packages/qa/dogfood/test/analytics-rls.dogfood.test.ts(随修复一起提交):owner-scoped RLS fixture + 真实 HTTP + 非管理员 member。修复前 member 的聚合返回全表 5 行,修复后返回自己的 2 行;回退 plugin.ts 改动后 4 个用例红 3 个。

修法

getReadScope 无条件接线,只用 init 时刻的探测决定日志措辞 —— 与两个兄弟桥完全一致。

待核

cloud 是独立仓库,我没检查它的插件注册顺序。如果任何已发布的 composition 用了不利顺序,这条应升级到 p1。


🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p2Medium: important, M3security

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions