从 #3878 的同类扫查里出来的。这一条不是"可能是有意设计"—— 规范里已经写死了不许这样,只是 REST 入口没走那条规范。
声明的不变式
packages/spec/src/security/sharing.zod.ts:
SharingRuleSchema = CriteriaSharingRuleSchema,其中 condition 是必填(:104)。
- 文件头
:22:"criteria is the single enforced rule form"。
SharingRuleSchema 的文档注释写得更直接:"A condition the compiler cannot lower (functions, cross-object traversal) is skipped and logged — never seeded as a permissive match-all (ADR-0049)."
也就是说:编译不出谓词的规则宁可不生效,也绝不能变成"匹配全部"。 seed 路径按这条执行。
REST 入口绕过了它
POST /api/v1/data/sharing/rules:
packages/rest/src/rest-server.ts:5786 —— 逐字段 pluck,criteria: body.criteria,没有任何 schema 校验。SharingRuleSchema 在这条路上从未被调用。
packages/plugins/plugin-sharing/src/sharing-rule-service.ts:87-92 —— defineRule 校验 name / label / object / recipientType / recipientId,唯独不校验 criteria。
- 同文件
:98-100 —— input.criteria == null ? null : JSON.stringify(...),缺失就存 criteria_json: null。
- 求值时
:264:
const filter = (rule.criteria ?? {}) as any;
const rows = await this.engine.find(rule.object_name, {
filter, fields: ['id'], limit: 5000, context: SYSTEM_CTX,
});
空 filter + SYSTEM_CTX → 该对象全部记录(上限 5000)逐条 expandRecipient 授予收件人。
recordMatches(:280)同理:{ ...(criteria ?? {}), id: recordId } 对任意记录都命中。
触发它不需要恶意,只需要打错一个字母
POST /api/v1/data/sharing/rules
{
"name": "won_deals", "label": "Won Deals", "object": "crm_opportunity",
"recipientType": "position", "recipientId": "sales_rep",
"criterias": { "stage": "won" }
}
意图是共享"已赢单"。实际是把 crm_opportunity 的全部记录共享给 sales_rep。返回 201,没有 warning,criteria 这个多出来的键被静默丢弃 —— 因为入口是 pluck 不是 parse。
同样的结果还可以由"忘了带 criteria"、"criteria 传了 null"、"前端表单没填完就提交"产生。
#3850 刚让"在 Setup 里授述的规则真正生效、关掉真正撤销"落地,这条路径现在是活的。
修法
入口接上已有的 SharingRuleSchema(或至少:criteria 缺失/为空对象时 defineRule 直接 VALIDATION_FAILED,与 name/label 等同级对待)。Zod 的默认对象模式会剥掉 criterias 这类未知键 —— 若希望拼错也能被指出来,用 .strict()。
需要一并裁定的:空 criteria 是否存在合法语义。规范目前的答案是"不存在"(condition 必填、单一 enforced 形态)。如果确实要保留 owner-based 那样的"全对象共享",它应当是一个显式的规则类型,而不是"字段没填"的副作用 —— 这正是 ADR-0049 那句话要防的东西。
门:一条断言"criteria 缺失的规则不产生任何 sys_record_share"的用例;目前 plugin-sharing 的测试没有覆盖空 criteria 这一支。
核对于 origin/main @ 93f267f。
从 #3878 的同类扫查里出来的。这一条不是"可能是有意设计"—— 规范里已经写死了不许这样,只是 REST 入口没走那条规范。
声明的不变式
packages/spec/src/security/sharing.zod.ts:SharingRuleSchema = CriteriaSharingRuleSchema,其中condition是必填(:104)。:22:"criteriais the single enforced rule form"。SharingRuleSchema的文档注释写得更直接:"Aconditionthe compiler cannot lower (functions, cross-object traversal) is skipped and logged — never seeded as a permissive match-all (ADR-0049)."也就是说:编译不出谓词的规则宁可不生效,也绝不能变成"匹配全部"。 seed 路径按这条执行。
REST 入口绕过了它
POST /api/v1/data/sharing/rules:packages/rest/src/rest-server.ts:5786—— 逐字段 pluck,criteria: body.criteria,没有任何 schema 校验。SharingRuleSchema在这条路上从未被调用。packages/plugins/plugin-sharing/src/sharing-rule-service.ts:87-92——defineRule校验name/label/object/recipientType/recipientId,唯独不校验 criteria。:98-100——input.criteria == null ? null : JSON.stringify(...),缺失就存criteria_json: null。:264:SYSTEM_CTX→ 该对象全部记录(上限 5000)逐条expandRecipient授予收件人。recordMatches(:280)同理:{ ...(criteria ?? {}), id: recordId }对任意记录都命中。触发它不需要恶意,只需要打错一个字母
意图是共享"已赢单"。实际是把
crm_opportunity的全部记录共享给sales_rep。返回 201,没有 warning,criteria这个多出来的键被静默丢弃 —— 因为入口是 pluck 不是 parse。同样的结果还可以由"忘了带 criteria"、"criteria 传了
null"、"前端表单没填完就提交"产生。#3850 刚让"在 Setup 里授述的规则真正生效、关掉真正撤销"落地,这条路径现在是活的。
修法
入口接上已有的
SharingRuleSchema(或至少:criteria缺失/为空对象时defineRule直接VALIDATION_FAILED,与name/label等同级对待)。Zod 的默认对象模式会剥掉criterias这类未知键 —— 若希望拼错也能被指出来,用.strict()。需要一并裁定的:空
criteria是否存在合法语义。规范目前的答案是"不存在"(condition必填、单一 enforced 形态)。如果确实要保留 owner-based 那样的"全对象共享",它应当是一个显式的规则类型,而不是"字段没填"的副作用 —— 这正是 ADR-0049 那句话要防的东西。门:一条断言"
criteria缺失的规则不产生任何sys_record_share"的用例;目前plugin-sharing的测试没有覆盖空 criteria 这一支。核对于
origin/main@ 93f267f。