从 #3583 的引用完整性工作中分出来(方案文档 §5 D4)。
现状
HotCRM 审计里有一类问题:hook 往对象上写它没有的字段(contact_email / contact_phone)。这一类目前静态查不出来,而且短期内也查不出来 。
原因是写入面没有结构:
HookSchema.body(packages/spec/src/data/hook-body.zod.ts)是 ExpressionBodySchema | ScriptBodySchema 的联合,两者的 source 都是不透明 z.string()。
HookSchema.handler(已废弃)是函数名或函数本身。
任何地方都没有「这个 hook 写哪些字段」的声明。
对比之下 flow 侧能查,是因为节点结构化地 声明了写入:validate-readonly-flow-writes(FLOW_UPDATE_READONLY_FIELD)和 validate-flow-template-paths(FLOW_TEMPLATE_UNKNOWN_FIELD)读的是 update_record 节点的 fields 配置,不是 JS 源码。
读取侧已经覆盖了:validate-expressions 会按对象字段检查 hook.condition,#3657 起也支持数组形态的 hook.object。缺的只有写入侧。
三个选项
接受缺口并写进文档。 这是 lint: no reference-integrity or option-key validation for app metadata #3583 九类问题里唯一没有静态答案的一类;明说比让作者以为有覆盖要好。
给 HookSchema 加结构化 writes: string[] 声明,并由运行时强制。 契约优先(Prime Directive Add comprehensive test suite for Zod schema validation #12 ):作者声明这个 hook 会写哪些字段,lint 校验字段存在,运行时拒绝声明之外的写入。这样既可静态检查,又能给出真正的安全属性。代价是新的 spec 面 + 运行时改动 + 存量迁移。
注册期开发态诊断 (ADR-0078 §4)。与授权面无关,但成本最低。ADR-0078 已把它列为 deferred / evidence-gated。
建议
短期取 1(记录已接受的缺口),把 2 作为独立提案讨论 —— 它是真正的能力增强,不该被塞进 lint 任务里顺手做掉。
关联
从 #3583 的引用完整性工作中分出来(方案文档 §5 D4)。
现状
HotCRM 审计里有一类问题:hook 往对象上写它没有的字段(
contact_email/contact_phone)。这一类目前静态查不出来,而且短期内也查不出来。原因是写入面没有结构:
HookSchema.body(packages/spec/src/data/hook-body.zod.ts)是ExpressionBodySchema | ScriptBodySchema的联合,两者的source都是不透明z.string()。HookSchema.handler(已废弃)是函数名或函数本身。对比之下 flow 侧能查,是因为节点结构化地声明了写入:
validate-readonly-flow-writes(FLOW_UPDATE_READONLY_FIELD)和validate-flow-template-paths(FLOW_TEMPLATE_UNKNOWN_FIELD)读的是update_record节点的fields配置,不是 JS 源码。读取侧已经覆盖了:
validate-expressions会按对象字段检查hook.condition,#3657 起也支持数组形态的hook.object。缺的只有写入侧。三个选项
HookSchema加结构化writes: string[]声明,并由运行时强制。 契约优先(Prime Directive Add comprehensive test suite for Zod schema validation #12):作者声明这个 hook 会写哪些字段,lint 校验字段存在,运行时拒绝声明之外的写入。这样既可静态检查,又能给出真正的安全属性。代价是新的 spec 面 + 运行时改动 + 存量迁移。建议
短期取 1(记录已接受的缺口),把 2 作为独立提案讨论 —— 它是真正的能力增强,不该被塞进 lint 任务里顺手做掉。
关联