764e1dff7737828ea9d3395804db520628e91a69
braney
  Tue Aug 18 10:59:46 2026 -0700
hgTracks: encode assembly and group header text consistently, refs #38123

diff --git src/hg/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c
index 442d61d3c3f..71f2ae89df5 100644
--- src/hg/hgTracks/hgTracks.c
+++ src/hg/hgTracks/hgTracks.c
@@ -9712,57 +9712,61 @@
      * narrow */
     hPrintf("<DIV STYLE=\"white-space:nowrap;\">\n");
     printMenuBar();
     //menuBarAppendExtTools();
 
     /* Show title */
     freezeName = hFreezeFromDb(database);
     if(freezeName == NULL)
         freezeName = "Unknown";
     hPrintf("<span id='assemblyName' style='font-size:large;'><B>");
 
     // for these assemblies, we do not display the year, to save space and reduce clutter
     // Their names must include a "(" character
     char* noYearDbs[] = { "hg19", "hg38", "mm39", "mm10" };
 
+    // on an assembly hub the organism, the freezeName (the hub's genome description) and the
+    // db name are all supplied by the hub, so escape them before they go in the page
     if ( stringArrayIx(database, noYearDbs, ArraySize(noYearDbs)) != -1 )
         {
         // freezeName is e.g. "Feb. 2009 (GRCh37/hg19)"
         char *afterParen = skipBeyondDelimit(freezeName, '(');
         afterParen--; // move back one char
-        hPrintf("%s %s on %s %s", organization, browserName, organism, afterParen);
+        hPrintf("%s %s on %s %s", organization, browserName, htmlEncode(organism),
+                htmlEncode(afterParen));
         }
     else if (startsWith("zoo",database) )
         {
 	hPrintf("%s %s on %s June 2002 Assembly %s target1",
-	    organization, browserName, organism, freezeName);
+	    organization, browserName, htmlEncode(organism), htmlEncode(freezeName));
 	}
     else
 	{
 	if (sameString(organism, "Archaea"))
 	    {
 	    hPrintf("%s %s on Archaeon %s Assembly",
-		organization, browserName, freezeName);
+		organization, browserName, htmlEncode(freezeName));
 	    }
 	else
 	    {
 	    if (stringIn(database, freezeName))
 		hPrintf("%s %s on %s %s",
-			organization, browserName, organism, freezeName);
+			organization, browserName, htmlEncode(organism), htmlEncode(freezeName));
 	    else
 		hPrintf("%s %s on %s %s (%s)",
-			organization, browserName, trackHubSkipHubName(organism), freezeName, trackHubSkipHubName(database));
+			organization, browserName, htmlEncode(trackHubSkipHubName(organism)),
+			htmlEncode(freezeName), htmlEncode(trackHubSkipHubName(database)));
 	    }
 	}
     hPrintf("</B></SPAN>");
 
     //hPrintf("<span target=_blank title='Show details about this assembly' id='gatewayLink'>"
             //"<a href='hgGateway?hgsid=%s'>Assembly Info</a></span>", cartSessionId(cart));
 
     printDatabaseInfoHtml(database);
 
     // Disable recommended track set panel when changing tracks, session, database
     char *sessionLabel = cartOptionalString(cart, hgsOtherUserSessionLabel);
     char *oldDb = hashFindVal(oldVars, "db");
     if (sessionLabel)
         {
         if (defaultTracks || hideAll || hideTracks ||
@@ -10175,84 +10179,93 @@
 	    jsOnEventByIdF("click", idText, "return vis.toggleForGroup(this, '%s');", group->name);
 
             if (isHubTrack(group->name))
 		{
 
                 if (strstr(group->label, "Collections"))
                     {
                     safef(idText, sizeof idText, "%s_edit", group->name);
                     hPrintf("<input name=\"hubEditButton\" id='%s'"
                         " type=\"button\" value=\"edit\">\n", idText);
                     jsOnEventByIdF("click", idText,
                         "document.editHubForm.submit();return true;");
                     }
                 }
 
-            hPrintf("</td><td style='text-align:center; width:90%%;'>\n<B>%s</B>", group->label);
+            // group->label for a hub group is built from the hub shortLabel and the hub's
+            // groups.txt label, both supplied by the hub, so escape it
+            hPrintf("</td><td style='text-align:center; width:90%%;'>\n<B>%s</B>", htmlEncode(group->label));
             
             char *hubName = hubNameFromGroupName(group->name);
             struct trackHub *hub = grabHashedHub(hubName);
             if (hub && hub->url)
                 {
                 puts("&nbsp;");
                 char infoText[10000];
                 if (startsWith("QuickLift", group->label))
                     safef(infoText, sizeof infoText, "This is a QuickLift track group that contains \
 				    tracks that are annotations on %s that have been lifted to this \
 				    assembly, along with a track (Alignment Differences) that shows \
 				    mismatches and indels between %s and this assembly."\
 				    , hub->defaultDb, hub->defaultDb);
                 else
                     safef(infoText, sizeof infoText, "A track hub is a list of tracks produced and hosted by external data providers. The UCSC browser group is not responsible for them. This hub is loaded from %s", hub->url);
                 printInfoIconColor(infoText, "white");
                 }
 
             hPrintf("</td><td style='text-align:right;'>\n");
             
             if (hubName)
 		{
                 if (cfgOptionBooleanDefault("groupDropdown", FALSE) && hub && hub->genomeList && hub->genomeList->next)
                     {
                     puts("<span style='font-size:13px'>Genomes: </span><select style='width:7em' name='db'>");
                     for (struct trackHubGenome *thg = hub->genomeList; thg != NULL; thg = thg->next)
                         {
                         if (!sameWord(thg->name, database))
-                            printf("<option value='%s'>%s</option>\n", thg->name, thg->name);
+                            {
+                            // hub genome names come from the hub, so encode them
+                            char *escName = htmlEncode(thg->name);
+                            printf("<option value='%s'>%s</option>\n", escName, escName);
+                            }
                         }
                     puts("</select>");
                     }
 
                 // visibility: hidden means that the element takes up space so the center alignment is not disturbed.
                 if ((hub != NULL) && !startsWith("QuickLift", group->label))
                     {
                     if (hub->descriptionUrl == NULL)
                         {
                         hPrintf("<a title='The track hub authors have not provided a descriptionUrl with background "
                                 "information about this track hub. ");
                         if (hub->email)
-                            hPrintf("The authors can be reached at %s. ", hub->email);
+                            // hub-supplied, so encode it
+                            hPrintf("The authors can be reached at %s. ", htmlEncode(hub->email));
                         hPrintf("This link leads to our documentation page about the descriptionUrl statement in hub.txt. ");
                         hPrintf("' href='../goldenPath/help/hgTrackHubHelp.html#hub.txt' "
                                 "style='color:#FFF; font-size: 13px;' target=_blank>No Info</a>");
                         }
                     else
                         {
                         hPrintf("<a title='Link to documentation about this track hub, provided by the track hub authors (not UCSC). ");
                         if (hub->email)
-                            hPrintf("The authors can be reached at %s", hub->email);
+                            // hub-supplied, so encode it
+                            hPrintf("The authors can be reached at %s", htmlEncode(hub->email));
                         hPrintf("' href='%s' "
-                            "style='color:#FFF; font-size: 13px;' target=_blank>Info</a>", hub->descriptionUrl);
+                            "style='color:#FFF; font-size: 13px;' target=_blank>Info</a>",
+                            htmlEncode(hub->descriptionUrl));
                         }
                     hPrintf("&nbsp;&nbsp;");
 
 
                     }
                 }
 
             hPrintf("<button type='button' class=\"hgtButtonHideGroup\" data-group-name=\"%s\" "
                     "title='Hide all tracks in this group'>Hide group</button>&nbsp;",
                     group->name);
 
             // The BLAT Results group gets a "Delete all" button that removes every BLAT result track
             // at once, so users are not stuck deleting accumulated results one by one.
             if (sameString(group->name, "blat"))
                 {