Surfaced during the #1046 review (PR #1058), out of scope there (read side). The page-id-keyed Subject write endpoints are themselves a page-existence oracle over restricted pages — the mirror image of the read-side oracle #1046 closes.
A user holding the wiki-global edit right (the default for ordinary editors) who POSTs to POST /page/{pageId}/mainSubject (or the child/ordering variants) gets:
403 for an existing page they may not edit (e.g. read/edit-restricted via an ACL extension) — authorizeWrite('edit', $title) fails, correct.
201 Created (a no-op) for a nonexistent page id — AuthorityBasedSubjectAuthorizer::canEditPage/authorize hits the null-title branch and falls back to isAllowed('edit') (a global right the user has), the save no-ops on the unresolvable page, and MediaWikiSubjectRepository::saveContent swallows the "Page not found" ERROR status (there's a // TODO: expose failure information there) so the handler still presentCreateds.
So 403 vs 201 distinguishes "exists but hidden" from "does not exist" across sequential page ids — a sweepable existence oracle. The global-edit fallback is a deliberate fail-closed floor for writes (it can't bypass protection because the write is a no-op), so the fix is likely to distinguish the two null cases: a genuinely unresolvable page id should not answer 201. Needs the swallowed save-failure to be surfaced (the existing TODO) so the handler can 404 a write to a nonexistent page.
Split out of the #1046 review (PR #1058). Directed by @alistair3149.
Written by Claude Code, Fable 5
Surfaced during the #1046 review (PR #1058), out of scope there (read side). The page-id-keyed Subject write endpoints are themselves a page-existence oracle over restricted pages — the mirror image of the read-side oracle #1046 closes.
A user holding the wiki-global
editright (the default for ordinary editors) who POSTs toPOST /page/{pageId}/mainSubject(or the child/ordering variants) gets:403for an existing page they may not edit (e.g. read/edit-restricted via an ACL extension) —authorizeWrite('edit', $title)fails, correct.201 Created(a no-op) for a nonexistent page id —AuthorityBasedSubjectAuthorizer::canEditPage/authorizehits the null-title branch and falls back toisAllowed('edit')(a global right the user has), the save no-ops on the unresolvable page, andMediaWikiSubjectRepository::saveContentswallows the "Page not found" ERROR status (there's a// TODO: expose failure informationthere) so the handler stillpresentCreateds.So
403vs201distinguishes "exists but hidden" from "does not exist" across sequential page ids — a sweepable existence oracle. The global-editfallback is a deliberate fail-closed floor for writes (it can't bypass protection because the write is a no-op), so the fix is likely to distinguish the two null cases: a genuinely unresolvable page id should not answer201. Needs the swallowed save-failure to be surfaced (the existing TODO) so the handler can 404 a write to a nonexistent page.