From 321a9182edcbcabbb0d85dc45b771572a2cbdb60 Mon Sep 17 00:00:00 2001 From: "kolega.dev" Date: Mon, 9 Feb 2026 12:18:42 +0000 Subject: [PATCH] security: validate loginRedirect cookie to prevent open redirect The loginRedirect cookie value was used directly in res.redirect() and window.location.replace() without validation, allowing redirection to arbitrary external URLs. Added validation to ensure the redirect target is a relative path before use. --- client/components/login.vue | 14 ++++++++++---- server/controllers/auth.js | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/client/components/login.vue b/client/components/login.vue index bf9b26c22..0bbaa2a5e 100644 --- a/client/components/login.vue +++ b/client/components/login.vue @@ -644,16 +644,22 @@ export default { Cookies.set('jwt', respObj.jwt, { expires: 365, secure: window.location.protocol === 'https:' }) _.delay(() => { const loginRedirect = Cookies.get('loginRedirect') + const isValidRedirect = loginRedirect && loginRedirect.startsWith('/') && !loginRedirect.startsWith('//') && !loginRedirect.includes('://') if (loginRedirect === '/' && respObj.redirect) { Cookies.remove('loginRedirect') window.location.replace(respObj.redirect) - } else if (loginRedirect) { + } else if (isValidRedirect) { Cookies.remove('loginRedirect') window.location.replace(loginRedirect) - } else if (respObj.redirect) { - window.location.replace(respObj.redirect) } else { - window.location.replace('/') + if (loginRedirect) { + Cookies.remove('loginRedirect') + } + if (respObj.redirect) { + window.location.replace(respObj.redirect) + } else { + window.location.replace('/') + } } }, 1000) } diff --git a/server/controllers/auth.js b/server/controllers/auth.js index 7a947338d..733486be0 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -73,16 +73,22 @@ router.all('/login/:strategy/callback', async (req, res, next) => { res.cookie('jwt', authResult.jwt, commonHelper.getCookieOpts()) const loginRedirect = req.cookies['loginRedirect'] + const isValidRedirect = loginRedirect && loginRedirect.startsWith('/') && !loginRedirect.startsWith('//') && !loginRedirect.includes('://') if (loginRedirect === '/' && authResult.redirect) { res.clearCookie('loginRedirect') res.redirect(authResult.redirect) - } else if (loginRedirect) { + } else if (isValidRedirect) { res.clearCookie('loginRedirect') res.redirect(loginRedirect) - } else if (authResult.redirect) { - res.redirect(authResult.redirect) } else { - res.redirect('/') + if (loginRedirect) { + res.clearCookie('loginRedirect') + } + if (authResult.redirect) { + res.redirect(authResult.redirect) + } else { + res.redirect('/') + } } } catch (err) { next(err)