Skip to content

Commit 167447f

Browse files
committed
fix(shared): scope portless redirect fallback to glob entries only
Exact string entries in allowedRedirectOrigins (e.g. https://www.clerk.com) were also matching ports they never declared because the portless-origin test ran against all patterns. Port is part of the browser origin, so an exact entry must remain port-sensitive. The portless fallback now only applies when the original entry is a glob (contains '*'), which covers the satellite-app use case where the pattern is https://*.example.net and the dev server runs on :5173.
1 parent 9a32372 commit 167447f

2 files changed

Lines changed: 15 additions & 14 deletions

File tree

packages/shared/src/internal/clerk-js/__tests__/url.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,10 +580,13 @@ describe('isAllowedRedirect', () => {
580580
['//evil.com', [/https:\/\/www\.clerk\.com/], false],
581581
['..//evil.com', ['https://www.clerk.com'], false],
582582
// non-default ports — satellite apps in local dev (e.g. Vite on :5173)
583-
['https://www.clerk.com:3000', ['https://www.clerk.com'], true],
584-
['https://www.clerk.com:3000/path?q=1', ['https://www.clerk.com'], true],
583+
// wildcard glob entries accept any port on the matching host
585584
['https://app.example.net:5173', ['https://*.example.net'], true],
586585
['https://app.example.net:5176', ['https://*.example.net'], true],
586+
['https://www.clerk.com:3000', ['https://*.clerk.com'], true],
587+
// exact string entries remain port-sensitive (port is part of the origin)
588+
['https://www.clerk.com:3000', ['https://www.clerk.com'], false],
589+
['https://www.clerk.com:3000/path?q=1', ['https://www.clerk.com'], false],
587590
// non-default port must not bypass domain validation
588591
['https://evil.com:5173', ['https://*.clerk.com'], false],
589592
['https://evil.clerk.com.evil.com:5173', ['https://*.clerk.com'], false],

packages/shared/src/internal/clerk-js/url.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -436,24 +436,22 @@ export const isAllowedRedirect =
436436

437437
const isSameOrigin = currentOrigin === url.origin;
438438

439-
const patterns = allowedRedirectOrigins.map(origin =>
440-
typeof origin === 'string' ? globs.toRegexp(trimTrailingSlash(origin)) : origin,
441-
);
442-
443439
// When the redirect URL includes a non-default port (common in local dev, e.g. :5173),
444-
// url.origin includes the port (https://app.example.net:5173) while allowedRedirectOrigins
445-
// patterns are typically port-less (https://*.example.net). Test both forms so that satellite
446-
// apps running on custom ports are not incorrectly rejected.
440+
// url.origin includes the port (https://app.example.net:5173) while glob entries like
441+
// https://*.example.net have none. For wildcard/glob entries only, also test the
442+
// port-stripped origin so satellite apps on custom ports are not rejected.
443+
// Exact string entries (no wildcard) remain port-sensitive to preserve the security boundary.
447444
const portlessOrigin = url.port !== '' ? `${url.protocol}//${url.hostname}` : null;
448445

449446
const isAllowed =
450447
!isProblematicUrl(url) &&
451448
(isSameOrigin ||
452-
patterns.some(
453-
pattern =>
454-
pattern.test(trimTrailingSlash(url.origin)) ||
455-
(portlessOrigin !== null && pattern.test(portlessOrigin)),
456-
));
449+
allowedRedirectOrigins.some(origin => {
450+
const pattern = typeof origin === 'string' ? globs.toRegexp(trimTrailingSlash(origin)) : origin;
451+
if (pattern.test(trimTrailingSlash(url.origin))) return true;
452+
const isGlobEntry = typeof origin === 'string' && origin.includes('*');
453+
return isGlobEntry && portlessOrigin !== null && pattern.test(portlessOrigin);
454+
}));
457455

458456
if (!isAllowed) {
459457
logger.warnOnce(

0 commit comments

Comments
 (0)