Skip to content

plugin-auth: convertWhere() 对 not_in / starts_with / ends_with 没有分支,谓词被整条丢弃 —— 认证路径上的过滤放大 #5813

Description

@baozhoutao

实测于 origin/main@1624f4a,在实施 #5710(同一个函数的 contains 支)时发现。与 #5710 不同类:#5710 是「译错了」($regex 把字面值当模式),本单是「根本没译」—— 谓词消失,过滤放大。

测到的东西

packages/plugins/plugin-auth/src/objectql-adapter.ts 的 convertWhere() 是一串 if / else if,只覆盖 eq / ne / in / gt / gte / lt / lte / contains。

better-auth 的算子词表(@better-auth/core/src/db/adapter/index.ts:308-320)是:

eq, ne, lt, lte, gt, gte, in, not_in, contains, starts_with, ends_with

对不上的三个 —— not_in、starts_with、ends_with —— 落在链尾之外,filter 里不写任何键。没有 else 兜底,没有告警。一个只带这类条件的 where 因此编成 {}:

  • findMany / count 变成全表(受 limit 截断);
  • update / delete / consumeOne 走的是「先 findOne(filter) 再按 id 写」,{} 让 findOne 返回任意一行(通常是第一行),于是写到了错误的记录上。

活体调用方

GET /api/v1/auth/admin/list-users(已挂载,auth-route-ledger.ts:161)直接把查询参数推进 where(better-auth dist/plugins/admin/routes.mjs:358-368):

  • searchOperator 的枚举是 contains | starts_with | ends_with —— 后两个命中本缺陷;
  • filterOperator 的枚举就是整张 whereOperators 表 —— 含 not_in。

所以 ?searchValue=abc&searchOperator=starts_with 今天返回的是「全部用户」而不是「以 abc 开头的用户」,?filterField=email&filterOperator=not_in&filterValue=... 同理不排除任何人。管理台的用户检索是它的主要消费者。

为什么这是缺陷而不是「未实现的可选能力」

丢一个谓词不是把结果变窄,是变宽;在读路径上等于放大可见集合(#3948 反复论证过的形状,driver-memory 的匹配器 default: 臂和 objectql 的 having 都为此改成了拒收)。这里的放大还发生在身份表上。

修法(建议)

三个算子在 FILTER_OPERATORS 里都有现成对应,一一直译即可:

better-auth ObjectQL
not_in $nin
starts_with $startsWith
ends_with $endsWith

同时把链尾的静默补上:未识别的算子应当响亮拒收(抛错),而不是写不出键就算了 —— 否则 better-auth 下次加算子时,这个洞会以同样的方式重开一次。

相关

Activity

  1. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    Contributor

    分诊:pm:queue + domain:identity。

    落点锚定:packages/plugins/plugin-auth/src/objectql-adapter.ts 的 convertWhere() ⇒ identity 行。⚠️ 与同族的 #5814 落在同一个函数,但两单不同类,分诊结论也不同(见下)。

    过时前提检查(origin/main@846ed1f,实读 git show origin/main:<path>):convertWhere 的 if / else if 链命中 eq / ne / in / gt / gte / lt / lte / contains 八支,not_in / starts_with / ends_with 零命中 ✅;零命中已用邻近词反查证伪「扫描器坏了」(同文件同 grep 打出了那八支)。链尾无 else 兜底 ✅。前提逐条成立。

    为什么判 pm:queue 且值得优先:丢谓词是把结果变宽不是变窄,且发生在身份表上;update / delete / consumeOne 的「先 findOne(filter) 再按 id 写」在 {} 下会写到任意一行。这是 restore-invariant 形状,与 driver-memory 匹配器 default: 臂、objectql having 改为拒收是同一条纪律(#3948 一族),不是「未实现的可选能力」。活体调用方 GET /api/v1/auth/admin/list-users 已挂载(auth-route-ledger.ts:161),searchOperator 的枚举里 starts_with / ends_with 两个值直接命中。

    与 #5814 的分流(同函数、不同判级,派发前请读这一段):

    缺什么 本轮判级 理由
    #5813(本单) 三个算子没有分支 pm:queue 词表里有现成对应($nin / $startsWith / $endsWith),直译即可,无契约问题
    #5814 Where.mode: 'insensitive' 从不读取 needs-user-decision 「不敏感的相等」这个算子在词表里不存在,需扩词表 / 降级 / 规范化三选一

    ⛔ 同文件串行:两单落在同一个函数,不得同批。#5814 目前在决策箱,天然不会被选批,所以实际约束是:本单先行;若 #5814 日后裁决落地,其实施应在本单合入之后重新定价(本单会顺带把链尾改成响亮拒收,那正是 #5814 落地时 mode 该走的接缝)。派发本单时建议带一条必答项:你的改动是否让 #5814 变简单 / 变难 / 变得不必要?

    查重:三仓 open issue 与 PR 各搜一遍(convertWhere / not_in / starts_with / list-users)。#5710(同函数 contains 支译成裸 $regex)已 closed(PR 已合),故不构成在飞同文件冲突,但其改动就在相邻几行 —— 实施前请对 origin/main 重读该函数,不要照着 issue 正文里的旧行号改。无影子单。

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


    Generated by Claude Code

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

    @baozhoutao
    ContributorAuthor

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

    前提已于 origin/main(846ed1f 后)复核:convertWhere 仍只有八支(eq/ne/in/gt/gte/lt/lte/contains),not_in/starts_with/ends_with 零命中(邻近词反查通过),链尾无兜底。#5710 的 PR #5812 已合并 —— 本单基于合并后代码实施,同函数相邻行已变,不按 issue 旧行号。#5814 在决策箱天然串行,按分诊要求带必答项派发。


    Generated by Claude Code

  4. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    ContributorAuthor

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

    CI 全绿(23 checks),转 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