Skip to content

Commit 2bd379d

Browse files
committed
chore(changeset): qualify the RLS base reading and state the real accept condition
The empty string was not refused under row-level security: the security middleware's composition dropped it as absent. And a filter object is accepted by its built-in tag, so one that overrides Symbol.toStringTag is refused. Prose only. Claude-Session: https://claude.ai/code/session_01Bvd69VPa6puiNzzPUroDBx Co-authored-by: Claude <noreply@anthropic.com>
1 parent aa923a1 commit 2bd379d

1 file changed

Lines changed: 23 additions & 7 deletions

File tree

‎.changeset/20121-engine-where-shape-refused.md‎

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,34 @@ the seam and the driver ignored it. Measured on `driver-memory` and
3232
guard refused the call, with no `code`).
3333
- `update(…, { where, multi: true })` rewrote all four rows, and
3434
`delete({ where, multi: true })` deleted all four. That held without
35-
`SecurityPlugin`, and with it under a system context. For a caller scoped by
36-
row-level security, the security middleware wrapped the value into its `$and`,
37-
where the driver refused it (`INVALID_FILTER`) and nothing was written.
35+
`SecurityPlugin`, and with it under a system context.
36+
- For a caller scoped by row-level security, it depended on the value.
37+
- A string, a number, a `Map`, a `Date`, a `Set` or `true` was wrapped by the
38+
security middleware into its `$and`, where the driver refused it
39+
(`INVALID_FILTER`) and nothing was written.
40+
- The empty string was not refused. The middleware's composition reads a
41+
falsy `where` as absent and dropped it, so `find` answered all of the
42+
member's rows, and `update(multi)` / `delete(multi)` rewrote or deleted
43+
every row the member could reach (2 of 4 on both drivers).
3844
- Such a `where` also stepped past the unscoped-write guard that a hook opts
3945
into with `dispatchUnscopedMultiWrite`, because that guard treats only an
4046
absent or `null` `where` as unscoped.
4147

4248
**Unchanged.** An absent `where`, `null`, `{}` and `[]` still mean "no
43-
filter". A filter object is accepted whatever its prototype, so an
44-
`Object.create(null)` object or an instance of your own class carrying the filter
45-
on its own keys filters exactly as before. A well-formed filter array is lowered
46-
as before. The REST door already answered a non-filter `?filter=` with
49+
filter". A filter object is accepted when it is a non-array object whose
50+
built-in tag (`Object.prototype.toString`) is `[object Object]`. That covers a
51+
plain object, an `Object.create(null)` object, an instance of your own class
52+
carrying the filter on its own keys, a `Proxy` of one, and an object from
53+
another realm, and each filters exactly as before. A well-formed filter array is
54+
lowered as before.
55+
56+
**One accepted shape is now refused.** An object that overrides
57+
`Symbol.toStringTag`, as its own key or through its prototype chain, has a
58+
different built-in tag. It is refused and named by that tag (for example
59+
`received Criteria`). Before this change such an object filtered correctly on
60+
its own keys. No producer in this repository creates one: the wire is JSON, and
61+
the SDK builds arrays or plain objects. If yours does, pass its filter keys in a
62+
plain object instead. The REST door already answered a non-filter `?filter=` with
4763
`INVALID_FILTER` / 400 and is not touched.
4864

4965
**Fix.** Pass the predicate you meant as a filter object, for example

0 commit comments

Comments
 (0)