f759bf663debc22e6015c6c38c22f922bfbdf45b
braney
  Fri Aug 21 10:01:02 2026 -0700
lib, cgilib: sanitize description HTML where it comes in, refs #38126

A track hub's track description, a custom track's documentation, and an
assembly hub's genome description are all written by somebody else and were
stored and printed as they arrived.  Run each through htmlSanitize() at the
point it is read, so that everything downstream sees text we are willing to
print.  Sanitizing on the way in is the only practical place: printTrackHtml()
alone has more than a hundred callers, and hgTrackUi, hui.c, hgGene,
hgGtexTrackSettings and hgTables all print tdb->html directly.

The five places are trackHubAddOneDescription(), customTrack.c where it used
to call jsStripJavascript(), the htmlUrl fetch in customFactory.c,
hgPositionsHelpHtmlCart() and hAssemblyDescription().  In the last two, only
the branch that fetches over the network is touched.  The branch that reads a
local file is left as it is, because those are our own description.html files.

trackHubAddOneDescription() keeps its old shape.  The fetch moves into a
helper so that trackHubDescriptionRemovals() can hand hubCheck the list of
things the filter takes out.

diff --git src/hg/cgilib/cartJson.c src/hg/cgilib/cartJson.c
index fcc76e374af..0bf5c2f92a0 100644
--- src/hg/cgilib/cartJson.c
+++ src/hg/cgilib/cartJson.c
@@ -1,24 +1,25 @@
 /* cartJson - parse and execute JSON commands to update cart and/or return cart data as JSON. */
 #include "common.h"
 #include "cartJson.h"
 #include "cartTrackDb.h"
 #include "cheapcgi.h"
 #include "errCatch.h"
 #include "grp.h"
 #include "hdb.h"
 #include "hgFind.h"
+#include "htmlSanitize.h"
 #include "htmshell.h"
 #include "hubConnect.h"
 #include "hui.h"
 #include "jsonParse.h"
 #include "obscure.h"
 #include "regexHelper.h"
 #include "suggest.h"
 #include "trackDb.h"
 #include "trackHub.h"
 #include "web.h"
 
 char *cartJsonOptionalParam(struct hash *paramHash, char *name)
 /* Convenience function for a CartJsonHandler function: Look up name in paramHash.
  * Return the string contained in its jsonElement value, or NULL if not found. */
 {
@@ -659,32 +660,35 @@
 static char *hAssemblyDescription(char *db)
 /* Return a string containing db's description.html, or NULL if not found. */
 //#*** LIBIFY: Code lifted from hgFind.c's hgPositionsHelpHtml.
 {
 char *htmlPath = hHtmlPath(db);
 char *htmlString = NULL;
 if (htmlPath != NULL)
     {
     if (fileExists(htmlPath))
 	readInGulp(htmlPath, &htmlString, NULL);
     else if (startsWith("http://" , htmlPath) ||
 	     startsWith("https://", htmlPath) ||
 	     startsWith("ftp://"  , htmlPath))
 	{
 	struct lineFile *lf = udcWrapShortLineFile(htmlPath, NULL, 256*1024);
-	htmlString = lineFileReadAll(lf);
+	char *fetched = lineFileReadAll(lf);
 	lineFileClose(&lf);
+	/* This one came in over the network from a hub, so print only what we allow. */
+	htmlString = htmlSanitize(fetched);
+	freeMem(fetched);
 	}
     }
 return htmlString;
 }
 
 static void getAssemblyInfo(struct cartJson *cj, struct hash *paramHash)
 /* Return useful things from dbDb (or track hub) and assembly description html (possibly NULL).
  * If db param is NULL, use db from cart. */
 {
 char *db = cartJsonOptionalParam(paramHash, "db");
 if (db == NULL)
     db = cartString(cj->cart, "db");
 jsonWriteString(cj->jw, "db", db);
 jsonWriteString(cj->jw, "commonName", hGenome(db));
 jsonWriteString(cj->jw, "scientificName", hScientificName(db));