Skip to content

analytics 侧三个 SQL 编译器都不转义 LIKE 比较值:$contains: '_admin' 命中 xyadmin$contains: '50%' 命中 off 5012 now —— driver-sql 自己把这条旁路标为 P0 —— 实测 #5567

Description

@os-zhuang

#5557(convertFiltercontains 送成未声明的 $regex)时,为了给「比较值带元字符」的用例挑一个能站住的断言面,必须逐个核对 $contains 落到各个编译器之后到底绑出什么 pattern。在这一步发现的分叉。

不属于 #5557 的范围面(那一单裁的是「送 $regex 还是送 $contains」这个算子选择,修完之后 $contains 到达驱动,driver-sql 会正确转义;本条裁的是 analytics 自己那三个 SQL 编译器收到正确的 $contains 之后怎么拼 LIKE pattern),按 Prime Directive #10 单独记在这里,unassigned。也不在 #5526 的范围面内:#5526 裁的是 unknownstring 再回来的类型往返有损('007' 绑成 7),本条的字符串一路都保真,坏在它被原样插进了一个 pattern 位置。

位置

三处,都是 values[0] 直接做字符串拼接,没有转义、也没有 ESCAPE 子句:

对照:packages/plugins/driver-sql/src/sql-driver.tsapplyLike(约 :6276)转义的,而且绑了显式 ESCAPE '\'(注释说 SQLite 不认默认转义符),它自己在 applyContainsLike 头上写着:

% matches every row (a filter-bypass, P0). Binds an explicit ESCAPE '\'

也就是同一族 pattern 拼接,驱动那一侧已经按 P0 处理过了,analytics 这三处没有跟上。

实测

绑定层(三个编译器,name 为字符串维度):

编译器 算子 比较值 绑出的 param
read-scope-sql $contains _admin %_admin%
read-scope-sql $startsWith _admin _admin%
read-scope-sql $endsWith _admin %_admin
read-scope-sql $contains 50% %50%%
read-scope-sql $contains a_b %a_b%
native-sql-strategy $contains _admin %_admin%
native-sql-strategy $contains 50% %50%%
objectql-strategy(回显) $contains _admin %_admin%
objectql-strategy(回显) $contains 50% %50%%

行结果(真 SQLite / sql.js,4 行:x_admin / xyadmin / off 50% now / off 5012 now,经 compileScopedFilterToSql 编译后执行):

where 取到 应为
{name: {$contains: '_admin'}} ['1','2'] ['1']
{name: {$contains: '50%'}} ['3','4'] ['3']

_ 在 LIKE 里是单字符通配,所以 xyadmin_admin 命中;% 是多字符通配,所以 off 5012 now50% 命中。两条方向都是放大

为什么是 bug

  1. read-scope-sql.ts 这一处的放大是越权,不是降级的筛选。 这个编译器编的是 ADR-0021 D-C 的读作用域(tenant + RLS),整个文件的其余部分是 fail-closed 写的($in 不是数组就抛、嵌套关系就抛、未知算子就抛)—— 唯独 LIKE 族安静地把作者的字面量当成 pattern。一条 {owner_name: {$contains: '_admin'}} 形状的作用域会多放进它没打算放的行。这与 $null 的比较值不是布尔时,driver-sql 与 driver-memory 给出**完全相反**的答案(一个 IS NULL,一个 IS NOT NULL)—— 实测 #5347 / driver-memory 的实时查询路径根本不支持 $not —— mingo 抛无 code 的 MingoError,CEL !expr 降下来的 RLS scope 在该驱动上直接 500 #5324 在同一个文件上立的规矩(「在读作用域上,放大是越权,不是降级的筛选」)是同一句话。
  2. _ 撞车是真实业务形状,不是构造出来的边角。 下划线在机器名 / 前缀约定里到处都是(sys_usercrm_account_adminapp_),而 ObjectStack 自己的命名规范(Prime Directive Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3:machine names → snake_case)保证了每一个 snake_case 比较值都带至少一个 LIKE 通配符。作者按名字筛一个 snake_case 值,几乎必然踩到。% 那半更容易被看见(折扣、百分比),但 _ 那半更普遍且完全无声。
  3. declared ≠ enforced,而且驱动这半已经做对了。 同一个 $containsdriver-sql 上是转义的字面子串、在 analytics 的三个编译器上是 pattern —— 同一个 dashboard 走 NativeSQLStrategy(有 executeRawSql)和走 ObjectQLStrategy(经引擎到 driver-sql)会返回不同行集,这正是 ObjectQLStrategy.convertFilter$contains 送成未声明的 $regex(比较值不转义),三个同族算子早在 #4128 已改成规范算子 —— 实测 #5557 论证第 3 条的形状,只是换了一层。
  4. 回显那一处方向相反,但也不一致。 objectql-strategy.tsLIKE_SQL_OPS 只喂给 /analytics/sql 的回显字符串,不执行 —— 但它存在的唯一理由是复现执行(/analytics/sql 回显的 SQL 丢掉 $startsWith / $endsWith 谓词:回显比实际执行的查询更宽,无法复现结果 #5333 的原话)。若只修执行侧不修回显侧,回显又会和执行分叉,是 fix(analytics): enforce read scope on the ObjectQL aggregate path (#3597) #3601 / analytics: 让 executeAggregate 桥携带 ExecutionContext(#3597 的纵深防御第二层)+ 两处残留无 scope 调用 #3602 / analytics: ObjectQLStrategy 静默忽略 timeDimensions[].dateRange —— 恰好是 date-granularity 图表必走的那条路 #3650 那一族。三处应当一起看。

建议(不代裁决)

  • 一个共用的 escapeLikePattern(v)(转义 \%_)+ 每处 LIKE ? ESCAPE '\',与 driver-sqlapplyLike 对齐 —— 三个编译器都改,回显那一处也改(否则回显与执行再次分叉)。escapeLikePattern 放哪里需要裁:driver-sql 自己那份是私有方法,analytics 不依赖任何驱动,所以要么在 @objectstack/core 起一个共享的、要么各自实现(第二份实现正是 A filter with an operator outside VALID_AST_OPERATORS is silently dropped, not rejected — single-condition views return unfiltered results #3948 那一族缺陷的来源)。
  • ESCAPE 子句的方言差异需要确认:SQLite / Postgres / MySQL 对 LIKE … ESCAPE '\' 的支持我只在 SQLite(sql.js)上跑过。read-scope-sql.ts 的输出是拼进上层语句的,bind() 只管 ?,所以 ESCAPE 加在哪一层也要一起看。
  • $notContains 同样受影响(方向是缩小:多排除行),表里没单独列,机制同一条。

未验证的部分

关联:#5557 / PR #5566(同一轮核对里发现;它裁的是算子选择,修完之后本条才是唯一剩下的转义缺口)、#5333 / PR #5558(LIKE_SQL_OPS 这张表的来源)、#4706($regex 在 driver-sql 上编译成子串 LIKE 的语义待裁)、#5234(driver-sql 的 LIKE 族对象比较值)、#5444(driver-memory analytics 回显的 contains 少了 % —— 方向相反的同族缺陷)、#5347 / #5324(read-scope-sql 上「放大即越权」的先例)。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions