#108 established that a caller may read a site's docs only if they may read the site's catalog entity, enforced in rw-backend's site middleware for every /site/:namespace/:kind/:name/* route.
Two routes sit outside that prefix and are therefore ungated by it, because both are global aggregates rather than site-scoped:
GET /pages/latest (latestChanges/latestChangesRouter.ts:29) — the changes feed. Returns site_ref, section_path, subpath and page titles for every indexed site, to any authenticated caller. Gated on nothing site-related.
GET /comments/inbox (inbox/inboxRouter.ts:38) — the comment inbox. Gated on rwCommentReadPermission only, then aggregates comment activity (which carries page titles and paths) across every site.
Neither serves page bodies, so this is a metadata leak, not a content leak — and both predate #108. But under the settled access model they are inconsistent: a caller who gets a 404 from /site/default/component/secret/pages/... can still learn that site's page titles and structure from the feed.
Fix: filter both by the caller's catalogEntityReadPermission on each row's site_ref. PermissionsService.authorize takes a batch, so one call per request with one entry per distinct site ref is enough. Note the feeds are keyset-paginated, so the filter must be pushed into the query (as a site-ref allowlist) rather than applied to the page after it is fetched, or page sizes will be wrong.
Surfaced while implementing #108. Related: #113 (aligning search's filter granularity to the same rule).
#108 established that a caller may read a site's docs only if they may read the site's catalog entity, enforced in rw-backend's site middleware for every
/site/:namespace/:kind/:name/*route.Two routes sit outside that prefix and are therefore ungated by it, because both are global aggregates rather than site-scoped:
GET /pages/latest(latestChanges/latestChangesRouter.ts:29) — the changes feed. Returnssite_ref,section_path,subpathand page titles for every indexed site, to any authenticated caller. Gated on nothing site-related.GET /comments/inbox(inbox/inboxRouter.ts:38) — the comment inbox. Gated onrwCommentReadPermissiononly, then aggregates comment activity (which carries page titles and paths) across every site.Neither serves page bodies, so this is a metadata leak, not a content leak — and both predate #108. But under the settled access model they are inconsistent: a caller who gets a 404 from
/site/default/component/secret/pages/...can still learn that site's page titles and structure from the feed.Fix: filter both by the caller's
catalogEntityReadPermissionon each row'ssite_ref.PermissionsService.authorizetakes a batch, so one call per request with one entry per distinct site ref is enough. Note the feeds are keyset-paginated, so the filter must be pushed into the query (as a site-ref allowlist) rather than applied to the page after it is fetched, or page sizes will be wrong.Surfaced while implementing #108. Related: #113 (aligning search's filter granularity to the same rule).