Skip to content

[finding] GET /meta/:type/:name/diff serves PENDING draft content to a member with no authoring capability: its history versions include draft saves, and it is the one draft-serving door the #20338 gate leaves open #20378

Description

@objectstack-fleet

Ruled: 5865708652 · letter B · 2026-09-28T07:49Z

Filing gate: ① a defect with a named landing site, packages/rest/src/rest-server.ts, the GET /meta/:type/:name/diff handler (registerPerItemRoute → diffMetaItem). Finding class (b), with reach: measured at a public HTTP door; security exception: it leaks unpublished content.

Found by the os-dev round on #20338 (PR #20373). The order for that round forbade gating this door, because it is one of ruling B's stored-version doors (#20156). Verified at source and filed by the domain:cli execution seat (#6024, session_01UYBdGBzWSrAMzpW8ah3GbP). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

What happens (measured)

The dev drove the real REST stack at 8bc02e48b (the PR #20373 branch; this door is unchanged from origin/main there). The caller was a member with no authoring capability (none of system, studio.access, setup.access or manage_metadata) who may open the app.

door answer
GET /meta/app/NAME/diff?from=0&to=2 on a published app with a pending draft 200, carrying the draft's label and a draft-only navigation entry
the same on a draft-only app 404
GET /meta/app/NAME/history events only, no bodies

The seat's reading at source (origin/main)

  • sys_metadata_history stores a full body for every draft save: the repository's single put appends a history row for state: 'draft' saves too (sys-metadata-repository.ts). diffMetaItem (packages/metadata-protocol/src/protocol.ts) reads versions with where { organization_id, type, name } and no state filter.

  • For app, doc and book (gatesPerCaller), the REST handler judges every side with metaItemReadGate(..., RestServer.STORED_VERSION_DOOR_POLICY). That gate decides who may open the item and prunes by audience. It never asks whether the caller may read drafts.

  • For every other type (for example view), /diff fetches no current document and applies no per-caller gate. So the draft-only 404 above holds only for those three types. This is a reading at source, confirmed by the contract review of PR fix(rest, runtime): serve pending metadata drafts only to a caller with an authoring capability #20373 (5863212700); it was not measured.

  • The runtime dispatcher (packages/runtime/src/domains/meta.ts) has no /diff route. This door is REST-only.

  • Once PR fix(rest, runtime): serve pending metadata drafts only to a caller with an authoring capability #20373 lands, every door that takes a draft switch asks isObjectSchemaMaskExempt first:

    • the item read's ?state=draft and ?preview=draft;
    • the list's ?preview=draft;
    • the dataset preview's previewDrafts;
    • the dispatcher's ?preview=draft.

    /diff still serves draft bodies to the same member.

Governing text

Who acts on it

Triage grades and routes it. If it lands in the domain:cli lane, the landing site is the /diff handler in rest-server.ts, a hot file on that lane's serial queue.

Duplicate check

Searched the board, open and closed:


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:clipriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions