Skip to content

analytics: 让 executeAggregate 桥携带 ExecutionContext(#3597 的纵深防御第二层)+ 两处残留无 scope 调用 #3602

Description

@os-zhuang

#3597 / #3601 拆出的后续项。#3601 已从 analytics 层堵死泄露(ObjectQLStrategy 现在把 read scope AND 进 filter),本 issue 处理第二层以及同一接缝上的两处残留。

1. 主项:让 executeAggregate 契约携带 ExecutionContext

#3597 的根因之一是 analytics→engine 的桥不传 context:

// services/service-analytics/src/plugin.ts:197-208
const rows = await engine.aggregate(objectName, {
  where: filter, groupBy, aggregations, timezone,   // ← 无 context
});

engine 侧本来是支持的 —— BaseEngineOptionsSchema(spec/src/data/data-engine.zod.ts:56-59)带 context: ExecutionContextSchema.optional(),engine.ts:3339mergeReadContext。但 .optional() 意味着类型系统从不强迫调用方传,于是 analytics 桥就没传,进而命中 plugin-security 的无 principal fall-open(security-plugin.ts:775-781),把 engine 自己的 RLS 注入整个跳过。

为什么值得做(即便 #3601 已堵住泄露):

落点(需要动 packages/spec,会碰 api-surface 门,建议独立 PR):

  • spec/src/contracts/analytics-service.ts:241-252 —— executeAggregate options 加 context?: ExecutionContext
  • StrategyContext —— 透传 ExecutionContext(或至少把 context 一路带到桥)。
  • services/service-analytics/src/plugin.ts:197-208 —— 桥转发 context。
  • analytics-service.tscallCtx 已经拿到了 context,只是没往下带。

注意:这条不能替代 #3601。engine 侧 RLS 与 analytics 的 getReadScope 是两套解析(前者走中间件,后者走 security.getReadFilter),两者都保留才是纵深防御;而且没装 plugin-security 的部署只有 analytics 这一层。

2. 残留:fetchRecordLabels 走的是同一条无 scope 的桥

// services/service-analytics/src/plugin.ts:348-364
const rows = await executeAggregate(targetObject, {
  groupBy: ['id', displayField],          // ← 每条记录一行
  aggregations: [{ field: 'id', method: 'count', alias: '_c' }],
  filter: { id: { $in: ids } },           // ← 无 read scope、无 context
});

groupBy: ['id', displayField] 等于逐记录返回真实显示名 —— 粒度上是行级而非聚合级。

当前是否越权:否。 #3601 之后聚合本身已加 scope,喂进来的 ids 已被限制在调用者可见范围内。所以这是潜在债而非活漏洞:一旦 ids 的来源变宽(新调用方、新 strategy),它会立刻变成行级泄露,且没有任何一层会拦。

建议:要么让它也吃 read scope,要么在 #1 落地后靠 context 兜底。

3. 残留:ObjectQLStrategy.generateSql 预览不含 scope

objectql-strategy.ts:117-149 生成的展示性 SQL 完全没有 WHERE 子句,所以 /analytics/sql 的预览与实际执行的语义不符(实际执行在 #3601 后是加了 scope 的)。

不构成泄露(该 SQL 不执行,只用于展示),但会误导排查者。修的话需要把 FilterCondition 编成 SQL —— read-scope-sql.tscompileScopedFilterToSql 已有能力,只是 ObjectQL 的 generateSql 没有 NativeSQL 那套 alias 体系,需要小心。优先级最低。


🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions