Summary
WP_Ultimo\Domain_Mapping::allow_network_redirect_hosts() adds port-suffixed
entries (e.g. main.test:8080) to the allowed_redirect_hosts list, but
WordPress core's wp_validate_redirect() strips the port from the redirect
target's host before comparison. Strict equality fails, wp_safe_redirect()
falls back to the safe-default URL, and cross-domain SSO redirects from a
mapped subsite to the main site silently fail when either domain runs on a
non-standard port.
Reproduction
- Multisite install on a non-standard port (e.g.
DOMAIN_CURRENT_SITE = 'main.test:8080').
- Subsite served on a different host (mapped, or a row in
wp_blogs with a
different domain), still on :8080.
- As an unauthenticated user, hit
http://mapped.test:8080/wp-admin/.
Expected: SSO redirect chain bounces the browser to
http://main.test:8080/wp-login.php?sso=login&return_url=...&redirect_to=....
Actual: wp_safe_redirect() rejects the SSO URL because
wp_validate_redirect() compares the parsed host (main.test, no port) against
the allowed entry (main.test:8080, with port). The fallback URL is used and
the browser ends up redirecting to its own subsite admin, never reaching the
main-site login.
Evidence
wp_safe_redirect() calls wp_validate_redirect( $location, $default ). Inside
that function (wp-includes/pluggable.php), the host is extracted with
wp_parse_url( $location, PHP_URL_HOST ) — port stripped. The comparison is
strict:
if ( in_array( $host, (array) $allowed_hosts, true ) ) {
return $location;
}
allow_network_redirect_hosts() (inc/class-domain-mapping.php ~line 222)
adds the request $host as-is, including any :port suffix carried from
HTTP_HOST. WordPress core never strips ports for that filter, so the
allow-list ends up containing main.test:8080 and the comparison fails for
main.test.
Trace from a live test (debug mu-plugin, real domains redacted):
[debug-redirects] FALLBACK fired host=mapped.test:8080 uri=/wp-admin/
fallback=http://mapped.test:8080/wp-admin/
[debug-redirects] allowed_redirect_hosts called host=main.test
allowed=["main.test:8080"]
[debug-redirects] HOST=mapped.test:8080 URI=/wp-admin/
-> http://mapped.test:8080/wp-admin/ (status=302)
Suggested fix
allow_network_redirect_hosts() should add both the port-suffixed and
port-stripped versions of any host it allows. Sketch:
public function allow_network_redirect_hosts($allowed_hosts, $host) {
if (empty($host)) {
return $allowed_hosts;
}
$host = strtolower($host);
$host_no_port = preg_replace('/:\d+$/', '', $host);
// ...existing lookup logic, but on every successful match push BOTH:
// $allowed_hosts[] = $host; // "main.test:8080"
// $allowed_hosts[] = $host_no_port; // "main.test"
}
Alternatively, when registering the filter, also normalise existing entries:
walk the allowed list once and append a port-stripped version of any
port-suffixed host.
Workaround
A small mu-plugin that walks allowed_redirect_hosts and adds port-stripped
versions of any port-suffixed entry is sufficient for dev environments:
add_filter('allowed_redirect_hosts', function ($hosts) {
foreach ((array) $hosts as $h) {
if (strpos($h, ':') !== false) {
$stripped = explode(':', $h, 2)[0];
if ($stripped && ! in_array($stripped, $hosts, true)) {
$hosts[] = $stripped;
}
}
}
return array_values(array_unique($hosts));
}, 99);
Without this, every cross-domain SSO redirect from a mapped subsite on a
non-standard port silently falls back to the same-host fallback URL, defeating
the SSO flow.
Impact
Surfaces only when:
- The network or mapped domain runs on a non-standard port, AND
- Cross-domain SSO redirects are exercised (
handle_auth_redirect,
handle_login_redirect, handle_already_logged_in_on_login_page, or any
other code path emitting a cross-host wp_safe_redirect).
Production installs on :80/:443 are unaffected because WordPress core
never carries those ports through to the host-comparison step. Most-impacted
users are local-dev setups testing the new cookie-less SSO flow added in #1086
and #1088.
Related
aidevops.sh v3.14.52 plugin for OpenCode v1.14.33 with claude-sonnet-4-6 spent 17h 17m on this as a headless worker.
Summary
WP_Ultimo\Domain_Mapping::allow_network_redirect_hosts()adds port-suffixedentries (e.g.
main.test:8080) to theallowed_redirect_hostslist, butWordPress core's
wp_validate_redirect()strips the port from the redirecttarget's host before comparison. Strict equality fails,
wp_safe_redirect()falls back to the safe-default URL, and cross-domain SSO redirects from a
mapped subsite to the main site silently fail when either domain runs on a
non-standard port.
Reproduction
DOMAIN_CURRENT_SITE = 'main.test:8080').wp_blogswith adifferent
domain), still on:8080.http://mapped.test:8080/wp-admin/.Expected: SSO redirect chain bounces the browser to
http://main.test:8080/wp-login.php?sso=login&return_url=...&redirect_to=....Actual:
wp_safe_redirect()rejects the SSO URL becausewp_validate_redirect()compares the parsed host (main.test, no port) againstthe allowed entry (
main.test:8080, with port). The fallback URL is used andthe browser ends up redirecting to its own subsite admin, never reaching the
main-site login.
Evidence
wp_safe_redirect()callswp_validate_redirect( $location, $default ). Insidethat function (
wp-includes/pluggable.php), the host is extracted withwp_parse_url( $location, PHP_URL_HOST )— port stripped. The comparison isstrict:
allow_network_redirect_hosts()(inc/class-domain-mapping.php~line 222)adds the request
$hostas-is, including any:portsuffix carried fromHTTP_HOST. WordPress core never strips ports for that filter, so theallow-list ends up containing
main.test:8080and the comparison fails formain.test.Trace from a live test (debug mu-plugin, real domains redacted):
Suggested fix
allow_network_redirect_hosts()should add both the port-suffixed andport-stripped versions of any host it allows. Sketch:
Alternatively, when registering the filter, also normalise existing entries:
walk the allowed list once and append a port-stripped version of any
port-suffixed host.
Workaround
A small mu-plugin that walks
allowed_redirect_hostsand adds port-strippedversions of any port-suffixed entry is sufficient for dev environments:
Without this, every cross-domain SSO redirect from a mapped subsite on a
non-standard port silently falls back to the same-host fallback URL, defeating
the SSO flow.
Impact
Surfaces only when:
handle_auth_redirect,handle_login_redirect,handle_already_logged_in_on_login_page, or anyother code path emitting a cross-host
wp_safe_redirect).Production installs on
:80/:443are unaffected because WordPress corenever carries those ports through to the host-comparison step. Most-impacted
users are local-dev setups testing the new cookie-less SSO flow added in #1086
and #1088.
Related
template_redirecthook for custom login pages andredirect_topropagation in the cookie-less flow. The flow itself iscorrect; this redirect-host validation is what blocks it on non-standard
ports.
aidevops.sh v3.14.52 plugin for OpenCode v1.14.33 with claude-sonnet-4-6 spent 17h 17m on this as a headless worker.