Skip to content

finding: an org-stamped sharing rule's criteria sweep runs unscoped — recipients are org-aware, matched records are not, and per-org materialization (10103) multiplies it #10119

Description

@os-warren

Observation

SharingRuleService.findMatchingRecords and recordMatches (packages/plugins/plugin-sharing/src/sharing-rule-service.ts, the criteria half of rule evaluation) run the rule's criteria query under a bare SYSTEM_CTX with no tenant scope, for every rule — including org-stamped ones:

constrows=awaitthis.engine.find(rule.object_name,{
filter,fields: ['id'],limit: 5000,context: SYSTEM_CTX,});

The recipient half is already org-aware — expandRecipient threads rule.organization_id into TeamGraphService / BusinessUnitGraphService / PositionGraphService. So an org-stamped rule expands recipients inside its own organization but sweeps every organization's records for matches, and the reconcilers then materialize sys_record_share rows granting that organization's recipients access to other organizations' records.

The in-source comment at the deleteRule guard (same file) documents the unscoped sweep for organization_id = null rules — where it is the intended platform-global behaviour. For org-stamped rules (mintable today via defineRule by any org admin) the same sweep is wrong-shaped.

Why this is an observation, not a measured breach

Under a walled posture the Layer-0 tenant wall AND-composes over sharing's Layer-1 widening, so a cross-org sys_record_share row cannot actually open a read across the wall — the rows are inert. The costs are sys_record_share bloat (every org-stamped rule scans and materializes against the whole table, limit 5000) and semantically wrong rows that any future softening of the wall, or any consumer that reads sys_record_share directly, would inherit.

Multiplier

Issue 10103 (ruled Option C, per-organization catalog materialization) will turn the seeded org-less rules into N per-org copies; each copy then sweeps the whole table, multiplying the wrong-shaped rows by the organization count.

Remedy sketch

Thread rule.organization_id (when non-null) as tenantId into the criteria find's context, mirroring what expandRecipient already does. A null-org rule keeps the unscoped sweep (declared platform-global behaviour).

Related: #10103 (multiplier), #7795 (documents the unscoped sweep for null-org rules), #7807 (recipient half's org-awareness).


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