ee5221ac43d6831f3cdc0ed6426eea98d12916f9
chmalee
  Tue Oct 14 12:33:04 2025 -0700
New hg.conf option 'login.approvedReturn' that hgLogin uses to check the validity of the returnto argument set by hgSession to prevent credential stealing. When this setting is not present the default behavior of accepting any URL in the returnto argument is active. refs #36485

diff --git src/hg/hgLogin/hgLogin.c src/hg/hgLogin/hgLogin.c
index 050aca46372..5f9cdadd71b 100644
--- src/hg/hgLogin/hgLogin.c
+++ src/hg/hgLogin/hgLogin.c
@@ -301,38 +301,66 @@
 
 return (count >= 1);
 }
 
 struct dyString *getLoginCookieJS(char *userName, uint idx)
 /* returns javascript statements that set the cookies associated with
  * logging in as a particular user */
 {
 struct dyString *result = dyStringNew(1024);
 struct slName *newCookies = loginLoginUser(userName, idx), *sl;
 for (sl = newCookies;  sl != NULL;  sl = sl->next)
     dyStringPrintf(result, " document.cookie = '%s';", sl->name);
 return result; 
 }
 
+static boolean isValidReturnUrl(char *returnUrl)
+/* Verify that returnUrl startswith an hg.conf approved set of hosts. */
+{
+struct slName *approvedHosts = slNameListFromComma(cfgOptionDefault(CFG_APPROVED_HOSTS, NULL));
+slAddHead(&approvedHosts, slNameNew(hLoginHostCgiBinUrl()));
+if (approvedHosts)
+    {
+    struct slName *approvedStart;
+    for (approvedStart = approvedHosts; approvedStart != NULL; approvedStart = approvedStart->next)
+        {
+        if (startsWith(approvedStart->name, returnUrl))
+            return TRUE;
+        }
+    }
+return FALSE;
+}
+
 char *getReturnToURL()
 /* get URL from cart var returnto; if empty, make URL to hgSession on login host.  */
 {
 char *returnURL = cartUsualString(cart, "returnto", "");
 char returnTo[2048];
   
 if (!returnURL || sameString(returnURL,""))
     safef(returnTo, sizeof(returnTo), "%shgSession?hgS_doMainPage=1", hLoginHostCgiBinUrl());
+else if (cfgOptionDefault(CFG_APPROVED_HOSTS, NULL))
+    {
+    if (isValidReturnUrl(returnURL))
+        safecpy(returnTo, sizeof(returnTo), returnURL);
+    else
+        {
+        errAbort("Error: Invalid returnto URL. Please send email to genome-www@soe.ucsc.edu "
+                "with the returnto argument from the URL (or just the full URL) so we can "
+                "fix this.");
+        }
+    }
 else
     safecpy(returnTo, sizeof(returnTo), returnURL);
 return cloneString(returnTo);
 }
 
 void returnToURL(int delay)
 /* delay for delay mill-seconds then return to the "returnto" URL */
 {
 char *returnURL = getReturnToURL();
 jsInlineF(
     "setTimeout(function(){location='%s';}, %d);\n"
     , returnURL, delay);
 }
 
 static void redirectToLoginPage(char *paramStr)