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-90 password_min_length min: 6, max: 64 接受 1(甚至负数)
auth.manifest.ts:94-100 password_max_length min: 16, max: 256 接受任意值
auth.manifest.ts:128-131 password_min_classes min: 1, max: 4 接受任意值
auth.manifest.ts:139-142 password_history_count min: 0, max: 24 接受负数
auth.manifest.ts:150-153 password_expiry_days min: 0, max: 3650 接受负数
ai.manifest.ts:189/193/197/216/288/294 temperature / 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

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions