Impact
An author fills in a row-level-security policy (name, object, USING, CHECK), clicks a fully-enabled Save, gets no error, and is told the permission set saved. No row filter is ever persisted.
Anyone reading the Studio surface afterwards sees a permission set that looks configured. The records the policy was meant to scope are unprotected, and the failure mode is silence — which is worse than a refusal, because the admin now has positive (false) evidence that a control is in place.
rowLevelSecurity is not the only casualty: tab visibility and delegated-admin scope are reverted the same way.
Reproduction (observed on the wire)
- Studio → Access pillar for a writable package → a permission set → Row-Level Security → Add policy.
- Fill Policy name, Object, USING (and CHECK).
- Save.
The PUT body is:
{"name":"qa_st2_rls","label":"QA ST2 RLS","objects":{},"fields":{}}
— no rowLevelSecurity key at all. The server answers 200, and a GET /api/v1/meta/permission/<n> after publish carries no policy.
Control that isolates the door: the environment door (the routed editor at /apps/setup/metadata/permission/<n>, no packageId) does send rowLevelSecurity in the body. So this is specific to the package path, not to the editor's data collection — the editor gathers the policy correctly and the package save path drops it.
Root cause (located)
PermissionMatrixEditor.doSave() routes through mergePermissionSlice() whenever packageId is set, and permission-slice.ts:117-124 returns:
{...base, name, label, isDefault, objects, fields}
It carries only those five keys from edited and takes every advanced facet from base — a fresh server read. So rowLevelSecurity, tab visibility and delegated-admin scope are silently reverted to whatever the server already had.
The slice is a whitelist that has drifted behind the facets the editor can author. That is the general shape worth fixing, not just the one missing key: any facet added to the editor in future is silently dropped on this door until someone remembers to widen the whitelist.
Acceptance criteria
- A policy authored through the package door is persisted — asserted by reading
GET /api/v1/meta/permission/<n> back after publish and finding the policy, not by asserting the save returned 200 (a 200 is exactly what this defect already produces).
- The same assertion covers the other facets the whitelist drops: tab visibility and delegated-admin scope.
- The environment door's behaviour is unchanged — assert it as a guard so the fix does not regress the path that already works.
- Consider a structural guard rather than five more keys: if the slice must stay a whitelist, something should fail loudly when the editor can author a facet the slice does not carry. A silent drop is what made this expensive.
Companion defect, filed separately
The console also seeds a new policy with the retired priority key (PermissionAdvancedFacets.tsx:368), which the environment door rejects with a located 422 (rowLevelSecurity[].priority was removed in @objectstack/spec 17.0.0, #3896 security audit). That one is being filed from the same QA run as the public companion.
Taken together: an RLS policy cannot currently be saved from the console on either door — the package door drops it silently (this card), and the environment door refuses it loudly over the retired key (the companion). This card is the dangerous half; a loud refusal at least tells the truth.
Provenance & disclosure
Held back from the public run card objectstack-ai/objectstack#7695 pending disclosure handling; the private write-up is docs/qa/platform-checklist/FOLLOW-UPS.md §1a entry D13 (objectstack branch claude/platform-test-checklist-ocwugl @ 1ce13eb). Filed publicly under the maintainer's standing instruction of 2026-08-11 (verbatim): 「有问题就立卡片」.
Source
QA run objectstack-ai/objectstack#7695 (studio-authoring.expression-editors, finding B; framework 92f26f75, console bundle 09987b680), private write-up FOLLOW-UPS.md §1a D13.
Impact
An author fills in a row-level-security policy (name, object, USING, CHECK), clicks a fully-enabled Save, gets no error, and is told the permission set saved. No row filter is ever persisted.
Anyone reading the Studio surface afterwards sees a permission set that looks configured. The records the policy was meant to scope are unprotected, and the failure mode is silence — which is worse than a refusal, because the admin now has positive (false) evidence that a control is in place.
rowLevelSecurityis not the only casualty: tab visibility and delegated-admin scope are reverted the same way.Reproduction (observed on the wire)
The PUT body is:
{"name":"qa_st2_rls","label":"QA ST2 RLS","objects":{},"fields":{}}— no
rowLevelSecuritykey at all. The server answers 200, and aGET /api/v1/meta/permission/<n>after publish carries no policy.Control that isolates the door: the environment door (the routed editor at
/apps/setup/metadata/permission/<n>, nopackageId) does sendrowLevelSecurityin the body. So this is specific to the package path, not to the editor's data collection — the editor gathers the policy correctly and the package save path drops it.Root cause (located)
PermissionMatrixEditor.doSave()routes throughmergePermissionSlice()wheneverpackageIdis set, andpermission-slice.ts:117-124returns:It carries only those five keys from
editedand takes every advanced facet frombase— a fresh server read. SorowLevelSecurity, tab visibility and delegated-admin scope are silently reverted to whatever the server already had.The slice is a whitelist that has drifted behind the facets the editor can author. That is the general shape worth fixing, not just the one missing key: any facet added to the editor in future is silently dropped on this door until someone remembers to widen the whitelist.
Acceptance criteria
GET /api/v1/meta/permission/<n>back after publish and finding the policy, not by asserting the save returned 200 (a 200 is exactly what this defect already produces).Companion defect, filed separately
The console also seeds a new policy with the retired
prioritykey (PermissionAdvancedFacets.tsx:368), which the environment door rejects with a located 422 (rowLevelSecurity[].prioritywas removed in@objectstack/spec17.0.0, #3896 security audit). That one is being filed from the same QA run as the public companion.Taken together: an RLS policy cannot currently be saved from the console on either door — the package door drops it silently (this card), and the environment door refuses it loudly over the retired key (the companion). This card is the dangerous half; a loud refusal at least tells the truth.
Provenance & disclosure
Held back from the public run card objectstack-ai/objectstack#7695 pending disclosure handling; the private write-up is
docs/qa/platform-checklist/FOLLOW-UPS.md§1a entry D13 (objectstack branchclaude/platform-test-checklist-ocwugl@1ce13eb). Filed publicly under the maintainer's standing instruction of 2026-08-11 (verbatim): 「有问题就立卡片」.Source
QA run objectstack-ai/objectstack#7695 (
studio-authoring.expression-editors, finding B; framework92f26f75, console bundle09987b680), private write-up FOLLOW-UPS.md §1a D13.