Skip to content

安全/设计:静态 readonly 的 INSERT 豁免让审批/状态字段可在创建时被直接播种(比 #3003 少一步) #3043

Description

@os-zhuang

概述

#2948 / #3003 修复后,静态 readonly: true 字段在非 system 的 UPDATE 上被服务端剥离——但 INSERT 被有意豁免(与 readonlyWhen 对称:创建时可播种 readonly 列,服务于 defaultValue / 导入 / 迁移)。

审批 / 状态 / 判定类字段,这个豁免意味着:与其像 #3003 那样先建 draft 再 PATCH 提权,攻击者可以直接 POST 一条已经 approval_status: 'approved' 的新记录——比原复现还少一步,且 update 剥离完全拦不到(它只作用于 UPDATE)。

本 issue 请求 决策该豁免是否收紧(设计级,先定方向再实现)。

定位

packages/objectql/src/engine.ts insert()(L2185 起):

  • L2206-2212 applyFieldDefaults 只填 undefined 字段,不剥离 caller 提交的 readonly 列;
  • 全 insert 路径无 stripReadonlyFields 调用(该函数只在 update() 里被调,见 L2423 / L2442)。

对照 UPDATE 侧:stripReadonlyFields(rule-validator.ts)显式只在 update 调用,注释也写明「INSERT 豁免,symmetric with readonlyWhen」。

备选方向

  1. 维持现状 + 硬性文档指引(最省):承认 insert 可播种 readonly,把「审批/状态/金额/判定字段必须叠 state_machine 验证规则(强制初始状态)或 requiredPermissions FLS 兜底」写成 skill / 文档硬性约束。缺点:仍是「declared ≠ enforced」的认知负担,和 安全:静态字段 readonly: true 仅 UI 层生效、服务端不强制 → 审批/状态/金额字段可被直接 PATCH 绕过写入(FLS 缺口) #3003 同类误读风险。
  2. 收紧 insert:非 system 的 insert,readonly 列只允许取 defaultValue,caller 显式提交的值剥离。保留导入/迁移(走 isSystem)与默认值播种,堵掉「直接 POST approved」。需评估对现有依赖「创建时带 readonly 初值」的应用侧写法的兼容影响。
  3. 字段级 opt-in:给 readonly 增加一个 readonlyOnInsert(或 immutable)维度,让作者显式声明「连创建都不可由用户设」,审批/状态字段声明它。最精确,但扩了 spec 面。

判据 / 需核实

关联

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions