CVE-2021-43812 (published 2021-12-16T19:15Z)

The Auth0 Next.js SDK is a library for implementing user authentication in Next.js applications. Versions before 1.6.2 do not filter out certain returnTo parameter values from the login url, which expose the application to an open redirect vulnerability. Users are advised to upgrade as soon as possible. There are no known workarounds for this issue.

Severity: MEDIUM
CVSS v2 base score: 5.8 (AV:N/AC:M/Au:N/C:P/I:P/A:N)
CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-601
cwe_name: URL Redirection to Untrusted Site ('Open Redirect')
description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
extended_description: An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance. Whether this issue poses a vulnerability will be subject to the intended behavior of the application. For example, a search engine might intentionally provide redirects to arbitrary URLs.
         

Fix information:
repo_url: https://github.com/auth0/nextjs-auth0
commit_details: repo_url: https://github.com/auth0/nextjs-auth0
author: Adam Mcgrath
author_date: 2021-12-15 16:24:33+00:00
msg: Enforce configured host on user supplied returnTo (#557)

* Enforce configured host on user supplied returnTo

* Test improvements per PR suggestions
repository: repo_url: https://github.com/auth0/nextjs-auth0
repo_name: auth0/nextjs-auth0
description: Next.js SDK for signing in with Auth0
repo_language: TypeScript
owner: auth0
file_changes: filename: login.ts
change_type: ModificationType.MODIFY
diff: @@ -1,8 +1,8 @@
 import { NextApiResponse, NextApiRequest } from 'next';
 import { AuthorizationParameters, HandleLogin as BaseHandleLogin } from '../auth0-session';
-import isSafeRedirect from '../utils/url-helpers';
+import toSafeRedirect from '../utils/url-helpers';
 import { assertReqRes } from '../utils/assert';
-import { NextConfig } from '../config';
+import { BaseConfig, NextConfig } from '../config';
 import { HandlerError } from '../utils/errors';
 
 /**
@@ -117,16 +117,19 @@ export type HandleLogin = (req: NextApiRequest, res: NextApiResponse, options?:
 /**
  * @ignore
  */
-export default function handleLoginFactory(handler: BaseHandleLogin, nextConfig: NextConfig): HandleLogin {
+export default function handleLoginFactory(
+  handler: BaseHandleLogin,
+  nextConfig: NextConfig,
+  baseConfig: BaseConfig
+): HandleLogin {
   return async (req, res, options = {}): Promise<void> => {
     try {
       assertReqRes(req, res);
       if (req.query.returnTo) {
-        const returnTo = Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo;
+        const dangerousReturnTo = Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo;
+        const safeBaseUrl = new URL(options.authorizationParams?.redirect_uri || baseConfig.baseURL);
 
-        if (!isSafeRedirect(returnTo)) {
-          throw new Error('Invalid value provided for returnTo, must be a relative url');
-        }
+        const returnTo = toSafeRedirect(dangerousReturnTo, safeBaseUrl);
 
         options = { ...options, returnTo };
       }

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -77,7 +77,7 @@ export const initAuth0: InitAuth0 = (params) => {
   const getAccessToken = accessTokenFactory(nextConfig, getClient, sessionCache);
   const withApiAuthRequired = withApiAuthRequiredFactory(sessionCache);
   const withPageAuthRequired = withPageAuthRequiredFactory(nextConfig.routes.login, getSession);
-  const handleLogin = loginHandler(baseHandleLogin, nextConfig);
+  const handleLogin = loginHandler(baseHandleLogin, nextConfig, baseConfig);
   const handleLogout = logoutHandler(baseHandleLogout);
   const handleCallback = callbackHandler(baseHandleCallback, nextConfig);
   const handleProfile = profileHandler(getClient, getAccessToken, sessionCache);

programming_language: TypeScript
filename: url-helpers.ts
change_type: ModificationType.MODIFY
diff: @@ -1,16 +1,17 @@
 /**
  * Helper which tests if a URL can safely be redirected to. Requires the URL to be relative.
- * @param url
+ * @param dangerousRedirect
+ * @param safeBaseUrl
  */
-export default function isSafeRedirect(url: string): boolean {
-  if (typeof url !== 'string') {
-    throw new TypeError(`Invalid url: ${url}`);
+export default function toSafeRedirect(dangerousRedirect: string, safeBaseUrl: URL): string | undefined {
+  let url: URL;
+  try {
+    url = new URL(dangerousRedirect, safeBaseUrl);
+  } catch (e) {
+    return undefined;
   }
-
-  // Prevent open redirects using the //foo.com format (double forward slash).
-  if (/\/\//.test(url)) {
-    return false;
+  if (url.origin === safeBaseUrl.origin) {
+    return url.toString();
   }
-
-  return !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url);
+  return undefined;
 }

programming_language: TypeScript
filename: open-redirect-payloads.json
change_type: ModificationType.ADD
diff: @@ -0,0 +1,487 @@
+[
+  "javascript:alert(1)",
+  "java%0d%0ascript%0d%0a:alert(0)",
+  "javascript://%250Aalert(1)",
+  "javascript://%250Aalert(1)//?1",
+  "javascript://%250A1?alert(1):0",
+  "%09Jav%09ascript:alert(document.domain)",
+  "javascript://%250Alert(document.location=document.cookie)",
+  "/%09/javascript:alert(1);",
+  "/%09/javascript:alert(1)",
+  "//%5cjavascript:alert(1);",
+  "//%5cjavascript:alert(1)",
+  "/%5cjavascript:alert(1);",
+  "/%5cjavascript:alert(1)",
+  "javascript://%0aalert(1)",
+  "<>javascript:alert(1);",
+  "//javascript:alert(1);",
+  "//javascript:alert(1)",
+  "/javascript:alert(1);",
+  "/javascript:alert(1)",
+  "javascript:alert(1)",
+  "javascript:alert(1);",
+  "javascript:alert(1)",
+  "javascripT://anything%0D%0A%0D%0Awindow.alert(document.cookie)",
+  "javascript:confirm(1)",
+  "javascript://https://whitelisted.com/?z=%0Aalert(1)",
+  "javascript:prompt(1)",
+  "jaVAscript://whitelisted.com//%0d%0aalert(1);//",
+  "javascript://whitelisted.com?%a0alert%281%29",
+  "/x:1/:///%01javascript:alert(document.cookie)/",
+  "<>//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "/////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "///;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "/〱Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  ".Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "〱Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ%00｡Ｐⓦ",
+  "%01https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "%01https://google.com",
+  "////%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////%09/google.com",
+  "///%09/google.com",
+  "//%09/google.com",
+  "/%09/google.com",
+  "////%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////%09/whitelisted.com@google.com",
+  "///%09/whitelisted.com@google.com",
+  "//%09/whitelisted.com@google.com",
+  "/%09/whitelisted.com@google.com",
+  "&%0d%0a1Location:https://google.com",
+  "%19Jav%09asc%09ript:https%20://whitelisted.com/%250Aconfirm%25281%2529",
+  "////216.58.214.206",
+  "///216.58.214.206",
+  "//216.58.214.206",
+  "/\\216.58.214.206",
+  "/216.58.214.206",
+  "216.58.214.206",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "%2f216.58.214.206//",
+  "%2f216.58.214.206",
+  "%2f216.58.214.206%2f%2f",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "//%2f%2fⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%2f%2fⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "%2f$2f216.58.214.206",
+  "$2f%2f216.58.214.206%2f%2f",
+  "%2f$2f3627734734",
+  "$2f%2f3627734734%2f%2f",
+  "//%2f%2fgoogle.com",
+  "/%2f%2fgoogle.com",
+  "$2f%2fgoogle.com",
+  "%2f$2fgoogle.com",
+  "$2f%2fgoogle.com%2f%2f",
+  "%2f3627734734//",
+  "%2f3627734734",
+  "%2f3627734734%2f%2f",
+  "/%2f%5c%2f%67%6f%6f%67%6c%65%2e%63%6f%6d/",
+  "/%2f%5c%2f%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77/",
+  "%2fgoogle.com//",
+  "%2fgoogle.com",
+  "%2fgoogle.com%2f%2f",
+  "////3627734734",
+  "///3627734734",
+  "//3627734734",
+  "/3627734734",
+  "3627734734",
+  "//3H6k7lIAiqjfNeN@whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//3H6k7lIAiqjfNeN@whitelisted.com+@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//3H6k7lIAiqjfNeN@whitelisted.com@google.com/",
+  "//3H6k7lIAiqjfNeN@whitelisted.com+@google.com/",
+  "////%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////%5cgoogle.com",
+  "///%5cgoogle.com",
+  "//%5cgoogle.com",
+  "/%5cgoogle.com",
+  "////%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////%5cwhitelisted.com@google.com",
+  "///%5cwhitelisted.com@google.com",
+  "//%5cwhitelisted.com@google.com",
+  "/%5cwhitelisted.com@google.com",
+  "/%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d",
+  "%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d",
+  "%68%74%74%70%73%3a%2f%2f%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80?@whitelisted.com/",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80#@whitelisted.com/",
+  "\";alert(0);//",
+  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=",
+  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+Cg==",
+  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik8L3NjcmlwdD4=",
+  "data:whitelisted.com;text/html;charset=UTF-8,<html><script>document.write(document.domain);</script><iframe/src=xxxxx>aaaa</iframe></html>",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ%E3%80%82pw",
+  "//google%00.com",
+  "/google%252ecom",
+  "google%252ecom",
+  "<>//google.com",
+  "/<>//google.com",
+  "//;@google.com",
+  "///;@google.com",
+  "/////google.com/",
+  "/////google.com",
+  "////;@google.com",
+  "////google.com//",
+  "////google.com/",
+  "////google.com",
+  "///;@google.com",
+  "///google.com//",
+  "///google.com/",
+  "///google.com",
+  "///google.com/",
+  "//google.com",
+  "//google.com//",
+  "//google.com/",
+  "//google.com",
+  "/.google.com",
+  "///google.com/",
+  "//google.com/",
+  "//google.com",
+  "/google.com",
+  "/〱google.com",
+  "/google.com",
+  "../google.com",
+  ".google.com",
+  "@google.com",
+  "//google.com/",
+  "〱google.com",
+  "google.com",
+  "google.com%23@whitelisted.com",
+  "////google.com/%2e%2e",
+  "///google.com/%2e%2e",
+  "//google.com/%2e%2e",
+  "/google.com/%2e%2e",
+  "//google.com/%2E%2E",
+  "////google.com/%2e%2e%2f",
+  "///google.com/%2e%2e%2f",
+  "//google.com/%2e%2e%2f",
+  "////google.com/%2f..",
+  "///google.com/%2f..",
+  "//google.com/%2f..",
+  "//google.com/%2F..",
+  "/google.com/%2F..",
+  "////google.com/%2f%2e%2e",
+  "///google.com/%2f%2e%2e",
+  "//google.com/%2f%2e%2e",
+  "/google.com/%2f%2e%2e",
+  "//google.com//%2F%2E%2E",
+  "//google.com:80?@whitelisted.com/",
+  "//google.com:80#@whitelisted.com/",
+  "google.com/.jpg",
+  "//google.com\twhitelisted.com/",
+  "//google.com/whitelisted.com",
+  "//google.com@whitelisted.com",
+  "google.com/whitelisted.com",
+  "//google%E3%80%82com",
+  "/http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/http:/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http://;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http://.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http:/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http:Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http://00330.00072.0000326.00000316",
+  "http:00330.00072.0000326.00000316",
+  "http://00330.0x3a.54990",
+  "http:00330.0x3a.54990",
+  "http://00330.3856078",
+  "http:00330.3856078",
+  "http://0330.072.0326.0316",
+  "http:0330.072.0326.0316",
+  "http:%0a%0dⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http:%0a%0dgoogle.com",
+  "http://0xd8.072.54990",
+  "http:0xd8.072.54990",
+  "http://0xd8.0x3a.0xd6.0xce",
+  "http:0xd8.0x3a.0xd6.0xce",
+  "http://0xd8.3856078",
+  "http:0xd8.3856078",
+  "http://0xd83ad6ce",
+  "http:0xd83ad6ce",
+  "http://[::216.58.214.206]",
+  "http:[::216.58.214.206]",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%23.whitelisted.com/",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%2f%2f.whitelisted.com/",
+  "http://3627734734",
+  "http:3627734734",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%3F.whitelisted.com/",
+  "http://3H6k7lIAiqjfNeN@00330.00072.0000326.00000316",
+  "http:3H6k7lIAiqjfNeN@00330.00072.0000326.00000316",
+  "http://3H6k7lIAiqjfNeN@00330.0x3a.54990",
+  "http:3H6k7lIAiqjfNeN@00330.0x3a.54990",
+  "http://3H6k7lIAiqjfNeN@00330.3856078",
+  "http:3H6k7lIAiqjfNeN@00330.3856078",
+  "http://3H6k7lIAiqjfNeN@0330.072.0326.0316",
+  "http:3H6k7lIAiqjfNeN@0330.072.0326.0316",
+  "http://3H6k7lIAiqjfNeN@0xd8.072.54990",
+  "http:3H6k7lIAiqjfNeN@0xd8.072.54990",
+  "http://3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce",
+  "http:3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce",
+  "http://3H6k7lIAiqjfNeN@0xd8.3856078",
+  "http:3H6k7lIAiqjfNeN@0xd8.3856078",
+  "http://3H6k7lIAiqjfNeN@0xd83ad6ce",
+  "http:3H6k7lIAiqjfNeN@0xd83ad6ce",
+  "http://3H6k7lIAiqjfNeN@[::216.58.214.206]",
+  "http:3H6k7lIAiqjfNeN@[::216.58.214.206]",
+  "http://3H6k7lIAiqjfNeN@3627734734",
+  "http:3H6k7lIAiqjfNeN@3627734734",
+  "http://3H6k7lIAiqjfNeN@472.314.470.462",
+  "http:3H6k7lIAiqjfNeN@472.314.470.462",
+  "http://3H6k7lIAiqjfNeN@[::ffff:216.58.214.206]",
+  "http:3H6k7lIAiqjfNeN@[::ffff:216.58.214.206]",
+  "http://3H6k7lIAiqjfNeN@whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "http://3H6k7lIAiqjfNeN@whitelisted.com+@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "http://3H6k7lIAiqjfNeN@whitelisted.com@google.com/",
+  "http://3H6k7lIAiqjfNeN@whitelisted.com+@google.com/",
+  "http://472.314.470.462",
+  "http:472.314.470.462",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%5c%5c.whitelisted.com/",
+  "/http://%67%6f%6f%67%6c%65%2e%63%6f%6d",
+  "http://%67%6f%6f%67%6c%65%2e%63%6f%6d",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80?@whitelisted.com/",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80#@whitelisted.com/",
+  "http://[::ffff:216.58.214.206]",
+  "http:[::ffff:216.58.214.206]",
+  "/http://google.com",
+  "/http:/google.com",
+  "http://;@google.com",
+  "http://.google.com",
+  "http://google.com",
+  "http://google.com",
+  "http:/google.com",
+  "http:google.com",
+  "http://google.com%23.whitelisted.com/",
+  "http://google.com%2f%2f.whitelisted.com/",
+  "http://google.com%3F.whitelisted.com/",
+  "http://google.com%5c%5c.whitelisted.com/",
+  "http://google.com:80?@whitelisted.com/",
+  "http://google.com:80#@whitelisted.com/",
+  "http://google.com\twhitelisted.com/",
+  "//https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https:Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/https://%09/google.com",
+  "https://%09/google.com",
+  "https://%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://%09/whitelisted.com@google.com",
+  "https://%0a%0dⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://%0a%0dgoogle.com",
+  "//https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "//https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "/https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https%3a%2f%2fgoogle.com%2f",
+  "/https://%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/https:/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https:/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "/https://%5cgoogle.com",
+  "/https:/%5cgoogle.com/",
+  "https://%5cgoogle.com",
+  "https:/%5cgoogle.com/",
+  "/https://%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/https://%5cwhitelisted.com@google.com",
+  "https://%5cwhitelisted.com@google.com",
+  "https://%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77",
+  "//https://google.com//",
+  "/https://google.com//",
+  "/https://google.com/",
+  "/https://google.com",
+  "/https:google.com",
+  "https://////google.com",
+  "https://google.com//",
+  "https://google.com/",
+  "https://google.com",
+  "https:/google.com",
+  "https:google.com",
+  "//https:///google.com/%2e%2e",
+  "/https://google.com/%2e%2e",
+  "https:///google.com/%2e%2e",
+  "//https://google.com/%2e%2e%2f",
+  "https://google.com/%2e%2e%2f",
+  "/https://google.com/%2f..",
+  "https://google.com/%2f..",
+  "/https:///google.com/%2f%2e%2e",
+  "/https://google.com/%2f%2e%2e",
+  "https:///google.com/%2f%2e%2e",
+  "https://google.com/%2f%2e%2e",
+  "https://:@google.com@whitelisted.com",
+  "https://google.com?whitelisted.com",
+  "https://google.com/whitelisted.com",
+  "https://google.comwhitelisted.com",
+  "https://google.com#whitelisted.com",
+  "https://google%E3%80%82com",
+  "//https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://:@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ@whitelisted.com",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
+  "https://whitelisted.com;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "//https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "/https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "//https://whitelisted.com@google.com//",
+  "/https://whitelisted.com@google.com/",
+  "https://whitelisted.com;@google.com",
+  "https://whitelisted.com.google.com",
+  "https://whitelisted.com@google.com//",
+  "https://whitelisted.com@google.com/",
+  "https://whitelisted.com@google.com",
+  "/https://whitelisted.com@google.com/%2e%2e",
+  "https:///whitelisted.com@google.com/%2e%2e",
+  "//https://whitelisted.com@google.com/%2e%2e%2f",
+  "https://whitelisted.com@google.com/%2e%2e%2f",
+  "/https://whitelisted.com@google.com/%2f..",
+  "https://whitelisted.com@google.com/%2f..",
+  "/https:///whitelisted.com@google.com/%2f%2e%2e",
+  "/https://whitelisted.com@google.com/%2f%2e%2e",
+  "https:///whitelisted.com@google.com/%2f%2e%2e",
+  "https://whitelisted.com@google.com/%2f%2e%2e",
+  "/https://whitelisted.com@google.com/%2f.//whitelisted.com@google.com/%2f..",
+  "https://whitelisted.com/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://whitelisted.com/https://google.com/",
+  "@https://www.google.com",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ\twhitelisted.com/",
+  "http://whitelisted.com@00330.00072.0000326.00000316",
+  "http:whitelisted.com@00330.00072.0000326.00000316",
+  "http://whitelisted.com@00330.0x3a.54990",
+  "http:whitelisted.com@00330.0x3a.54990",
+  "http://whitelisted.com@00330.3856078",
+  "http:whitelisted.com@00330.3856078",
+  "http://whitelisted.com@0330.072.0326.0316",
+  "http:whitelisted.com@0330.072.0326.0316",
+  "http://whitelisted.com@0xd8.072.54990",
+  "http:whitelisted.com@0xd8.072.54990",
+  "http://whitelisted.com@0xd8.0x3a.0xd6.0xce",
+  "http:whitelisted.com@0xd8.0x3a.0xd6.0xce",
+  "http://whitelisted.com@0xd8.3856078",
+  "http:whitelisted.com@0xd8.3856078",
+  "http://whitelisted.com@0xd83ad6ce",
+  "http:whitelisted.com@0xd83ad6ce",
+  "http://whitelisted.com@[::216.58.214.206]",
+  "http:whitelisted.com@[::216.58.214.206]",
+  "http://whitelisted.com%2eⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "http://whitelisted.com%2egoogle.com/",
+  "http://whitelisted.com@3627734734",
+  "http:whitelisted.com@3627734734",
+  "http://whitelisted.com@472.314.470.462",
+  "http:whitelisted.com@472.314.470.462",
+  "http://whitelisted.com:80%40Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "http://whitelisted.com:80%40google.com/",
+  "http://whitelisted.com@[::ffff:216.58.214.206]",
+  "http:whitelisted.com@[::ffff:216.58.214.206]",
+  "http://whitelisted.com@google.com/",
+  "http://whitelisted.com+&@google.com#+@whitelisted.com/",
+  "http://whitelisted.com+&@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ#+@whitelisted.com/",
+  "http://www.google.com.whitelisted.com",
+  "http://www.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ.whitelisted.com",
+  "ja\nva\tscript\r:alert(1)",
+  "java%09script:alert(1)",
+  "java%0ascript:alert(1)",
+  "java%0d%0ascript%0d%0a:alert(0)",
+  "java%0dscript:alert(1)",
+  "Javas%26%2399;ript:alert(1)",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ\twhitelisted.com/",
+  "\u006A\u0061\u0076\u0061\u0073\u0063\u0072\u0069\u0070\u0074\u003aalert(1)",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ@whitelisted.com",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
+  "whitelisted.com;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "/whitelisted.com:80%40google.com",
+  "whitelisted.com@%E2%80%AE@google.com",
+  "////whitelisted.com@google.com//",
+  "////whitelisted.com@google.com/",
+  "///whitelisted.com@google.com//",
+  "///whitelisted.com@google.com/",
+  "//whitelisted.com@google.com//",
+  "//whitelisted.com@google.com/",
+  "whitelisted.com;@google.com",
+  "whitelisted.com.google.com",
+  "////whitelisted.com@google.com/%2e%2e",
+  "///whitelisted.com@google.com/%2e%2e",
+  "////whitelisted.com@google.com/%2e%2e%2f",
+  "///whitelisted.com@google.com/%2e%2e%2f",
+  "//whitelisted.com@google.com/%2e%2e%2f",
+  "////whitelisted.com@google.com/%2f..",
+  "///whitelisted.com@google.com/%2f..",
+  "//whitelisted.com@google.com/%2f..",
+  "////whitelisted.com@google.com/%2f%2e%2e",
+  "///whitelisted.com@google.com/%2f%2e%2e",
+  "//whitelisted.com@google.com/%2f%2e%2e",
+  "//whitelisted.com+&@google.com#+@whitelisted.com/",
+  "//whitelisted.com@https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "//whitelisted.com@https:///google.com/%2e%2e",
+  "//whitelisted.com+&@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ#+@whitelisted.com/"
+]

programming_language: Python
filename: login.test.ts
change_type: ModificationType.MODIFY
diff: @@ -214,7 +214,7 @@ describe('login handler', () => {
 
     const decodedState = decodeState(state.split('.')[0]);
     expect(decodedState).toEqual({
-      returnTo: '/foo'
+      returnTo: new URL('/foo', withoutApi.baseURL).toString()
     });
   });
 
@@ -229,7 +229,7 @@ describe('login handler', () => {
 
     const decodedState = decodeState(state.split('.')[0]);
     expect(decodedState).toEqual({
-      returnTo: '/foo'
+      returnTo: new URL('/foo', withoutApi.baseURL).toString()
     });
   });
 
@@ -239,9 +239,47 @@ describe('login handler', () => {
     };
     const baseUrl = await setup(withoutApi, { loginOptions });
 
-    await expect(
-      get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { fullResponse: true })
-    ).rejects.toThrow('Invalid value provided for returnTo, must be a relative url');
+    const cookieJar = new CookieJar();
+    await get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { cookieJar });
+    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;
+
+    const decodedState = decodeState(state.split('.')[0]);
+    expect(decodedState).toEqual({});
+  });
+
+  test('should allow absolute urls in params of returnTo urls', async () => {
+    const loginOptions = {
+      returnTo: '/default-redirect'
+    };
+    const baseUrl = await setup(withoutApi, { loginOptions });
+
+    const cookieJar = new CookieJar();
+    await get(baseUrl, '/api/auth/login?returnTo=/foo?url=https://www.google.com', { cookieJar });
+    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;
+
+    const decodedState = decodeState(state.split('.')[0]);
+    expect(decodedState).toEqual({
+      returnTo: new URL('/foo?url=https://www.google.com', withoutApi.baseURL).toString()
+    });
+  });
+
+  test('should redirect relative to the redirect_uri over the base url', async () => {
+    const loginOptions = {
+      returnTo: '/default-redirect',
+      authorizationParams: {
+        redirect_uri: 'https://other-org.acme.com/api/auth/callback'
+      }
+    };
+    const baseUrl = await setup(withoutApi, { loginOptions });
+
+    const cookieJar = new CookieJar();
+    await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar });
+    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;
+
+    const decodedState = decodeState(state.split('.')[0]);
+    expect(decodedState).toEqual({
+      returnTo: 'https://other-org.acme.com/foo'
+    });
   });
 
   test('should escape html in errors', async () => {

programming_language: TypeScript
filename: url-helpers.test.ts
change_type: ModificationType.MODIFY
diff: @@ -1,25 +1,34 @@
-import isSafeRedirect from '../../src/utils/url-helpers';
+import toSafeRedirect from '../../src/utils/url-helpers';
+import payloads from '../fixtures/open-redirect-payloads.json';
 
 describe('url-helpers', () => {
+  const safeBaseUrl = new URL('http://www.example.com');
+
   describe('isSafeRedirect', () => {
     test('should not allow absolute urls', () => {
-      expect(isSafeRedirect('file://foo')).toEqual(false);
-      expect(isSafeRedirect('https://foo')).toEqual(false);
-      expect(isSafeRedirect('http://foo')).toEqual(false);
+      expect(toSafeRedirect('file://foo', safeBaseUrl)).toEqual(undefined);
+      expect(toSafeRedirect('https://foo', safeBaseUrl)).toEqual(undefined);
+      expect(toSafeRedirect('http://foo', safeBaseUrl)).toEqual(undefined);
     });
 
     test('should allow relative urls', () => {
-      expect(isSafeRedirect('/foo')).toEqual(true);
-      expect(isSafeRedirect('/foo?some=value')).toEqual(true);
+      expect(toSafeRedirect('/foo', safeBaseUrl)).toEqual('http://www.example.com/foo');
+      expect(toSafeRedirect('foo', safeBaseUrl)).toEqual('http://www.example.com/foo');
+      expect(toSafeRedirect('/foo?some=value', safeBaseUrl)).toEqual('http://www.example.com/foo?some=value');
+      expect(toSafeRedirect('/foo?someUrl=https://www.google.com', safeBaseUrl)).toEqual(
+        'http://www.example.com/foo?someUrl=https://www.google.com'
+      );
+      expect(toSafeRedirect('/foo', new URL('http://www.example.com:8888'))).toEqual('http://www.example.com:8888/foo');
     });
 
     test('should prevent open redirects', () => {
-      expect(isSafeRedirect('//google.com')).toEqual(false);
-      expect(isSafeRedirect('///google.com')).toEqual(false);
+      for (const payload of payloads) {
+        expect(toSafeRedirect(payload, safeBaseUrl) || safeBaseUrl.toString()).toMatch(/^http:\/\/www.example.com\//);
+      }
     });
 
-    test('should throw when non string provided', () => {
-      expect(isSafeRedirect.bind(null)).toThrow('Invalid url: undefined');
+    test('should not throw for empty redirect', () => {
+      expect(toSafeRedirect.bind(null, '', safeBaseUrl)).not.toThrow();
     });
   });
 });

programming_language: TypeScript