Skip to content

plugin-sharing: grant() skips the ADR-0111 D7 inert-grant guard entirely for SYSTEM callers, so the sharing-rule evaluator can still materialise rows no gate consults #8207

Description

@os-zhuang

Found while implementing #8119 (the assertSharingEnforced half). Filed unassigned, deliberately not fixed there: #8119's scope ruling names assertSharingEnforced only, and this is about who the guard runs for, not what it checks.

The shape

SharingService.grant runs the ADR-0111 D7 posture guard and the D1 management gate only for non-system callers (sharing-service.ts):

if(!context?.isSystem){this.assertSharingEnforced(input.object);awaitthis.assertCanManageShares(input.object,input.recordId,context);}

The comment above it states the intent — "System callers bypass: the rule evaluator materialises through here under its own validation." The management half (D1) is clearly right to skip: a rule is not a principal and has no ownership to prove.

The posture half is a different question. assertSharingEnforced is not an authorization check — it is an inertness check (its own docblock says so: "an inertness guard, not the authority gate"). It answers "would any gate ever read a row on this object?", and that answer does not depend on who is asking. A rule-materialised row on a public_read_write object, a controlled_by_parent detail, an owner-less object, or (after #8119) a federated phantom-anchor object is exactly as inert as a manually granted one — the ADR-0078 silently-inert trap, arriving through the door the guard does not watch.

Why this is observation-class, not a live defect

I did not find a shipped app that declares a sharing rule on any of those object classes, so as far as I measured the path is dormant. What I did measure (booted showcase stack, #8119's evidence) is only that the guard is skipped for system context by construction, and that the non-system path now refuses all four classes.

The "own validation" the comment refers to is the rule evaluator's — worth checking whether it independently rejects these object classes before materialising. If it does, this is a documentation gap rather than a hole; if it does not, an authored rule pointed at one of those objects mints rows that look granted and enforce nothing. That check is the first step of triaging this, and I did not do it.

Suggested direction (not a decision)

Split the two halves rather than moving the whole block: keep the D1 management gate system-skipped, and let the D7 inertness guard run for every caller. It refuses nothing that could have worked, so it is the fail-closed direction — the same argument that authorised #8119's Phase 2. Worth confirming it does not break the boot-time rule backfill (SharingServicePlugin: boot rule backfill done), which reconciles declared rules through this path on every start.

Related


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