Skip to content

plugin-auth: better-auth 的 contains 被译成裸 $regex,用户输入当正则求值 —— 且是 $regex 退役唯一挡路的生产者 #5710

Description

@os-zhuang

实测于 origin/main@eb26126d5,在核验 #5701(#4706 裁决 B 案契约半边)的前置前提时发现。独立缺陷,不依赖任何裁决即可修;同时它是 #5702$regex 响亮拒收」落地前必须先排掉的那块。

测到的东西

packages/plugins/plugin-auth/src/objectql-adapter.ts:137,convertWhere() 的最后一支:

} else if (condition.operator === 'contains') {
  filter[fieldName] = { $regex: condition.value };
}

condition.value 是 better-auth 传下来的未转义比较值,直接坐进 $regex 的模式位。它随后的命运按 driver 分叉:

driver 求值方式 结果
driver-memory new RegExp(target, condition.$options || '')(memory-matcher.ts:209-213) 当真正则跑 —— 元字符全部生效
driver-sql / driver-sqlite-wasm 编成子串 LIKE '%v%'(sql-driver.ts:6511,走 applyLike,%/_/\ 有转义) 子串匹配,元字符是字面量
driver-turso 同上(local 继承 SqlDriver;remote 见 remote-transport.ts:1258) 子串匹配

也就是说 better-auth 的一个 contains 查询:

  • 在内存 driver 上,a.b 会命中 axb(. 是通配),^x 会锚定,café 之类无元字符的值才碰巧正确;
  • 在 SQL 上,同样的值是纯子串;
  • 模式非法时(如值里带单个 (),memory-matchercatch 返回 false —— 静默不匹配,不报错。

这正是 #4706 正文描述的缺陷形态,只是发生在认证路径上,而且比较值来自用户输入。app 级测试若跑在内存替身上、生产跑 SQL,两边结果不同(hotcrm#630 的形状)。

为什么它同时挡着 #5702

driver-sql / driver-turso / driver-memory / objectql(having-filter.ts:37)四处的 $regex 支持都是为这一个生产者存在的,代码里已写明。driver-memory/src/filter-refusal.ts:154-158 原话:

$regex —— not in FILTER_OPERATORS, but really produced: plugin-auth's ObjectQL adapter emits { field: { $regex: value } } for a contains search. ... Refusing it here would break a live producer.

所以 #5702 的「校验期响亮拒收 $regex」在这一行翻转之前落地,会直接打断 better-auth 的认证查询。顺序:先本单,后拒收。

建议的修法(contract-first)

把这一支改成词表里真有的算子:

} else if (condition.operator === 'contains') {
  filter[fieldName] = { $contains: condition.value };
}

$containsFILTER_OPERATORS(spec/src/data/filter.zod.ts:957-968)里,五后端都实现,且 SQL 侧的 applyLike 已带 %/_/\ 转义与显式 ESCAPE —— 元字符回归字面量,正是 better-auth 的 contains 本意。

一处需要在实施时确认的语义差:$contains大小写行为今天各后端不一(memory/formula 敏感、SQLite/turso ASCII 不敏感、mongo 不敏感、PG 敏感;spec 在 filter.zod.ts:139 明写「Case sensitivity should be handled at backend level」)。相对现状而言这个差不是本单引入的 —— 裸 $regex 在 memory 上本来也是大小写敏感的,所以翻转对该 driver 是等价的;但若 #5701 的 Q2(是否同批钉死 $contains 大小写语义)有裁决,本单应与之对齐。

验证建议

翻转后应有一条 pin:一个带元字符的比较值(如 a.b)经 better-auth contains 路径,在内存 driver 上命中 axb —— 反向验证方向是「先红后绿」(翻转前该断言红,因为裸 $regex 会命中)。

相关:#4706(算子名实不符母单)、#5701(契约半边)、#5702(驱动半边拒收)。

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions