Skip to content

controlled_by_parent children: the master-editability check still refuses an RLS-widened master, so #6909's fix covers the by-id write but not the derived-write path (17.0.0 GA) #8679

Description

@hotlong

Part of objectstack-ai/hotcrm#706. Successor to #5493, which closed via merged PR #6909 ("fix(sharing): defer to an app-authored RLS widener before hard-refusing a by-id write").

Measured on @objectstack/*17.0.0 GA during HotCRM's GA close-out sweep (hotcrm#1150 / #1152), real Bearer tokens against a running dev server.

What #6909 fixed, and what it did not

#5493's rc.2 symptom is gone: an app-authored RLS update-widener is now consulted on a direct by-id write to an object that carries sharing rules.

What still refuses is the derived write. crm_campaign_member is controlled_by_parent (ADR-0055) with master crm_campaign; its insert/update path first asks whether the caller may edit the MASTER. That check does not consult the master's RLS update policy — it resolves master editability through ownership and sys_record_share only. So the same principal, on the same master record, is simultaneously allowed to write the master and refused to write its children.

The isolation

One variable — who created the campaign. Everything else identical: same principal (marketing_user, which authors marketing_campaign_updates, an operation: 'update' RLS policy using: 'id != null' on crm_campaign), same objects, same payloads, same session.

stepcampaign created by ADMINcampaign created by MARKETING
1. PATCH the campaign itself (master, by id)200200
2. INSERT a child (crm_campaign_member)403201
3. UPDATE an admin-created child403200
4. control: admin updates the same child200200
5. security/explain update on the master, record-scopedallowed=trueallowed=true

Row 1 shows the widener in force in both columns; row 5 is the platform's own verdict agreeing. Rows 2 and 3 nonetheless refuse in the left column with:

[Security] Access denied: insert on 'crm_campaign_member' requires edit access to its
master record (master 'crm_campaign' not editable by this user (record sharing))

So security/explain and the master-editability check disagree about the same record, the same operation, and the same principal — the parenthetical names record sharing as the deciding layer, which is the layer #6909 taught to defer on the by-id path.

Why it matters

For HotCRM this is the app's "Add to Campaign" enrolment path — marketing's core write surface. On rc.2 the child widener worked and the master one did not; on GA the two have swapped, so the user-visible workflow is broken in the mirror direction. The app's declaration is unchanged and correct: an object grant plus an RLS update widener that says "marketing works any campaign".

Reproduction

# marketing_user token; a campaign it neither owns nor created
PATCH /api/v1/data/crm_campaign/:id {"description":"x"} -- 200
POST /api/v1/data/crm_campaign_member {"crm_campaign":":id", "crm_lead":"...", "status":"sent"}
-- 403, master not editable
# then repeat both against a campaign the same principal created -- 200 / 201

Suggested direction

Have the ADR-0055 master-accessibility resolution for WRITE consult the same composition #6909 installed on the by-id path, so "may I edit this master" answers identically whether it is asked directly or on behalf of a child.

Evidence: hotcrm#1152 probe 31-706-isolate.mjs, readings posted on hotcrm#706.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions