Skip to content

#17376 - Redirect admin requests whose path has no site - #14746

Open
skikken wants to merge 2 commits into
WordPress:trunkfrom
skikken:fix/17376-subfolder-admin-path
Open

skikken wants to merge 2 commits into
WordPress:trunkfrom
skikken:fix/17376-subfolder-admin-path

Conversation

@skikken

@skikken skikken commented Oct 22, 2026

Copy link
Copy Markdown

On a subfolder network, requesting a path that has no site behind it still resolves to a site, because get_site_by_path() walks back through shorter paths and finally tries /. The consequence for admin requests is that foo.org/typo/wp-admin/ loads the main site's dashboard while the URL keeps saying /typo/. Nothing indicates the requested site does not exist, so a typo silently lands you in a different site's admin, and the reporter changed the wrong site that way. It is also convenient for scanners, which can reach a real dashboard on an unlimited number of paths.

The bug is still present on trunk. The bootstrap already handles the case where no site can be found at all: it redirects to the network path. What it does not handle is the case where a site is found at a different path than the one requested.

This adds that case to ms_load_current_site_and_network(). When the request is for the admin area and the resolved site's path does not match the requested path, the function returns a destination string for the site that was actually found, and ms-settings.php sends it as a redirect. foo.org/typo/wp-admin/ therefore lands on foo.org/wp-admin/, and foo.org/bar/typo/wp-admin/ lands on foo.org/bar/wp-admin/ rather than serving /bar/ at the bogus path.

Three details worth noting for review:

  • The destination is built from $current_blog->path, the site actually found, not from the requested path. That is what makes the URL afterwards match the site being served.
  • Only is_admin() requests are touched. A front end request to /typo/ keeps resolving to the main site as it does today, so this does not change front end behaviour.
  • The redirect cannot loop: the next request's path equals the resolved site's path, so the condition is false on the second pass.

A filter, ms_site_path_mismatch_redirect, exposes the destination.

The alternative of teaching get_site_by_path() about admin requests was raised during the ticket's history and rejected in comment #19, since it would put global state into a general purpose utility. The check here stays in the bootstrap where the redirect decision is already made.

Trac ticket: https://core.trac.wordpress.org/ticket/17376

Use of AI Tools

AI assistance: Yes
Tool(s): Claude
Model(s): Sonnet
Used for: test cases, code review


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

On a subfolder network the site lookup falls back to shorter paths, so a
request for a path that does not belong to any site still resolves to
one. Requesting /typo/wp-admin/ therefore loaded the main site's
dashboard while the URL kept saying /typo/, which reads as though the
requested site exists and makes it easy to edit the wrong site.

When an admin request resolves to a site whose path does not match the
requested path, send it to the admin URL of the site that was actually
found. Front end requests are untouched, and a request that does match
its site is never redirected, so the next request settles.

Props MadtownLems, ddean, nacin, jeremyfelt, Denis-de-Bernardy, ocean90.
Fixes #17376.
Cover that an admin request whose path resolves to a different site is
redirected to the found site's admin URL, that a matching path is not
redirected, that front end requests are unaffected, and that the
destination is filterable.

Props MadtownLems, ddean, nacin, jeremyfelt, Denis-de-Bernardy, ocean90.
Fixes #17376.
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props whopiyush.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

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.

1 participant