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

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions