Skip to content

Close the username completion and the actions that resolve an account - #27

Closed
JeroenDeDauw wants to merge 1 commit into
masterfrom
fix/username-completion-and-account-actions
Closed

Close the username completion and the actions that resolve an account#27
JeroenDeDauw wants to merge 1 commit into
masterfrom
fix/username-completion-and-account-actions

Conversation

@JeroenDeDauw

@JeroenDeDauw JeroenDeDauw commented Sep 1, 2026

Copy link
Copy Markdown
Member

Fixes #24
Follows-up to #23

Two paths named accounts to members past UserListSpecialPageHandler and UserListApiHandler: username
completion, which bypasses SpecialPage::run(), and top-level API actions, which the API handler never
looked at.

Username completion. The core pages that complete a username — eight in 1.43, Special:Contributions
and Special:UserRights among them — and the extension pages that do the same all declare the
UserNamePrefixSearch service in their special-page list entry and ask it on behalf of the public, paging
through the whole user table; action=opensearch, list=prefixsearch, generator=prefixsearch and
GET /rest.php/v1/search/title, which the search box calls, all arrive there. The extension now rewrites,
in SpecialPage_initList, every entry that declares the service, so the page is built with a wrapper that
answers members with nothing and hands every other search on. Decorating the service from the
MediaWikiServices hook is not an option: that hook never reaches extensions under ProAdmin's loader.php
bootstrap (https://github.com/ProfessionalWiki/ProAdmin/issues/1375). On the dev wiki, under that
bootstrap, all four entry points now return nothing to a member and the names to an ordinary account. Since the
answer now differs by reader, $wgSearchSuggestCacheExpiry is set to 0 on load; it is what marks
action=opensearch and the REST title search publicly cacheable.

API actions. $wgMemberAccessBlockedApiModules, closed to members as before, now refuses top-level
actions as well and ships four: feedcontributions, validatepassword, emailuser and userrights,
each of which names an account or confirms one before any right or token is checked (feedcontributions
and userrights also resolve user=#<id>). A wiki that set the variable gets them too, since array
settings merge. Members lose only the contributions feed: sendemail and createaccount are revoked
already, a member has no password to validate, and no group to grant.

Left open, on purpose: Special:Contributions/<name> and Special:ListFiles/<name> still tell a member
that a name has no account. Each confirms one name at a time, and closing the pages would cost members every
contribution listing and the list of files; a wiki that wants them closed adds them to
$wgMemberAccessBlockedSpecialPages. Whom the roster is hidden from at all is
#26. The #<id> existence answers that remain —
in modules that return no name, and in userrights token validation, which runs before the hook and
answers for a name as well — are
#28.

Considered, omitted: limiting Special:Contributions to the member's own account; naming the completing
pages in the README.

AI-authored — Claude Code, Opus 5 (max) diff from a Fable 5 (max) spec for @JeroenDeDauw, redirected once when the first mechanism proved inert under the ProAdmin bootstrap, then adjusted after an independent review pass; diff not yet human-reviewed; both leaks reproduced on a dev wiki before the fix and seen closed after it under that bootstrap, regression tests written first and seen failing, mutations of the wrapper, the page-list rewrite, the wiring and the API handler each caught, every special page on the dev wiki built as member, ordinary account and anonymous visitor, PHPCS, PHPStan and the whole suite run locally, CI green.

@codecov-commenter

codecov-commenter commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.98%. Comparing base (d05e3ee) to head (edbd507).

Files with missing lines Patch % Lines
src/EntryPoints/UserNameCompletionHandler.php 94.28% 2 Missing ⚠️
src/EntryPoints/RestrictedUserNamePrefixSearch.php 87.50% 1 Missing ⚠️
src/EntryPoints/UserListApiHandler.php 87.50% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master      #27      +/-   ##
============================================
+ Coverage     86.77%   86.98%   +0.21%     
- Complexity      594      618      +24     
============================================
  Files            67       69       +2     
  Lines          2072     2129      +57     
============================================
+ Hits           1798     1852      +54     
- Misses          274      277       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@JeroenDeDauw
JeroenDeDauw force-pushed the fix/username-completion-and-account-actions branch from 786c19c to 867c304 Compare September 1, 2026 14:22
@JeroenDeDauw
JeroenDeDauw force-pushed the fix/username-completion-and-account-actions branch from 867c304 to 4d0bfd8 Compare September 1, 2026 15:25
Fixes #24
Follows-up to #23

Two paths named accounts to members past `UserListSpecialPageHandler` and `UserListApiHandler`: username
completion, which bypasses `SpecialPage::run()`, and top-level API actions, which the API handler never
looked at.

**Username completion.** The core pages that complete a username — eight in 1.43, `Special:Contributions`
and `Special:UserRights` among them — and the extension pages that do the same all declare the
`UserNamePrefixSearch` service in their special-page list entry and ask it on behalf of the public, paging
through the whole `user` table; `action=opensearch`, `list=prefixsearch`, `generator=prefixsearch` and
`GET /rest.php/v1/search/title`, which the search box calls, all arrive there. The extension now rewrites,
in `SpecialPage_initList`, every entry that declares the service, so the page is built with a wrapper that
answers members with nothing and hands every other search on. Decorating the service from the
`MediaWikiServices` hook is not an option: that hook never reaches extensions under ProAdmin's `loader.php`
bootstrap (ProfessionalWiki/ProAdmin#1375). On the dev wiki, under that
bootstrap, all four entry points now return nothing to a member and the names to an ordinary account. Since the
answer now differs by reader, `$wgSearchSuggestCacheExpiry` is set to `0` on load; it is what marks
`action=opensearch` and the REST title search publicly cacheable.

**API actions.** `$wgMemberAccessBlockedApiModules`, closed to members as before, now refuses top-level
actions as well and ships four: `feedcontributions`, `validatepassword`, `emailuser` and `userrights`,
each of which names an account or confirms one before any right or token is checked (`feedcontributions`
and `userrights` also resolve `user=#<id>`). A wiki that set the variable gets them too, since array
settings merge. Members lose only the contributions feed: `sendemail` and `createaccount` are revoked
already, a member has no password to validate, and no group to grant.

Left open, on purpose: `Special:Contributions/<name>` and `Special:ListFiles/<name>` still tell a member
that a name has no account. Each confirms one name at a time, and closing the pages would cost members every
contribution listing and the list of files; a wiki that wants them closed adds them to
`$wgMemberAccessBlockedSpecialPages`. Whom the roster is hidden from at all is
#26. The `#<id>` existence answers that remain —
in modules that return no name, and in `userrights` token validation, which runs before the hook and
answers for a name as well — are
#28.

Considered, omitted: limiting `Special:Contributions` to the member's own account; naming the completing
pages in the README.

> <sub>AI-authored — Claude Code, `Opus 5 (max)` diff from a `Fable 5 (max)` spec for @JeroenDeDauw, redirected once when the first mechanism proved inert under the ProAdmin bootstrap, then adjusted after an independent review pass; diff not yet human-reviewed; both leaks reproduced on a dev wiki before the fix and seen closed after it under that bootstrap, regression tests written first and seen failing, mutations of the wrapper, the page-list rewrite, the wiring and the API handler each caught, every special page on the dev wiki built as member, ordinary account and anonymous visitor, PHPCS, PHPStan and the whole suite run locally, CI green.</sub>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JeroenDeDauw
JeroenDeDauw force-pushed the fix/username-completion-and-account-actions branch from 4d0bfd8 to edbd507 Compare September 1, 2026 15:36
@JeroenDeDauw

Copy link
Copy Markdown
Member Author

Might not do this one, certainly lower priority due to removing the emails from usernames (#29), which removes the main incentive for this PR

@JeroenDeDauw

Copy link
Copy Markdown
Member Author

Closing unmerged: #29 removed the identity behind every name these paths could return — username completion and the ID walks now yield Member ABC123 forms. What this branch still hides is that members exist and how many, which no requirement asks for and which the merged defences already narrow. The branch stays in place should that ever change.

AI-authored — Claude Code, Fable 5 (max); close requested by @JeroenDeDauw in the post-merge cleanup.

@JeroenDeDauw
JeroenDeDauw deleted the fix/username-completion-and-account-actions branch September 2, 2026 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Account enumeration paths that do not go through SpecialPage::run() or ApiQuery

2 participants