Skip to content

Commit f49e075

Browse files
committed
fix(changeset, spec): scope the fail-closed sentence to the sole-policy case
The RLS compiler drops a policy carrying a list comparand, and the clause fails closed (RLS_DENY_FILTER) only when no other policy applies: beside an OR-sibling, the sibling decides. The qualifier "when no other policy applies" is added after "fails closed" in this PR's changeset and in the cel-predicate-list-comparand-refused entry's reason, as ruled. registry.ts regenerated by gen:migration-registry. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent a54aa9e commit f49e075

3 files changed

Lines changed: 5 additions & 3 deletions

File tree

‎.changeset/19886-cel-list-comparand-and-mongodb-ne-array-refused.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Clause-②: no (narrowing)
1616
**Security fix for RLS reads on MongoDB and RLS write checks.** A policy written `record.status != ['closed', 'archived']` (or `!(record.status == [...])`, or `!=` against a `current_user` membership set) lowered to `{ status: { $ne: [...] } }` (or `$not` around a bare-array equality). The RLS `using` clause is composed into the query after the engine's comparand-shape check, and driver-mongodb passed the shape to the server, where it selects every scalar row: the read returned the rows the policy was written to hide. A `check` written `!=` against a membership set admitted every write.
1717

1818
- `@objectstack/formula`: `compileCelToFilter` refuses `==` / `!=` whose comparand is a list (`unsupported`): a list literal, or a `current_user` variable that resolves to an array. The authoring shape check (`isPushdownableCel`, `isSupportedRlsExpression`) reports the literal; a resolved array is refused per request.
19-
- `@objectstack/plugin-security`: the RLS compiler drops such a policy and fails closed (`RLS_DENY_FILTER`: reads return no rows, `check` writes are refused 403). A CEL-authored `check` gets this 403; the `INVALID_FILTER` / 400 of `matchesFilterCondition` remains for a filter passed to it directly.
19+
- `@objectstack/plugin-security`: the RLS compiler drops such a policy and fails closed when no other policy applies (`RLS_DENY_FILTER`: reads return no rows, `check` writes are refused 403). A CEL-authored `check` gets this 403; the `INVALID_FILTER` / 400 of `matchesFilterCondition` remains for a filter passed to it directly.
2020
- `@objectstack/plugin-sharing`: a declared sharing rule with such a `condition` is skipped at bootstrap and never seeded.
2121
- `@objectstack/lint`: the list-literal form is reported (`rls-predicate-unenforceable`, `sharing-rule-unlowerable-condition`). The RLS reference pass probes each kernel-resolved `current_user` key with its runtime type.
2222
- `@objectstack/driver-mongodb`: `translateFilter` refuses `$ne` with an array comparand at any depth, with `INVALID_FILTER` / 400, as driver-sql and driver-memory already do.

‎packages/spec/src/migrations/entries/semantic/18.cel-predicate-list-comparand-refused.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export const entry: SemanticMigration = {
3333
+ 'row storing a scalar, so the read returned the rows the policy was written to hide. A check '
3434
+ 'written != against a membership set admitted and stored every write, on driver-sql as on '
3535
+ 'driver-mongodb. The compiler now refuses the comparison with reason unsupported, so the RLS '
36-
+ 'compiler drops the policy and fails closed: reads under it return no rows and check writes '
36+
+ 'compiler drops the policy and fails closed when no other policy applies: reads under it '
37+
+ 'return no rows and check writes '
3738
+ 'are refused 403. A declared sharing rule with such a condition is skipped at bootstrap and '
3839
+ 'never seeded. The authoring lint reports a list literal as rls-predicate-unenforceable; a '
3940
+ 'membership set holds its value only per request, so that form is refused at request time. '

‎packages/spec/src/migrations/registry.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6236,7 +6236,8 @@ const step18: MigrationStep = {
62366236
+ 'row storing a scalar, so the read returned the rows the policy was written to hide. A check '
62376237
+ 'written != against a membership set admitted and stored every write, on driver-sql as on '
62386238
+ 'driver-mongodb. The compiler now refuses the comparison with reason unsupported, so the RLS '
6239-
+ 'compiler drops the policy and fails closed: reads under it return no rows and check writes '
6239+
+ 'compiler drops the policy and fails closed when no other policy applies: reads under it '
6240+
+ 'return no rows and check writes '
62406241
+ 'are refused 403. A declared sharing rule with such a condition is skipped at bootstrap and '
62416242
+ 'never seeded. The authoring lint reports a list literal as rls-predicate-unenforceable; a '
62426243
+ 'membership set holds its value only per request, so that form is refused at request time. '

0 commit comments

Comments
 (0)