实测于 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 下次加算子时,这个洞会以同样的方式重开一次。
相关
实测于
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)是:对不上的三个 ——
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-authdist/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里都有现成对应,一一直译即可:not_in$ninstarts_with$startsWithends_with$endsWith同时把链尾的静默补上:未识别的算子应当响亮拒收(抛错),而不是写不出键就算了 —— 否则 better-auth 下次加算子时,这个洞会以同样的方式重开一次。
相关
contains被译成裸$regex,用户输入当正则求值 —— 且是$regex退役唯一挡路的生产者 #5710(同一函数的contains支译成裸$regex;已提 PR,本单不在其范围内)$regexon driver-sql is not a regex — it compiles to a substring LIKE, so it both over-matches and silently matches nothing #4706 / spec:$regex按 ADR-0049 退役 +$icontains入算子词表与 FILTER_LOGIC_CASES(#4706 裁决 B 案 · 契约半边,先行) #5701 / drivers:$regex响亮拒收 +$icontains各后端实现(#4706 裁决 B 案 · 驱动半边) #5702(算子名实不符母单 / 契约半边 / driver 半边)starts_with/ends_with的大小写语义按 spec:$regex按 ADR-0049 退役 +$icontains入算子词表与 FILTER_LOGIC_CASES(#4706 裁决 B 案 · 契约半边,先行) #5701 Q2=A 是敏感的,与$startsWith/$endsWith契约一致。