Skip to content

settings 写入路径只执行 required / options / pattern —— 声明的 min / max / minLength / maxLength 一条都不校验(auth.password_min_length 可被写成 1) #5932

Description

@hotlong

#5712(localization options 表)的核查中发现,记录下来交 triage。不在 #5712 的完成范围内 —— #5712 处理的是 select 的 options 词表,这条是同一个 validatePatch 里另外四个已声明但完全未执行的约束。

事实

packages/spec/src/system/settings-manifest.zod.tsSpecifierSchema 声明了五类值约束,pattern / min / max / minLength / maxLength
packages/services/service-settings/src/settings-service.tsSettingsService.validatePatch(约 :899-1025)只有三个分支:requiredoptions(#5131)、patternmin / max / minLength / maxLength 在整个写入路径上没有任何读取点 —— grep 全仓,这四个键在 service-settings 里只出现在 manifest 字面量中,没有消费者。

实测(worktree 内探针,SettingsService + 内存后端):

number quota declared min:0 max:100 — 写 -500,读回 -500;写 999999,读回 999999
text code declared minLength:2 maxLength:4 — 写 "X",读回 "X";写 "ABCDEFGHIJ",读回 "ABCDEFGHIJ"
slider ratio declared min:0 max:1 — 写 42,读回 42

为什么不是休眠项:已上线的 manifest 真的在声明这些边界

不是「没人用的能力」,而是在库的声明与执行不一致,且落点包含安全项:

文件:行声明写入侧实际
packages/services/service-settings/src/manifests/auth.manifest.ts:85-90password_min_lengthmin: 6, max: 64接受 1(甚至负数)
auth.manifest.ts:94-100password_max_lengthmin: 16, max: 256接受任意值
auth.manifest.ts:128-131password_min_classesmin: 1, max: 4接受任意值
auth.manifest.ts:139-142password_history_countmin: 0, max: 24接受负数
auth.manifest.ts:150-153password_expiry_daysmin: 0, max: 3650接受负数
ai.manifest.ts:189/193/197/216/288/294temperature / max_tokens / timeout / 等 6 项min/max接受任意值

PUT /api/settings/authpassword_min_length: 1 会被接受并生效,而 UI 的数字框声明的下限是 6。攻击面不是「绕过 UI」本身,而是 Prime Directive #10 的正面形状:声明了却没执行,下游(better-auth 口令策略)按写入值工作,没有任何一层把它拉回声明的区间。

#5131 的关系(为什么算同一族)

#5131 关掉的正是同一个洞的 options 那一半:在它之前 options 表「只是前端约定」,PUT /api/settings/:ns 什么都收。min/max/minLength/maxLength 今天仍停在 #5131 之前的状态。修法应当与 #5131 同形:在 validatePatch 里补分支,发 out_of_range / too_short / too_long 一类 FieldError(packages/spec/src/api/errors.zod.ts 已有的码里挑,ADR-0114 的 constraint 字段带上 { min, max }),并沿用 #5131 的 TOUCH 闸门语义 —— 只校验本次 patch 触及的键,避免历史漂移把工作区锁死。

需要一并决定的一点:env 侧(#5204)目前只对 options 表做拒收。若补 min/max,应同样走 effectiveEnvOverride 那一个判定点,而不是在 env 路径上开第二份实现(#5204 的成因就是同一比较有两份)。

影响面(已核实)

  • 写入侧:活的,任何有 setup.write 的调用方(REST / 脚本 / AI 生成的引导代码)都能写越界值。
  • env 侧:OS_AUTH_PASSWORD_MIN_LENGTH=1 同样不被拦。
  • 未核实:各消费者(better-auth 策略、AI 服务)对越界值的实际容忍度 —— 这决定严重度是「配置面不一致」还是「可利用」,建议 triage 时定。

相关

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions