Skip to content

Commit 2946dce

Browse files
committed
docs(releases): correct the 17.6.0 security, analytics and filter claims
- anonymous endpoints: no supported channel grants anonymous callers a permission set until #21158 lands, so the page no longer prescribes one, and the release-time TODO says what to do if it lands first; - manage_platform_settings covers reads of both types and writes of datasource only; the data door's routes are named as cfad7de names them; - the field answers, the dataset-door status after 434c6c7, the sum / avg class, radio among multi-capable types, the filter positions, the $exists readings, the time-default refusal path and the year-first import cells are stated as their changesets state them. Clause-②: no Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014VGCS11YUtYAiinRcdqQwL
1 parent d2b08b3 commit 2946dce

1 file changed

Lines changed: 67 additions & 44 deletions

File tree

‎content/docs/releases/v17/17-6.mdx‎

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
1414
Console" and drop each "Known console issues" line the new pin fixes
1515
(objectui 0858267e, 8001068b, 3ae91930); if it did not, record the
1616
accepted-for-GA waiver the release-readiness rule asks for;
17-
5. update v17/index.mdx (status blockquote, per-release list, checklist links).
17+
5. if #21158 (the guest anchor's bindings for anonymous callers) lands
18+
before the cut, replace the "no supported channel" lines in Highlights,
19+
the deny-baseline Migration and the checklist with its grant channel;
20+
6. update v17/index.mdx (status blockquote, per-release list, checklist links).
1821
Delete this comment when done.
1922
*/}
2023

@@ -25,9 +28,11 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
2528
caller that carries a principal but resolves no permission set used to be
2629
admitted to every object no set grants, with every field served as stored.
2730
It is now refused on every object, served gated fields masked or not at all,
28-
and refused any query on them. ⚠️ **App-declared anonymous endpoints and
29-
embedders that set `fallbackPermissionSet: null` must grant a permission set
30-
before upgrading.**
31+
and refused any query on them. ⚠️ **An embedder that sets
32+
`fallbackPermissionSet: null` must grant its users a permission set before
33+
upgrading, and an app-declared anonymous endpoint (`authRequired: false`)
34+
can no longer read or write objects: no supported channel grants anonymous
35+
callers a permission set yet (#21158, open).**
3136
- **Field-level security reaches every query door** — cross-field comparands,
3237
the activity stream, the compliance ledger, approval snapshots and analytics
3338
(`de8cd58`, #20954; `1ecb871`, #21179; `1571aed`, #20931). And
@@ -38,7 +43,8 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
3843
door, audit copies, realtime events and MCP stdio (`cfad7de`, #21115;
3944
`336e191`, #21144; `3ddd3d0`, #21228). Run
4045
`os migrate audit-metadata-bodies --apply` to rewrite the copies already at
41-
rest. `datasource` and `external_catalog` metadata on `/api/v1/meta` now need
46+
rest. Reads of `datasource` and `external_catalog` metadata, and writes of
47+
`datasource` metadata, on `/api/v1/meta` now need
4248
`manage_platform_settings` (`454bbb6`, #21148; `7a606a9`, #21119).
4349
- **Analytics is judged like the data door** — anonymous `401`, hidden and
4450
masked fields `403`, related objects admitted and row-scoped, structured,
@@ -54,8 +60,9 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
5460
`''` and `[]`** (`f1e921a`, #20570).
5561
- **Temporal values follow one rule at every door:** a `datetime` names a UTC
5662
year from 1000 to 9999, a `time` is a zone-less wall clock, and an import
57-
reads `date` / `datetime` / `time` cells only in ISO 8601 and the export's
58-
own shape (`05a7547`, #20843; `63bfe69`, #20721; `eb4b17c`, #20601).
63+
reads `date` / `datetime` / `time` cells only in ISO 8601, the export's own
64+
shape or a year-first date (`05a7547`, #20843; `63bfe69`, #20721; `eb4b17c`,
65+
#20601).
5966
- **One rule decides which flow is packaged.** A packaged flow wins over a
6067
stored flow of the same name at every startup step (`75519e1`, #20942), so ⚠️
6168
**a stored flow that shares a packaged flow's name stops running.** Flows
@@ -69,7 +76,7 @@ description: "Release notes and upgrade checklist for 17.6.0 of the v17 line."
6976
tenant database — applies the caller's tenant scope (`4b59a38`, #21245), and
7077
a tenant-scoped `upsert` never merges into another organization's row
7178
(`95e24b0`, #21225). The Turso remote transport issues `auto_number` values
72-
(`e35c40a`, #21160), and MySQL stores `sys_jwks` and `sys_member` rows again
79+
(`e35c40a`, #21160), and MySQL stores `sys_jwks` and `sys_member` rows
7380
(`95b91cc`, #21272).
7481
- **Shared picklists:** a new `picklist` metadata kind that fields reference by
7582
name and other packages can extend (`addbbf0`, #20823; `88b484e`, #21047).
@@ -107,8 +114,9 @@ explicitly breaking. Several things in this release change behaviour on a
107114
included, only rows about records they can read — every `delete` row and
108115
sign-out row drops out of the data API;
109116
- stored metadata bodies are redacted on the data door, in audit copies,
110-
realtime events and MCP stdio, and `datasource` / `external_catalog` metadata
111-
on `/api/v1/meta` needs `manage_platform_settings`;
117+
realtime events and MCP stdio, and reading `datasource` / `external_catalog`
118+
metadata (or writing `datasource`) on `/api/v1/meta` needs
119+
`manage_platform_settings`;
112120
- analytics widgets that read a hidden or masked field, or a related object the
113121
caller may not read, answer `403`; a time dimension that declares one
114122
granularity is bucketed by it;
@@ -118,7 +126,8 @@ explicitly breaking. Several things in this release change behaviour on a
118126
- 「is empty」 in a stored sharing rule or view also matches `''` and `[]`, so
119127
such a sharing rule shares more records;
120128
- a `datetime` write before year 1000, a `time` value with an offset, and an
121-
import cell in a non-ISO date spelling are refused;
129+
import date cell that is not ISO 8601, the export's shape or year-first
130+
(`07/15/2026`, an Excel serial) are refused;
122131
- a caller-supplied `formula` value is stripped from every write;
123132
- a stored flow that shares a packaged flow's name stops running; the toggle
124133
refuses customer-authored flows with `409`; enabling a packaged flow whose
@@ -169,8 +178,10 @@ filtered, sorted, grouped, aggregated and written.
169178
- **Fields gated by `requiredPermissions`** (`665cab3`, #21134). The field is
170179
not served (or is served masked, if it also declares a `maskingRule`). A
171180
filter, sort key, group key, aggregate or write payload naming it is refused
172-
`403 PERMISSION_DENIED`. `getReadableFields`, `getQueryableFields`,
173-
`getWritableFields` and `getMetadataReadableFields` no longer list it.
181+
`403 PERMISSION_DENIED`. `getQueryableFields` and `getWritableFields` no
182+
longer list it; `getReadableFields` lists it only when a `maskingRule` serves
183+
it masked, and `getMetadataReadableFields` drops it when the deployment's
184+
fallback set resolves to nothing.
174185
- **Fields with a `maskingRule`** (`a9d36d5`, #21051). The field is served
175186
masked and is not queryable. A write that sends the masked placeholder back
176187
is refused `400 VALIDATION_ERROR`.
@@ -185,7 +196,10 @@ users of a stock `objectstack serve` are not affected.
185196

186197
- App-declared anonymous endpoints (`authRequired: false`) can no longer read
187198
or write objects until the `guest` anchor's bindings resolve for anonymous
188-
callers (#21158). Give those callers a permission set before you upgrade.
199+
callers (#21158). That issue is open, and until it lands no administrator
200+
binding grants an unauthenticated caller a permission set, so there is no
201+
supported migration: an app that depends on such an endpoint reading or
202+
writing objects should hold the upgrade.
189203
- An embedder that sets `fallbackPermissionSet: null` must grant signed-in
190204
users a permission set explicitly.
191205
- A caller that needs a gated field needs a permission set holding all of the
@@ -273,8 +287,8 @@ including a `datasource` body's credential material. That body was served as
273287
stored on several exits. It is now projected through the same redactor the
274288
`/meta` exits use:
275289

276-
- **the generic data door** (`cfad7de`, #21115): `GET` and `POST` on
277-
`/api/v1/data/:object`, the by-id read, and the export route. A body whose row
290+
- **the generic data door** (`cfad7de`, #21115): `GET /api/v1/data/:object`,
291+
`POST /api/v1/data/:object/query`, the by-id read, and the export route. A body whose row
278292
has no `type`, or that fails to parse while its type registers a redactor, is
279293
omitted. `?select=metadata` still works.
280294
- **audit copies and realtime events** (`336e191`, #21144): `sys_audit_log`'s
@@ -314,8 +328,8 @@ definitions there.
314328
unaffected.
315329

316330
**Migration.** Grant `manage_platform_settings`, through a permission set, to
317-
every user or integration that reads or writes these types through
318-
`/api/v1/meta` while holding only
331+
every user or integration that reads either type, or writes `datasource`,
332+
through `/api/v1/meta` while holding only
319333
`manage_metadata`, `studio.access` or `setup.access` — or route those calls
320334
through a caller that already holds it.
321335

@@ -335,7 +349,7 @@ strategies.
335349
(`1571aed`, #20931). Dimensions, measures, time dimensions, filter members,
336350
order keys, joined members and a dataset's own and requested measures'
337351
filters are judged; a hidden one answers `403 PERMISSION_DENIED`. Before,
338-
the native-SQL strategy answered, and `/sql` printed the statement.
352+
the native-SQL strategy answered such queries.
339353
- **Masked fields cannot be grouped, aggregated, filtered or sorted**
340354
(`83480c6`, #20955); they answer `403`. If the security service cannot say
341355
which fields are queryable, every field declaring a `maskingRule` is treated
@@ -365,9 +379,11 @@ strategies.
365379
(`58a77db`, #21117), as the data door does. Before, a compiled read scope
366380
matched the stored JSON text as a substring: on SQLite a policy could admit
367381
rows outside it, and on PostgreSQL every query under it answered `500`.
368-
- **A dataset `field` that is not a column reference is refused `403`** at the
369-
dataset door, for every caller and with or without a security service
370-
(`ce4e205`, #21190).
382+
- **A dataset `field` that is not a column reference is refused at the
383+
dataset door**, for every caller and with or without a security service
384+
(`ce4e205`, #21190). Since `434c6c7` (#21240) the door parses the dataset
385+
first and answers `400 VALIDATION_FAILED`; the `403` remains only for a
386+
stored row that reaches the service without that parse.
371387

372388
**What may be grouped, counted and aggregated**
373389

@@ -384,8 +400,9 @@ strategies.
384400
- **Every cube measure is checked against the aggregate × field-type table**
385401
(`39ab294`, #21128), and so is a measure over a relationship path such as
386402
`account.name` (`3a7b6eb`, #21230). `min` / `max` over a non-numeric,
387-
non-temporal, non-boolean type, and `sum` / `avg` over a non-numeric type
388-
(or `sum` over `percent`), answer `400 INVALID_FIELD` instead of a raw value
403+
non-temporal, non-boolean type, and `sum` / `avg` over a type outside the
404+
numeric and boolean classes (or `sum` over `percent`), answer `400
405+
INVALID_FIELD` instead of a raw value
389406
typed `number`, a `0` or a `500`. `min` / `max` over `date`, `datetime` or
390407
`time` is now described `fields[] { type: 'time' }`, not `number`, and a
391408
related numeric `min` / `max` on PostgreSQL returns a number instead of an
@@ -492,8 +509,8 @@ before any driver is asked. The error names the position (`groupBy[0]`,
492509
- **`groupBy` on a structured-JSON field** — `json`, `composite`, `repeater`,
493510
`record`, `location`, `address`, `vector` (`157baa7`, #20804).
494511
- **`groupBy` on a multi-value field** — `multiselect`, `checkboxes`, `tags`,
495-
or a `select` / `lookup` / `user` / `file` / `image` declared `multiple:
496-
true` — **and `count_distinct` over a JSON-stored or multi-value field**
512+
or a `select` / `radio` / `lookup` / `user` / `file` / `image` declared
513+
`multiple: true` — **and `count_distinct` over a JSON-stored or multi-value field**
497514
(`975b248`, #20911).
498515
- **`min` / `max` / `avg` over a type the aggregate × field-type table refuses**
499516
(`a75311d`, #21037). `min` / `max` accept `number`, `currency`, `percent`,
@@ -524,8 +541,8 @@ are the hand-migrations already registered under
524541
#### A filter is refused where it used to answer the wrong rows
525542

526543
Each of these used to return every row, no rows, a text comparison or a
527-
driver's own `500`. Each now answers `INVALID_FILTER` / `400` at `where`, a
528-
per-aggregation `filter` and `having`, before any driver read:
544+
driver's own `500`. Each now answers `INVALID_FILTER` / `400` — at `where`, a
545+
per-aggregation `filter` and `having` unless the item names its positions:
529546

530547
- **An object with no `$` operator under a scalar field** (`{ "amount": { "a":
531548
1 } }`, or `{}`) (`97005ae`, #20744), and **under a structured-JSON field or
@@ -543,17 +560,19 @@ per-aggregation `filter` and `having`, before any driver read:
543560
(`dcd3309`, #21065) or past what a JavaScript `Date` can hold (`1bd14c9`,
544561
#21123), such as `{300000_years_ago}`.
545562
- **`$startsWith`, `$endsWith`, `$icontains`, `$like` or `$ilike` on a
546-
JSON-stored or multi-value field** (`2c1cef3`, #21165). SQLite matched the
547-
serialized text (`$startsWith: "["` matched every valued row) and PostgreSQL
548-
answered `500`.
563+
JSON-stored or multi-value field**, in the SQL drivers' `where` (Turso's
564+
local transport included) and the per-aggregation `filter` (`2c1cef3`,
565+
#21165). SQLite matched the serialized text (`$startsWith: "["` matched every
566+
valued row) and PostgreSQL answered `500`.
549567
- **`$eq`, `$ne`, orderings, `$between`, `$in`, `$nin` or implicit equality on
550568
a JSON-stored field inside a per-aggregation `filter`** (`a11faee`, #21097),
551569
as `where` already refused. `{ owners: { $in: ['u1', 'u9'] } }` counted `0`,
552570
and `$nin` counted the rows it was asked to exclude.
553-
- **A non-boolean `$exists` or `$null`** in the in-memory and MongoDB drivers
554-
(`a3dc817`, #20979) and in a per-aggregation `filter` or `having`
555-
(`c35436c`, #21157). `"yes"`, `1` and the string `"false"` were read by
556-
truthiness.
571+
- **A non-boolean `$exists`** in the in-memory and MongoDB drivers, which
572+
read anything but `true` as "has no value" (`a3dc817`, #20979), and **a
573+
non-boolean `$exists` or `$null`** in a per-aggregation `filter` or `having`,
574+
where `"yes"`, `1` and the string `"false"` were read by truthiness
575+
(`c35436c`, #21157).
557576

558577
**Migration.** Compare a field with a value or an operator; to filter by a
559578
related record, name a relation field. Send temporal comparands in ISO 8601
@@ -600,9 +619,10 @@ payloads.
600619
- **A `time` value is a zone-less wall clock.** A `time` field refuses a value
601620
carrying `Z` or an offset (`"10:00Z"`, `"10:00+08:00"`) and an instant whose
602621
UTC year has no four-digit spelling (`63bfe69`, #20721), with
603-
`VALIDATION_FAILED` / `400` (`invalid_time`). A `time` field default, a
604-
`time` action-param default and a submitted `time` action param are refused
605-
the same way where they are authored or submitted (`c9c182e`, #20763). On
622+
`VALIDATION_FAILED` / `400` (`invalid_time`). A zone-suffixed `time` field
623+
default or `time` action-param default is refused when the schema parses it,
624+
and a submitted `time` action param with a zone answers `invalid_shape`
625+
(`c9c182e`, #20763). On
606626
memory and SQLite, zone-suffixed values used to be stored verbatim and read
607627
back differently.
608628
- **`POST /api/v1/data/:object/import` reads `date`, `datetime` and `time`
@@ -1456,13 +1476,16 @@ been given.
14561476

14571477
**Before you upgrade**
14581478

1459-
- **Give a permission set to every caller that carries a principal but
1460-
resolves none:** callers of app-declared anonymous endpoints
1461-
(`authRequired: false`) that read or write objects, and signed-in users on an
1462-
embedder that sets `fallbackPermissionSet: null`. After the upgrade they are
1463-
refused every object. *Not exercised.*
1479+
- **Find every app-declared anonymous endpoint (`authRequired: false`) that
1480+
reads or writes objects.** After the upgrade it is refused every object, and
1481+
no supported channel can grant anonymous callers a permission set until
1482+
#21158 lands; hold the upgrade if you depend on one. *Not exercised.*
1483+
- **Grant a permission set to signed-in users on an embedder that sets
1484+
`fallbackPermissionSet: null`;** without one they are refused every object.
1485+
*Not exercised.*
14641486
- **Grant `manage_platform_settings`** to every user or integration that reads
1465-
or writes `datasource` or `external_catalog` metadata through `/api/v1/meta`
1487+
`datasource` or `external_catalog` metadata, or writes `datasource`, through
1488+
`/api/v1/meta`
14661489
while holding only `manage_metadata`, `studio.access` or `setup.access`. *Not
14671490
exercised.*
14681491
- **List stored flows that share a packaged flow's name** — startup warnings or

0 commit comments

Comments
 (0)