fix(#5578): stop login next= param from nesting into itself and exploding the URL

An expired-auth 401 on the login page fed the login redirect its own address:
each of the three redirect guards (_safeNextPath in login.js,
_safe_login_redirect_path in routes.py, the api() 401 redirect in workspace.js,
_safe_login_inner_next in auth.py) validated against open-redirect but none
rejected a `next` pointing back at the login page, so every auth bounce wrapped +
re-encoded the previous login URL one level deeper — growing the URL exponentially
until the browser's length limit broke the tab. All guards now collapse/reject a
`next` whose decoded leading PATH resolves to the login route (bounded
percent-decoding up to 8 levels, fail-closed at the cap), while preserving the
existing open-redirect protections AND legitimate non-login paths that merely
carry their own `next=` query key. Length cap as belt-and-suspenders.

Self-built (nesquena-hermes); nesquena independent review APPROVED. Gate (Codex)
found + fixed an over-broad nested-next reject (regressed next-carrying paths) and
a deep-encode decode off-by-one; both root-fixed, login guards verified clean.
This commit is contained in:
nesquena-hermes committed 2026-07-05 00:12:19 +00:00
1 parent 1f68c8cfa5
commit e09e819f08
7 files changed
+293 -3

No files matched your search

+4
View File
@@ -3071,6 +3071,10 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer;
return true;
};
const redirectToLogin=(nextUrl)=>{
// #5578: never nest the login URL into its own next= — if already on a
// login-shaped page, reload 'login' bare (the page keeps its inner next).
const _p=(window.location.pathname||'').replace(/\/+$/,'');
if(/(?:^|\/)login$/.test(_p)){window.location.href='login';return;}
window.location.href='login?next='+encodeURIComponent(nextUrl);
};
return {