Repository navigation
ADR-0056 D4 的 RLS authoring gate 从未接线:isSupportedRlsExpression 没有任何非测试消费者 #4983
Description
Activity
xuyushun441-sys commented
on Aug 3, 2026 CollaboratorAuthorMore actions认领(PM 分派)
- session:
session_018iARDqtrhQgz6fVHDeDkbQ - 分支:
claude/issue-4983-rls-authoring-gate - 车道:
domain:devx(落点packages/formula+packages/lint;前置 feat(lint): 判红「声明了但运行期只会跳过」的 sharing-rule condition (#4698) #4985 已合入)
按本单正文的顺序执行:第一步把
sqlPredicateToCel从plugin-security提升进@objectstack/formula(plugin-security 改为消费,保持单一定义);第二步在 lint 侧按 #4985 的同款模式(调用消费者本人的判定isSupportedRlsExpression,经 formula)为rowLevelSecurity[].using/.check立 authoring gate。这同时终结正文里那个讽刺:该函数从此有了真正的消费者。
Generated by Claude Code
- session:
xuyushun441-sys commented
on Aug 3, 2026 CollaboratorAuthorMore actions审核通过 — PR #5008 已取消草稿并挂上 auto-merge(SQUASH);两步都落地,那个讽刺正式终结
isSupportedRlsExpression从"零消费者的 authoring-gate 用函数"变成了真 gate 的判据,且判据是消费者本人的那次调用 ——RLSCompiler.compileFilter决定丢弃与否用的同一个函数,两边按构造同一个布尔。plugin-security 侧还换上了更强的消费侧断言:corpus 测试断言"编译器真正丢弃的集合 == 该函数拒绝的集合" —— 这句话正是新 gate 的成立前提,现在被钉住了。报错文案写的是实读出来的后果,读/写两路分开:
using被丢弃且是唯一策略 →RLS_DENY_FILTER匹配零行;check被丢弃 → post-image 变哨兵 →PermissionDeniedError。fail-closed 不是漏洞 —— 是一条"看起来在授权、实际一律拒绝"的策略,而三个 authoring 命令今天全绿。这正是 gate 的存在理由。两处对模板的必要偏离,判断都对:
- parse-error 没让给
validateStackExpressions(feat(lint): 判红「声明了但运行期只会跳过」的 sharing-rule condition (#4698) #4985 是让的)—— 因为实测那条规则根本不走 rowLevelSecurity,让给它等于让给没有人;而且它判不对:owner_id = current_user.id同时是 CEL 语法错和一条能正常工作的 RLS 谓词(桥接先跑)。收成自己的第二个 id,修法确实不同。 - gate 范围比议题正文窄:议题建议覆盖
objects[].rowLevelSecurity[],但 ObjectSchema 根本没有这个键 —— 照抄会新增一条任何 spec 合法 stack 都到不了的惰性分支,即 validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984 的缺陷本身。拒绝复制刚修过的错误形状,并用测试钉住该面判绿。
依赖方向用测试钉而非注释(import 说明符集合 + package.json dependencies 断言)—— 理由说得准:只在构建期出错的依赖方向不会产生任何失败断言。搬迁逐字、单测随迁、不留 re-export(单一地址,不是单一定义加转发口)。
与 #4991 的注册处双写没有发生(它没碰 authoring-rules.ts),merge main 后 lint 1052 测试全绿。全仓 9 条真实 RLS 谓词全部判绿;桥接最锋利的边(引号内
=/IN)单独钉住。衍生 #5009 已入队:
validateOrgAxisRedLines还剩三条 spec 合法 stack 到不了的别名分支(含整段objects[].rowLevelSecurity死代码)—— #4984 只修了 sharing rule 那一半,危险在误导下一个作者相信那是真实授权面。
Generated by Claude Code
- parse-error 没让给
- added a commit that references this issue
on Aug 4, 2026 - added a commit that references this issue
on Aug 23, 2026
发现于 #4698 的实现过程(
packages/lint侧已落地 sharing-rule 那条,见 PR)。这是同一缺陷类的姊妹面,但修不进那个 PR,原因写在下面。事实
isSupportedRlsExpression(packages/plugins/plugin-security/src/rls-compiler.ts) 的 TSDoc 明确说明它存在的唯一理由:仓内消费者盘点(
grep -rn isSupportedRlsExpression --include=*.ts packages/):packages/plugins/plugin-security/src/rls-compiler.ts— 定义处packages/plugins/plugin-security/src/security-plugin.test.ts— 它自己的单测packages/qa/dogfood/test/authz-conformance.matrix.ts— 一致性矩阵里的一行描述文字即:那个 authoring gate 从来没有被任何 authoring 命令调用过。函数本身就是一个 declared-but-never-read 的实例 —— 而且它恰好是为了修 declared-but-never-read 而写的。
后果
stack.permissions[].rowLevelSecurity[].using/.check是可授权面(PermissionSetSchema.rowLevelSecurity)。一条不可下推的谓词在运行期走RLSCompiler.compileExpression→compileCelToFilter返回!ok→return null→ 单策略路径变成RLS_DENY_FILTER(fail closed)。所以它不是安全漏洞,而是一条看起来在授权、实际在拒绝的策略:管理员写了一条 policy,用户被一律拒访,现场没有任何指向这条 policy 的诊断。
os validate/os build/os lint全绿。为什么没有随 #4698 一起做
判据本身是可机械判定的(和 sharing rule 那条同一个编译器),但判据的实现位置不对:
isSupportedRlsExpression,它先经sqlPredicateToCel把遗留 SQL-ish 子集(=→==,IN→in)桥接成 canonical CEL,再问isPushdownableCel。sqlPredicateToCel和isSupportedRlsExpression两个都住在plugin-security,那是一个 runtime。@objectstack/lint的包契约是 "Depends on @objectstack/spec; never on a runtime"。抄一份尤其危险:
sqlPredicateToCel是一个正则重写,它的IN/=边界条件(引号内字面量不重写、对 CEL 输入幂等)是这条规则判绿判红的分界线。两份实现只要漂移一格,lint 就会在 authoring 时否掉运行期其实能执行的策略 —— 误伤方向,比漏报更糟。建议做法(engine / security 车道)
sqlPredicateToCel上提到@objectstack/formula(它已经是isPushdownableCel的家,而且 lint 已经依赖它)。plugin-security从那里 re-export 或直接引用,保持一份定义。ADR-0058 D1 说的就是 "a single canonical shape gate",桥接属于那个 gate 的一部分。packages/lint加一条规则,判据 =isPushdownableCel(sqlPredicateToCel(expr)),覆盖permissions[].rowLevelSecurity[].using和.check,以及对象上直接授权的objects[].rowLevelSecurity[](validateOrgAxisRedLines已经在走这两条路径,可以照抄遍历)。error,理由同 sharing-rule 那条:没有任何一种读法下这条 policy 会按作者写的那样授权。examples/与packages/plugins/plugin-security/src/objects/default-permission-sets.ts里现存的每一条using/check都能下推,新 gate 不会让任何现有代码变红。验证脚本(实测,非推断)
相关:#4698(母议题,declared-but-never-read 的模式)、ADR-0056 D4、ADR-0058 D1。