From 8889a50ec090780053d9d6f307e6b2321ba96baf Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 04:14:00 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=94=92=20prevent=20open=20redirect?= =?UTF-8?q?=20in=20login=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate 'loginFrom' parameter to ensure it is a safe internal path before navigating. This prevents attackers from redirecting users to malicious external websites after login. Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com> --- src/services/auth.ts | 5 ++++- src/utils/helper.test.ts | 35 ++++++++++++++++++++++++++++++++++- src/utils/helper.ts | 6 ++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/services/auth.ts b/src/services/auth.ts index 60359c8..050679d 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -4,6 +4,7 @@ import { md5 } from 'hash-wasm'; import { rootRouterPath, router } from '@/router'; import { api } from '@/services/api'; import { setToken } from '@/services/request'; +import { isSafeRedirect } from '@/utils/helper'; let _email = ''; export const setUserEmail = (email: string) => { @@ -23,7 +24,9 @@ export async function login(email: string, password: string) { const loginFrom = new URLSearchParams(window.location.search).get( 'loginFrom', ); - router.navigate(loginFrom || rootRouterPath.user); + router.navigate( + isSafeRedirect(loginFrom) ? loginFrom! : rootRouterPath.user, + ); } } catch (err) { const e = err as Error; diff --git a/src/utils/helper.test.ts b/src/utils/helper.test.ts index 8068340..d034fe7 100644 --- a/src/utils/helper.test.ts +++ b/src/utils/helper.test.ts @@ -1,5 +1,38 @@ import { describe, expect, test } from 'bun:test'; -import { isExpVersion } from './helper'; +import { isExpVersion, isSafeRedirect } from './helper'; + +describe('isSafeRedirect', () => { + test('should return true for internal paths', () => { + expect(isSafeRedirect('/dashboard')).toBe(true); + expect(isSafeRedirect('/user/profile')).toBe(true); + expect(isSafeRedirect('/')).toBe(true); + }); + + test('should return false for empty or nullish values', () => { + expect(isSafeRedirect(null)).toBe(false); + expect(isSafeRedirect(undefined)).toBe(false); + expect(isSafeRedirect('')).toBe(false); + }); + + test('should return false for external URLs', () => { + expect(isSafeRedirect('https://google.com')).toBe(false); + expect(isSafeRedirect('http://malicious.com')).toBe(false); + expect(isSafeRedirect('ftp://server.com')).toBe(false); + }); + + test('should return false for protocol-relative URLs', () => { + expect(isSafeRedirect('//google.com')).toBe(false); + }); + + test('should return false for backslash-prefixed paths (potential bypass)', () => { + expect(isSafeRedirect('/\\google.com')).toBe(false); + }); + + test('should return false for paths that do not start with /', () => { + expect(isSafeRedirect('user/profile')).toBe(false); + expect(isSafeRedirect('javascript:alert(1)')).toBe(false); + }); +}); describe('isExpVersion', () => { test('should return false when config is null', () => { diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 51064a9..0fc0a11 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -66,6 +66,12 @@ export const testUrls = async (urls?: string[]) => { return urls[0]; }; +export const isSafeRedirect = (url: string | null | undefined): boolean => { + if (!url) return false; + // Ensure it starts with / and not // or \ + return url.startsWith('/') && !url.startsWith('//') && !url.startsWith('/\\'); +}; + export const isExpVersion = ( config: VersionConfig | null | undefined, packageVersion: string,