[WEB-4943]fix: next path url redirection (#7817)

* fix: next path url redirection

* fix: enhance URL redirection safety in authentication views

Updated SignInAuthSpaceEndpoint, GitHubCallbackSpaceEndpoint, GitLabCallbackSpaceEndpoint, and GoogleCallbackSpaceEndpoint to include checks for allowed hosts and schemes before redirecting. This improves the security of URL redirection by ensuring only valid URLs are used.

* chore: updated uitl to handle double /

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
This commit is contained in:
Vamsi Krishna 2025-09-17 18:52:35 +05:30 committed by GitHub
parent 3d06189723
commit 877c117c37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 99 additions and 26 deletions

View file

@ -114,22 +114,6 @@ export function extractHostname(url: string): string {
* - Removes hash fragments (everything after '#')
* - Removes port numbers (everything after ':')
* 3. Validates the TLD against a list of known TLDs
*
* @example
* // Valid cases (returns the TLD)
* extractTLD('example.com') // returns 'com'
* extractTLD('sub.example.com') // returns 'com'
* extractTLD('example.com/path') // returns 'com'
* extractTLD('example.com:8080') // returns 'com'
* extractTLD('example.com?query=1') // returns 'com'
* extractTLD('example.com#hash') // returns 'com'
*
* // Invalid cases (returns empty string)
* extractTLD('') // returns ''
* extractTLD('.example.com') // returns ''
* extractTLD('example.com.') // returns ''
* extractTLD('example.invalid') // returns ''
* extractTLD('localhost') // returns ''
*/
export function extractTLD(urlString: string): string {
@ -257,3 +241,66 @@ export function extractURLComponents(url: URL | string): IURLComponents | undefi
return undefined;
}
}
/**
* Validates that a next_path parameter is safe for redirection.
* Only allows relative paths starting with "/" to prevent open redirect vulnerabilities.
*
* @param url - The next_path URL to validate
* @returns True if the URL is a safe relative path, false otherwise
*
* @example
* isValidNextPath("/dashboard") // true
* isValidNextPath("/workspace/123") // true
* isValidNextPath("https://malicious.com") // false
* isValidNextPath("//malicious.com") // false (protocol-relative)
* isValidNextPath("javascript:alert(1)") // false
* isValidNextPath("") // false
* isValidNextPath("dashboard") // false (must start with /)
* isValidNextPath("\\malicious") // false (backslash)
* isValidNextPath(" /dashboard ") // true (trimmed)
*/
export function isValidNextPath(url: string): boolean {
if (!url || typeof url !== "string") return false;
// Trim leading/trailing whitespace
const trimmedUrl = url.trim();
if (!trimmedUrl) return false;
// Only allow relative paths starting with /
if (!trimmedUrl.startsWith("/")) return false;
// Block protocol-relative URLs (//example.com) - open redirect vulnerability
if (trimmedUrl.startsWith("//")) return false;
// Block backslashes which can be used for path traversal or Windows-style paths
if (trimmedUrl.includes("\\")) return false;
try {
// Use URL constructor with a dummy base to normalize and validate the path
const normalizedUrl = new URL(trimmedUrl, "http://localhost");
// Ensure the path is still relative (no host change from our dummy base)
if (normalizedUrl.hostname !== "localhost" || normalizedUrl.protocol !== "http:") {
return false;
}
// Use the normalized pathname for additional security checks
const pathname = normalizedUrl.pathname;
// Additional security checks for malicious patterns in the normalized path
const maliciousPatterns = [
/javascript:/i,
/data:/i,
/vbscript:/i,
/<script/i,
/on\w+=/i, // Event handlers like onclick=, onload=
];
return !maliciousPatterns.some((pattern) => pattern.test(pathname));
} catch (error) {
// If URL constructor fails, it's an invalid path
return false;
}
}