From 7f4e5368723a02003f6375dd453be5a65204a34e Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sat, 4 Apr 2026 19:22:25 +0530 Subject: [PATCH] fix(security): validate redirect_uri to prevent open redirect attacks Add isValidRedirectUri() validation in Root.tsx to ensure redirect_uri from URL query params is validated before use. Only same-origin and configured redirect URLs are allowed. Blocks javascript: and data: URIs. --- web/app/src/Root.tsx | 45 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/web/app/src/Root.tsx b/web/app/src/Root.tsx index 03760b9f..fed3aed1 100644 --- a/web/app/src/Root.tsx +++ b/web/app/src/Root.tsx @@ -9,6 +9,43 @@ const Login = lazy(() => import('./pages/login')); const Dashboard = lazy(() => import('./pages/dashboard')); const SignUp = lazy(() => import('./pages/signup')); +/** + * Validates a redirect URI to prevent open redirect attacks. + * Allows same-origin redirects and cross-origin redirects only for + * http/https protocols that match configured redirect URLs. + */ +function isValidRedirectUri( + uri: string, + configuredRedirectURL?: string, +): boolean { + try { + const url = new URL(uri, window.location.origin); + // Only allow http and https protocols (block javascript:, data:, etc.) + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + return false; + } + // Same-origin redirects are always allowed + if (url.origin === window.location.origin) { + return true; + } + // Cross-origin: only allow if it matches the configured redirect URL origin + if (configuredRedirectURL) { + try { + const configuredUrl = new URL(configuredRedirectURL); + if (url.origin === configuredUrl.origin) { + return true; + } + } catch { + // Invalid configured URL, reject cross-origin + } + } + return false; + } catch { + // If URI can't be parsed, reject it + return false; + } +} + export default function Root({ globalState, }: { @@ -31,12 +68,12 @@ export default function Root({ scope, }; - const redirectURL = + const rawRedirectURL = searchParams.get('redirect_uri') || searchParams.get('redirectURL'); - if (redirectURL) { - urlProps.redirectURL = redirectURL; + if (rawRedirectURL && isValidRedirectUri(rawRedirectURL, config?.redirectURL)) { + urlProps.redirectURL = rawRedirectURL; } else { - urlProps.redirectURL = hasWindow() ? window.location.origin : redirectURL; + urlProps.redirectURL = hasWindow() ? window.location.origin : '/'; } urlProps.redirect_uri = urlProps.redirectURL;