From 90cf286a09fa682dc6b2a9ddd72d88c60a720352 Mon Sep 17 00:00:00 2001 From: "shaggy (mosaic-dev box)" Date: Sun, 9 Aug 2026 20:43:27 -0500 Subject: [PATCH] fix(web): reject protocol-relative auth callbacks --- apps/web/src/lib/auth-redirect.spec.ts | 5 +++++ apps/web/src/lib/auth-redirect.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/web/src/lib/auth-redirect.spec.ts b/apps/web/src/lib/auth-redirect.spec.ts index ba7170b2..2dca7ff8 100644 --- a/apps/web/src/lib/auth-redirect.spec.ts +++ b/apps/web/src/lib/auth-redirect.spec.ts @@ -14,6 +14,11 @@ describe('resolveAuthCallbackURL', () => { null, 'chat', '//evil.example', + '/..//evil.com', + '/..//evil.com/x', + '/./..//evil.com', + '/../..//evil.com', + '/foo/..//evil.com', '/\\evil.example', '/\n//evil.example', '/\r//evil.example', diff --git a/apps/web/src/lib/auth-redirect.ts b/apps/web/src/lib/auth-redirect.ts index 74b24d0b..e9725ac0 100644 --- a/apps/web/src/lib/auth-redirect.ts +++ b/apps/web/src/lib/auth-redirect.ts @@ -12,7 +12,9 @@ export function resolveAuthCallbackURL(candidate: string | null, currentOrigin: try { const expectedOrigin = new URL(currentOrigin).origin; const resolved = new URL(candidate, expectedOrigin); - if (resolved.origin !== expectedOrigin) return DEFAULT_AUTH_CALLBACK_URL; + if (resolved.origin !== expectedOrigin || resolved.pathname.startsWith('//')) { + return DEFAULT_AUTH_CALLBACK_URL; + } return `${resolved.pathname}${resolved.search}${resolved.hash}`; } catch {