Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/blue-pumpkins-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/clerk-js": patch
---

Correctly handle malformed or protocol-relative URLs before navigating to cross-origin URLs
6 changes: 6 additions & 0 deletions packages/clerk-js/src/utils/__tests__/url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,12 @@ describe('isAllowedRedirectOrigin', () => {
// regexp
['https://www.clerk.com/foo?hello=1', [/https:\/\/www\.clerk\.com/], true],
['https://test.clerk.com/foo?hello=1', [/https:\/\/www\.clerk\.com/], false],
// malformed or protocol-relative URLs
['http:evil.com', [/https:\/\/www\.clerk\.com/], false],
['https:evil.com', [/https:\/\/www\.clerk\.com/], false],
['http//evil.com', [/https:\/\/www\.clerk\.com/], false],
['https//evil.com', [/https:\/\/www\.clerk\.com/], false],
['//evil.com', [/https:\/\/www\.clerk\.com/], false],
];

const warnMock = jest.spyOn(logger, 'warnOnce');
Expand Down
40 changes: 20 additions & 20 deletions packages/clerk-js/src/utils/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,29 @@ export function relativeToAbsoluteUrl(url: string, origin: string | URL): string
return new URL(url, origin).href;
}

export function isRelativeUrl(val: unknown): val is string {
export function isRelativeUrl(val: string): boolean {
if (val !== val && !val) {
return false;
}

if (val.startsWith('//') || val.startsWith('http/') || val.startsWith('https/')) {
// Protocol-relative URL; consider it absolute.
return false;
}

try {
const temp = new URL(val as string, DUMMY_URL_BASE);
return temp.origin === DUMMY_URL_BASE;
} catch (e) {
// If this does not throw, it's a valid absolute URL
new URL(val);
return false;
} catch (e) {
try {
// If this does not throw, it's a valid relative URL
new URL(val, DUMMY_URL_BASE);
return true;
} catch (e) {
// Invalid URL case
return false;
}
}
}

Expand Down Expand Up @@ -344,12 +358,11 @@ export const isAllowedRedirectOrigin =
return true;
}

const url = new URL(_url, DUMMY_URL_BASE);
const isRelativeUrl = url.origin === DUMMY_URL_BASE;
if (isRelativeUrl) {
if (isRelativeUrl(_url)) {
return true;
}

const url = new URL(_url, DUMMY_URL_BASE);
const isAllowed = allowedRedirectOrigins
.map(origin => (typeof origin === 'string' ? globs.toRegexp(trimTrailingSlash(origin)) : origin))
.some(origin => origin.test(trimTrailingSlash(url.origin)));
Expand Down Expand Up @@ -380,16 +393,3 @@ export function createAllowedRedirectOrigins(

return origins;
}

export const isCrossOrigin = (url: string | URL, origin: string | URL = window.location.origin): boolean => {
try {
if (isRelativeUrl(url)) {
return false;
}
const urlOrigin = new URL(url).origin;
const originOrigin = new URL(origin).origin;
return urlOrigin !== originOrigin;
} catch (e) {
return false;
}
};