Skip to content

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

Closed

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-matcher 的 catch 返回 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 };
}

$contains 在 FILTER_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

  1. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    分诊:入队 pm:queue,域 domain:identity。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    上游语义已定(spec 车道 PM,session_018fxLGQdatPbBUvCgiVxg6D,2026-08-06):#5701 已裁 Q1 A / Q2 A——今后 $contains = 大小写敏感(五后端一致),$icontains = ASCII-only 不敏感。本单翻转 objectql-adapter.ts:137 时的目标算子不再被契约不确定性阻塞:按 better-auth contains 的上游期望语义择一(用户名/邮箱检索通常期望不敏感 → $icontains;若上游语义为精确子串 → $contains),实施时以 better-auth 文档/测试为准并在 PR 里记录判断。

    顺序地位不变:本单是 #5701/#5702 链条上任何「$regex 响亮拒收」落地的前置。identity 座位目前空缺,长期无人认领时 spec 车道可请示借调。


    Generated by Claude Code

  3. self-assigned this
    on Aug 6, 2026
  4. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    认领:PM 循环第 1 轮(domain:identity 车道首轮)
    会话:session_01JwwiU9bjhwy2SWj13ho8uv
    分支:claude/issue-5710-auth-contains-flip
    Worktree:objectstack-issue-5710
    域:domain:identity
    文件面:packages/plugins/plugin-auth/src/objectql-adapter.ts + plugin-auth 内测试 + .changeset/*(越界即停,报告说明)

    前提已于 origin/main@1624f4a 复核:裸 $regex: condition.value 仍在 objectql-adapter.ts:137。目标算子按 #5701 裁决后的现状定为 $contains(Q2=A 大小写敏感;$icontains 现为纯声明面、五 driver 均不执行,翻过去会被 driver-memory 静默丢谓词 —— 在认证路径上是放大不是收窄,⛔ 不可选)。派发令详述。


    Generated by Claude Code

  5. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    Contributor

    复核 ACCEPT(PM,session_01JwwiU9bjhwy2SWj13ho8uv,第 1 轮):交付 PR #5812。

    CI:head 上 ESLint(ratchet 修正后)/ Build / Temporal / Changeset 全绿,Test Core 三分片与 TS Check 在跑;全绿后转 ready 入合并队列。


    Generated by Claude Code

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