Skip to content

env 来源的 settings 值绕过 manifest 的 options 表校验 —— #5094 在写入 API 上堵住的洞,在 OS_* 覆盖这一侧原样敞开 #5204

Closed

Description

@os-zhuang

来自 #5152 的实现(PR 见下)。不是 #5152 的搭车,那边只在自己这一个键的消费端补了拒绝逻辑;这里说的是所有 select/radio/multiselect 键共有的通路。

对着 origin/main(e96ad55c0 之后合入 #5152 分支时现查)。

事实

SettingsService 有两条产出「有效值」的路径,只有一条过 options 表:

  1. 写入路径 setMany → validatePatch(settings-service.ts:731 一带)。plugin-email: SendGrid / Amazon SES 设置项同样后端无实现 —— #5087 的同形缺口 #5094/fix(service-settings,plugin-email): 邮件服务商下拉框只列真能投递的值 (#5094) #5133 把 sendgrid/ses 从 mail.provider 的 options 里摘掉之后,补上了这道 invalid_option 校验,理由原话是「PUT /api/settings/:ns 是可授权的公开面,脚本、迁移或 AI 生成的 bootstrap 代码可以往 select 里写任意字符串,存下来、读回来,再让每个消费方各自即兴发挥」。有测试钉住(settings-service.test.ts:431 起)。

  2. env 路径 get()(settings-service.ts:309-329):

    const envName = envKeyOf(namespace, key);
    const envRaw = this.env[envName];
    if (typeof envRaw === 'string') {
      const def = reg.defaults.get(key);
      const value = coerceEnvValue(envRaw, def);
      return { value, source: 'env', locked: true, ... };
    }

    coerceEnvValue 只按默认值的类型做形状转换,不看 spec.options。env 值在 cascade 里优先级最高且 locked: true,所以它就是有效值。

结果:OS_MAIL_PROVIDER=sendgrid 会被原样交给 mail 插件——正是 #5094 刚摘掉的那个值,从另一扇门走回来了。同理 OS_BRANDING_THEME_MODE=drak、OS_STORAGE_PROVIDER=s4、OS_AI_PROVIDER=openal 等等。

为什么算缺陷而不是「运维自己负责」

  • 仓库自己的判断已经写在 plugin-email: SendGrid / Amazon SES 设置项同样后端无实现 —— #5087 的同形缺口 #5094 的测试注释里:options 表是执行面,不是前端约定。那句话对 env 来源同样成立,只是当时没有覆盖到。
  • 失效是静默的:get() 不 warn,/api/settings/:ns 的读取面会把这个非法值当成 source: 'env' 的正常值展示,消费方各自即兴处理(有的 switch 落 default,有的把它当字符串拼进 URL)。
  • 这正是「declared ≠ enforced」的形状:manifest 声明了封闭值域,一条通路不执行它。

可能的形状(供定夺)

  1. get() 的 env 分支也过 options 表:不匹配就按 error 记一行(写清后果与合法值集),并忽略该 env 值回落到 cascade 的下一层。与 membershipPolicy 无法作为平台设置配置,且注册路径与回填路径读的是两个来源 #5152 在消费端采取的姿态一致,但放在唯一正确的地方,一次覆盖所有命名空间。
  2. 启动时一次性校验:registerManifest 时扫描该命名空间所有键的 env 覆盖,非法的直接拒绝启动(对照 OS_TENANCY_POSTURE 「无法识别的值拒绝启动」的先例)。姿态最硬,也最容易把既有部署拦在门外。
  3. 什么都不做,只在文档里写——但这就是 plugin-email: SendGrid / Amazon SES 设置项同样后端无实现 —— #5087 的同形缺口 #5094 明确反对的那种静默。

倾向 1 + 2 的组合:注册时报一次(够响、够早),运行时忽略非法值(够安全)。但「拒绝启动 vs 忽略」是姿态决定,不该由实现者自己定。

已知的一处已缓解

auth.membership_policy(#5152)在 bindAuthSettings() 里自己挡了一道:非法值记 error 并保持当前策略,不静默落回 auto。那是单点补丁,不是这个通路的修复——本 issue 修好后,那段消费端逻辑可以留着当第二道防线,也可以收敛。

Found-during: #5152

Activity

  1. added
    bugSomething isn't working
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    on Aug 5, 2026
  2. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    ContributorAuthor

    分诊:needs-user-decision + domain:services。

    • 落点:packages/services/service-settings/src/settings-service.ts 的 get() env 分支(coerceEnvValue 只按类型转换、不查 spec.options);services/* 按域表归 domain:services。过时前提检查:origin/main 上该文件自立单以来无相关合并,缺口仍在。
    • 为什么进决策箱而不是队列:修法 1(运行时忽略非法 env 值 + error 日志)与修法 2(registerManifest 时非法 env 覆盖直接拒绝启动)之间是运维姿态拍板 —— 方案 2 会把携带非法 OS_* 值的既有部署拦在启动之外,发单人明说「拒绝启动 vs 忽略」不该由实现者自己定。
    • 待裁决问题:采 1+2 组合(发单人倾向,对照 OS_TENANCY_POSTURE 拒启动先例)/仅 1(最温和)/仅 2?裁决落评论后即可转 pm:queue 派发。

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


    Generated by Claude Code

  3. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    维护者裁决(2026-08-06,经 services 车道 PM 转达):批准「方案 1 为主 + 注册时响亮 error(不拒绝启动)」

    • get() 的 env 分支过 options 表:非法值记 error(写清后果与合法值集)并忽略该 env 值、回落 cascade 下一层;
    • registerManifest 时扫描该命名空间的 env 覆盖,非法者记一次响亮 error —— 不拒绝启动(避免升级陷阱:昨天合法的值不应把老部署整个拦在门外,plugin-email: SendGrid / Amazon SES 设置项同样后端无实现 —— #5087 的同形缺口 #5094 的 sendgrid 摘除正是这种场景);
    • 安全关键命名空间(tenancy 类)如需硬姿态,后续按键位加旗标另议,不做默认。

    摘 needs-user-decision 回 pm:queue,本车道随后认领直派。


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 6, 2026
  5. self-assigned this
    on Aug 6, 2026
  6. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    认领(services 车道 PM 派发,session_01BWS4heBoAitLmzCLhcYdbK)


    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

bugSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpm: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