Skip to content

[security] An RLS policy authored in Studio's package door is silently discarded — Save succeeds, no row filter is ever persisted #4302

Description

@huangyiirene

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)

  1. Studio → Access pillar for a writable package → a permission set → Row-Level Security → Add policy.
  2. Fill Policy name, Object, USING (and CHECK).
  3. 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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpm:queue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions