User tests: Successful: Unsuccessful:
One-character fix for the bug reported in #47828. Pass xhtml=false to Route::_('index.php') inside Session::checkToken() so the URL written to the HTTP Location: response header contains raw & rather than HTML-encoded &.
Session::checkToken('get') without a valid token. A widely-deployed reproducer is JCE Editor's editor.loadlanguages endpoint:
curl -ski "https://example.test/component/jce/?task=editor.loadlanguages&lang=en&context=1&profile_id=2"HTTP/1.1 303 See other
Location: https://example.test/component/jce/?task=editor.loadlanguages&lang=en&context=1&profile_id=2
— note literal & in Location. Each subsequent follow doubles the entity prefix.HTTP/1.1 303 See other
Location: https://example.test/component/jce/?task=editor.loadlanguages&lang=en&context=1&profile_id=2
— raw &, no chain.Verified against Joomla 6.1.0 (infinetinc.com production) — XOVI crawler errors for temporaryRedirects and serverErrors on the affected URLs dropped to zero on the next scan after this patch was applied (was 463 errors over 30 days pre-patch).
None required — Route::_()'s $xhtml parameter is already documented as "Replace & by & for XML compliance," which makes clear it shouldn't be used for HTTP Location headers.
libraries/src/Session/Session.php::checkToken() redirects when:
Route::_($url, $xhtml = true, ...) runs htmlspecialchars() on the URL when $xhtml=true. That output is passed straight to $app->redirect(), which writes it to the Location: header without entity-decoding. Per RFC 3986, ampersands in the URI of a Location header must be raw & — entity encoding belongs in HTML attribute contexts, not response headers.
Crawlers (notably XOVI) that don't decode HTML entities before following Location then GET the literal &-laden URL. PHP's parse_str treats &Itemid=101 as separator-& plus param-name-amp;Itemid. The next redirect rebuilds the query string from the polluted $_GET and re-runs htmlspecialchars() on it, so each hop multiplies the amp; prefix until the URL exceeds server limits and the chain terminates in 502s.
Browsers happen to tolerate this (they decode entities before re-issuing requests), which is likely why this has gone unreported in the 14 years since Session::checkToken was introduced.
This patch is against 5.4-dev per Joomla's lowest-maintained-branch convention; the same single-line change applies cleanly to 6.1-dev, 6.2-dev, and 7.0-dev (verified all four Session.php files still contain the bare Route::_('index.php') call as of 2026-05-25). Happy to open follow-up PRs against the other release branches if upmerge doesn't pick it up.
| Status | New | ⇒ | Pending |
| Category | ⇒ | Libraries |
Hi @tadosborn, thank you for your first time contribution.
Could you please use this template?
Pull Request resolves # .
- [ ] I read the [Generative AI policy](https://developer.joomla.org/generative-ai-policy.html) and my contribution is either not created with the help of AI or is compatible with the policy and GNU/GPL 2 or later.
### Summary of Changes
### Testing Instructions
### Actual result BEFORE applying this Pull Request
### Expected result AFTER applying this Pull Request
### Link to documentations
Please select:
- [ ] Documentation link for guide.joomla.org: <link>
- [ ] No documentation changes for guide.joomla.org needed
- [ ] Pull Request link for manual.joomla.org: <link>
- [ ] No documentation changes for manual.joomla.org neededYou can keep the similar sections, but you should add the missing ones.
@tadosborn In addition to the above, please reference your issue #47829 in this pull request with the Pull Request resolves #47829 . line at the top.
@tadosborn Please confirm the Generative AI policy at the top of your pull request. That is part of the pull request template which you see when creating a new pull request, and you should not have removed that. Thanks in advance.