a1d7c07c50f1d895337de121680ea672b261c058
max
  Mon Aug 17 02:26:36 2026 -0700
escape reflected/echoed user input across several CGIs (XSS), refs #38057

Route user-, DB- and hub-derived values through htmlEncode (HTML/attribute text),
cgiEncode (values composed into URLs), jsonStringEscape (values placed in a JS string
literal inside an inline script) or, for hgMirror, the existing mustBeClean sanitizer.
Covers hgHubConnect, hgUserSuggestion, hgLiftOver, hgBlat, hgc pubs, hgVisiGene,
hgSession, hgTrackUi, hgGenome, phyloPng, hgFileSearch, hgLinkIn, hgPal, hui,
hgPhyloPlace, hgMirror, hgCustom and hgSearch.

diff --git src/hg/hgSearch/hgSearch.c src/hg/hgSearch/hgSearch.c
index f3d0cb81ec2..726cce77400 100644
--- src/hg/hgSearch/hgSearch.c
+++ src/hg/hgSearch/hgSearch.c
@@ -598,35 +598,39 @@
             track = tdbForTrack(db, trackName+strlen("all_"), &hgFindTdbList);
         if (!track)
             errAbort("no track for table \"%s\" found via a findSpec", trackName);
         }
     if (track)
         {
         trackName = cloneString(track->track);
         }
     trackHubFixName(trackName);
     puts("Content-type:text/html\n");
     puts("<HTML>\n<HEAD>\n");
     printf("<script type='text/javascript' src='../js/utils.js'></script>\n");
     printf("<script>\n");
     // we are about to redirect back to hgTracks, save the search term onto the
     // history stack so it will appear in the dropdown of auto-suggestions before
-    // redirecting
+    // redirecting.  db and userSearch are user-supplied and go into a JS string literal
+    // inside this inline <script>; jsonStringEscape escapes quotes and '/' so neither the
+    // string literal nor a literal </script> can break out (XSS).
+    char *jsDb = jsonStringEscape(db);
+    char *jsSearch = jsonStringEscape(userSearch);
     printf("addRecentSearch(\"%s\", \"%s\", {\"label\": \"%s\", \"value\": \"%s\", \"id\": \"%s\"});\n",
-            db, userSearch, userSearch, userSearch, newPosBuf);
+            jsDb, jsSearch, jsSearch, jsSearch, newPosBuf);
     printf("window.location.href=\"../cgi-bin/hgTracks?");
-    printf("db=%s", db);
+    printf("db=%s", jsDb);
     printf("&position=%s", newPosBuf);
     if (!sameString(trackName, "chromInfo"))
         printf("&%s=pack", trackName);
     printf("&hgFind.matches=%s", hgp->singlePos->name);
     if (track && track->parent)
         {
         if (tdbIsSuperTrackChild(track))
             printf("&%s=show", track->parent->track);
         else
             {
             // tdb is a subtrack of a composite or a view
             printf("&%s_sel=1&%s_sel=1", trackName, track->parent->track);
             }
         }
     printf("\"</script>\n");