Skip to content

rls.zod.ts 的 check @example status IN ('draft', 'pending') 编译不出来 —— 照抄 schema 自带示例的策略会 fail-closed 拒绝全部访问 #6641

Description

@baozhoutao

范围外发现,出自 #6132 的可达性测量(未在该单内修)。按 Prime Directive #10 记录,不指派。

事实(origin/main 0b63b5677 基线,实测)

packages/spec/src/security/rls.zod.ts:386check 属性 TSDoc 带这条 @example:

@example "status IN ('draft', 'pending')" - Only allow certain statuses

它编译不出来。实测(sqlPredicateToCel + isSupportedRlsExpression + compileCelToFilter,与运行期 RLSCompiler.compileExpression 同一条路径):

@example 桥接后 isSupportedRlsExpression compileCelToFilter
organization_id = current_user.organization_id organization_id == current_user.organization_id true ok {"organization_id":"o1"}
owner_id = current_user.id owner_id == current_user.id true ok {"owner_id":"u1"}
status = 'published' status == 'published' true ok {"status":"published"}
assigned_to_id IN (current_user.team_member_ids) assigned_to_id in (current_user.team_member_ids) true ok {"assigned_to_id":{"$in":["u1","u2"]}}
1 = 1 1 == 1 true ok {}
status IN ('draft', 'pending') status in ('draft', 'pending') false FAIL parse-error: Expected RPAREN, got COMMA

原因:sqlPredicateToCel 只把 IN 这个换成 in,不改写括号。CEL 的列表字面量是 ['draft', 'pending'](方括号),('draft', 'pending') 在 CEL 里不是合法表达式,于是 parse 失败。单元素的 IN (current_user.xxx) 之所以能过,是因为 (expr) 恰好是合法的括号分组 —— 一进多元素就塌。

影响

不是「示例排版错」那一类。作者照抄 schema 自带示例写出的策略,在运行期走的是:

compileExpressioncompileCelToFilter 返回 !ok → 返回 nullcompileFilterfilters.length === 0 → 返回 RLS_DENY_FILTER

该对象上的这条策略拒绝全部行(单策略时是彻底拒绝)。同时 @objectstack/lintvalidateRlsPredicateEnforceability 也会因 isSupportedRlsExpression 为 false 而报错。所以症状是「按官方示例写,然后 lint 报错 / 数据全空」,自纠成本不低 —— 作者没有理由怀疑 schema 自己的 @example

这正是 Prime Directive #10 的 declared ≠ enforced 形状:文档广告了一个运行时不提供的能力。

两个修法(留给 triage,不代裁)

  1. 改文档(最小):把该 @example 改成 CEL 方括号形式 status in ['draft', 'pending'],与 ADR-0058 D1「CEL 是 canonical」一致。using 的几条 @example 也仍是 SQL 风格,但那几条都实测可编译,属于 transitional bridge 的既有面。
  2. 改桥:让 sqlPredicateToCelIN (a, b, c) 的圆括号列表改写成 CEL 方括号列表。这是扩桥,而 sqlPredicateToCel 的 TSDoc 明写自己是 @deprecated 过渡桥、「只桥历史支持的子集」,方向上与「让 SQL 风格逐步退场」相反。

倾向 1 —— 契约优先:CEL 是 canonical,示例就该是 CEL;把一个已声明 deprecated 的桥加宽,是给要退场的方言追加新面。但这条示例是 check 子句唯一的「枚举取值」范例,改写时值得顺带确认 status in ['draft', 'pending'] 在 check 语义下确实是想表达的意思。

关联

#6132(测量出处)、ADR-0058 D1(一个 canonical 形状闸)、ADR-0056 D4(RLS 谓词形状闸门)。

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