Skip to content

[Decision] sys_* objects silently default to PUBLIC row visibility — should the platform's own objects keep an unstated default at all, and is isSystem even the right predicate? #8641

Description

@qq9340100

Filed unassigned by the domain:spec PM seat (session session_012MNV7ZSCjNfA38eDCjsXQL) at the maintainer's direct instruction, 2026-08-14, during the #8310 shift. This is the carve-out #8310's ruling left behind, surfaced deliberately rather than allowed to evaporate with the conversation that found it.

Where this comes from

#8310 (PR #8600) ruled that absence is not a decision at the runtime object door: an active-state object publish with no authored sharingModel is now refused 422 security-owd-unset. The ordinary-object default (ADR-0094 D1, "absence resolves to private") was overturned, and the pin asserting a write with NO OWD keys SAVES was rewritten into the refusal.

That rule exempts system objects. Per the ADR-0094 amendment landed by the same PR:

a system object (isSystem / sys_*) with no authored sharingModel is effectively PUBLIC at runtime (effectiveSharingModel, plugin-sharing) … and security-owd-unset deliberately exempts system objects for the same reason.

So after #8310 the platform's silent defaults look like this:

Object class Unstated sharingModel resolves to After #8310
ordinary (customer/package authored) private refused — must be stated
isSystem / sys_* public still silent, still accepted

The widest silent default in the platform is the one that survived.

Why the exemption's rationale does not settle the default

The exemption is correct as damage control, and its stated reason is sound: R2's hardcoded private baseline was refusing system-object configurations that are not external-wider at runtime — those were false positives, which is exactly why R2 was retired. ⚠️ But that reason establishes only that private was the wrong baseline; it does not establish that public is the right default. The two got merged into one carve-out without the second half being argued.

The two populations under one rule

sys_* is not one kind of thing, and a single default serves them very differently:

A — reference / configuration data. Positions, capabilities, permission-set definitions, settings, status vocabularies. The whole org must read these for the product to function (a picklist of positions, an approval screen, the permission admin UI). For these, row visibility is not the control surface at all — CRUD/FLS and the ADR-0086 two-doors gate are. public here is an honest statement of "this object does not defend itself by row visibility," and defaulting them private would break the platform.

B — record-bearing system objects. Candidates, to be measured, not asserted: sys_approval_request / sys_approval_action (who asked for what, who approved), sys_job (payloads), sys_http_delivery (delivery records — see #8566 on its plaintext-secret shape), sys_notification_subscription (who subscribes to what), sys_user_permission_set (who holds which access — a ready-made reconnaissance map). For these, public is not "the control surface is elsewhere"; it may be "the control surface is absent."

⛔ The sharper form of the question

The exemption keys on isSystem — a provenance flag meaning "the platform shipped this." The property that should decide a visibility default is "does this object hold records whose visibility matters". Those two are not the same predicate, and population B is exactly where they come apart.

The decision needed

Should sys_* objects keep an unstated PUBLIC default — and if not, what replaces it?

  • A — classify per object by what actually enforces visibility (recommended shape, ruling still the maintainer's). Reuse the pattern ADR-0094's own addendum already established for the declared-metadata ↔ queryable-record family: that addendum refused a blanket rule and classified each type by one question (which store does enforcement read at request time?). The analogue here: for this object, is row visibility the control surface, or do tenancy/FLS/CRUD already gate it? Population A keeps a wide default (stated, not silent); population B states a real posture.
  • B — platform objects must declare, same as everyone else. Drop the exemption; every sys_* declaration authors its sharingModel explicitly. Most expensive in edits, but the platform's own objects have the largest blast radius and are the least deserving of silence. Can be staged: declare first, refuse later.
  • C — keep the blanket public default, and record it as a deliberate, documented decision rather than an artifact of R2's retirement. Cheapest; acceptable only if the measurement below shows population B is genuinely gated elsewhere.
  • Not an option: flip the blanket default to private. That is the R2 baseline that was just measured wrong and retired; it would break population A.

⚠️ What must be MEASURED before this is ruled — it is not a philosophical question

This card deliberately does not claim any object is currently exposed. The load-bearing unknown:

For each population-B object, is the PUBLIC OWD default actually reachable, or is it already masked by the tenancy filter (computeTenantLayer0Filter), FLS, or CRUD?

Worklist for whoever takes it:

  1. Enumerate isSystem objects with no authored sharingModel (the real set — do not assume the list above is complete or correct).
  2. Split them A/B by the criterion in option A.
  3. For each B object, determine empirically what refuses an unauthorized read today, if anything — a live probe, not a code-reading argument.
  4. Report the divergence set: objects where PUBLIC row visibility is the only thing standing between a caller and the rows.

⚠️ Do not assume tenancy already covers it. Tonight's backlog is an active, measured line of work finding exactly these assumptions wrong: #8577 (two more security-graded inheritors of the #8323 class), #8578 (the tenancy filter alone yields 20 findings, not 6), #8555 (sys_setting's installation-wide unique key on a tenant-scoped object), #8566 (sys_webhook.headers_secret).

What this card does NOT claim

  • ⛔ Not that [3 of #7891] Flip the registration: runtimeTypes gains object + permission/book — whole rule families cross the publish door #8310's exemption was wrong. It was right as shipped — refusing system objects would have broken population A and the decision is not the customer's to make.
  • ⛔ Not that any sys_* object is currently leaking. Step 3 above is precisely what has not been done.
  • ⛔ Not that population B's list above is accurate. It is a starting hypothesis produced by reading, not measuring.
  • Not urgent by default. Grading belongs to triage — ⚠️ if step 3 finds a B object where OWD is the only gate, this becomes a security card and should be regraded immediately.

Recommendation

Classify (option A) rather than pick a blanket default, and treat "stated" as separable from "wide": population A can legitimately stay world-readable, but it should say so in its declaration instead of inheriting silence. That keeps #8310's principle — a posture is something someone decided — while not pretending row visibility is the control surface where it demonstrably is not. The ruling is the maintainer's; this card does not pre-empt it, and the measurement above should inform it.

Related

#8310 / PR #8600 (the ruling and the exemption) · ADR-0094 (D1, the 2026-08-14 amendment, and the addendum whose classification pattern option A reuses) · ADR-0090 D11 · #8547 (the other #8310 downstream) · the sys_* scoping family: #8323, #8555, #8566, #8577, #8578

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions