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

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions