b03051dec6a7b9d132995e809f378bc3277a987a
max
  Thu Aug 27 01:18:06 2026 -0700
escape search terms and uploaded sequence names (XSS), refs #38057

#Preview2 week - bugs introduced now will need a build patch to fix
#Preview2 week - bugs introduced now will need a build patch to fix
Follow-up to the QA review of dbd10edb43b.

hgFileSearch and hgTracks track search: the name/description/simple search
boxes echoed the search term into a value attribute unescaped.  The earlier
pass escaped the hgFileSearch simple box only, which sits inside a
USE_TABS block that is not compiled.

hgPhyloPlace: uploaded sequence names are echoed in the QC failure
messages from vcfFromFasta, in the subtree summary table, in the
placement and mutation-path headings, in the subtree download links and
in the no-reference-match list.  All now go through htmlEncode.

hgLiftOver: the failure file was read back while its FILE* was still
open and unflushed, so the "Failed input regions" block always came out
empty.  Close mapped and unmapped right after liftOver writes them.

diff --git src/hg/hgPhyloPlace/phyloPlace.c src/hg/hgPhyloPlace/phyloPlace.c
index d18a38cbb66..c12f47b7476 100644
--- src/hg/hgPhyloPlace/phyloPlace.c
+++ src/hg/hgPhyloPlace/phyloPlace.c
@@ -820,31 +820,31 @@
         printf("%s: ", vpn->nodeName);
     struct singleNucChange *snc;
     for (snc = vpn->sncList;  snc != NULL;  snc = snc->next)
         {
         if (snc != vpn->sncList)
             printf(", ");
         printf("%c%d%c", snc->parBase, snc->chromStart+1, snc->newBase);
         }
     }
 }
 
 static void displayVariantPath(struct variantPathNode *variantPath, char *sampleId)
 /* Display mutations on the path to this sample. */
 {
 printf("<p>Mutations along the path from the root of the phylogenetic tree to %s:\n",
-       sampleId);
+       htmlEncode(sampleId)); // uploaded sample name, escape (XSS)
 if (variantPath)
     {
     boolean makeCollapsible = (variantPathCountMuts(variantPath) > 20);
     if (makeCollapsible)
         beginCollapsibleSpan();
     puts("<br>");
     variantPathPrint(variantPath);
     if (makeCollapsible)
         endCollapsibleSpan();
     puts("<br>");
     }
 else
     puts("(None; your sample was placed at the root of the phylogenetic tree)");
 puts("</p>");
 }
@@ -1311,36 +1311,36 @@
     struct placementInfo *info = ref->val;
     if (clumpSize > 1)
         {
         // Sort identical samples alphabetically:
         struct slName *sortedSamples = NULL;
         int i;
         for (i = 0, ref = refsToGo;  ref != NULL && i < clumpSize;  ref = ref->next, i++)
             {
             info = ref->val;
             slNameAddHead(&sortedSamples, info->sampleId);
             }
         slNameSort(&sortedSamples);
         printf("<b>%d identical samples:</b>\n<ul>\n", clumpSize);
         struct slName *sln;
         for (sln = sortedSamples;  sln != NULL;  sln = sln->next)
-            printf("<li><b>%s</b>\n", sln->name);
+            printf("<li><b>%s</b>\n", htmlEncode(sln->name)); // uploaded sample name, escape (XSS)
         puts("</ul>");
         }
     else
         {
-        printf("<b>%s</b>\n", info->sampleId);
+        printf("<b>%s</b>\n", htmlEncode(info->sampleId)); // uploaded sample name, escape (XSS)
         ref = ref->next;
         }
     refsToGo = ref;
     displaySampleMuts(info, refAcc);
     if (info->imputedBases)
         {
         puts("<p>Base values imputed by parsimony:\n<ul>");
         struct baseVal *bv;
         for (bv = info->imputedBases;  bv != NULL;  bv = bv->next)
             printf("<li>%d: %s\n", bv->chromStart+1, bv->val);
         puts("</ul>");
         puts("</p>");
         }
     displayVariantPath(info->variantPath, clumpSize == 1 ? info->sampleId : "samples");
     displayNearestNeighbors(info, source, db);
@@ -2229,31 +2229,31 @@
 else if (isRsv)
     puts("<th>RGCC 2023 clade (Nextclade)"
          TOOLTIP("The RGCC clade assigned by Nextclade "
                  "to the nearest neighboring sample already in the tree")
          "</th>");
 else
     puts("<th>Nextclade lineage"
          TOOLTIP("The lineage assigned by Nextclade "
                  "to the nearest neighboring sample already in the tree")
          "</th>");
 puts("<th>subtree</th></tr>");
 struct slName *si;
 for (si = sampleIds;  si != NULL;  si = si->next)
     {
     puts("<tr>");
-    printf("<th>%s</td>", replaceChars(si->name, "|", " | "));
+    printf("<th>%s</td>", htmlEncode(replaceChars(si->name, "|", " | "))); // escape (XSS)
     struct placementInfo *pi = hashFindVal(results->samplePlacements, si->name);
     if (pi)
         {
         if (gotClades)
             printf("<td>%s</td>", pi->nextClade ? pi->nextClade : "n/a");
         if (gotLineages)
             printLineageTd(pi->pangoLineage, "n/a", db);
         }
     else
         {
         if (gotClades)
             printf("<td>n/a</td>");
         if (gotLineages)
             printf("<td>n/a</td>");
         }
@@ -3568,39 +3568,39 @@
 if (lfLooksLikeFasta(lf))
     {
     struct slPair *failedSeqs;
     struct slPair *failedPsls;
     struct hashOrMmHash *treeNames = NULL;
     // We need to check uploaded names in fasta only for original usher, not usher-sampled(-server).
     if (!serverIsConfigured(org) && !endsWith(usherPath, "-sampled"))
         treeNames = getTreeNames(sampleNameFile, protobufPath, &bigTree, FALSE, &startTime);
     vcfTn = vcfFromFasta(lf, org, refName, refGenome, maskSites, treeNames,
                          &sampleIds, &seqInfoList, &failedSeqs, &failedPsls, &startTime);
     if (failedSeqs)
         {
         puts("<p>");
         struct slPair *fail;
         for (fail = failedSeqs;  fail != NULL;  fail = fail->next)
-            printf("%s<br>\n", fail->name);
+            printf("%s<br>\n", htmlEncode(fail->name)); // message quotes seq name, escape (XSS)
         puts("</p>");
         }
     if (failedPsls)
         {
         puts("<p>");
         struct slPair *fail;
         for (fail = failedPsls;  fail != NULL;  fail = fail->next)
-            printf("%s<br>\n", fail->name);
+            printf("%s<br>\n", htmlEncode(fail->name)); // message quotes seq name, escape (XSS)
         puts("</p>");
         }
     if (seqInfoList == NULL)
         printf("<p>Sorry, could not align any sequences to reference well enough to place in "
                "the phylogenetic tree.</p>\n");
     isFasta = TRUE;
     }
 else if (lfLooksLikeVcf(lf))
     {
     vcfTn = checkAndSaveVcf(lf, refGenome, maskSites, &seqInfoList, &sampleIds);
     reportTiming(&startTime, "check uploaded VCF");
     }
 else
     {
     subtreesOnly = TRUE;
@@ -3826,49 +3826,49 @@
         printf("<li><a href='%s' download>phylogenetic tree "
                "with your samples (Newick file)</a>\n", results->bigTreePlusTn->forHtml);
         printf("<li><a href='%s' download>TSV summary of sequences and placements</a>\n",
                tsvTn->forHtml);
         printf("<li><a href='%s' download>TSV summary of S (Spike) gene changes</a>\n",
                sTsvTn->forHtml);
         }
     printf("<li><a href='%s' download>ZIP archive of subtree Newick and JSON files</a>\n",
            zipTn->forHtml);
     // For now, leave in the individual links so I don't break anybody's pipeline that's
     // scraping this page...
     for (ix = 0, ti = results->subtreeInfoList;  ti != NULL;  ti = ti->next, ix++)
         {
         int subtreeUserSampleCount = slCount(ti->subtreeUserSampleIds);
         printf("<li><a href='%s' download>Subtree with %s", ti->subtreeTn->forHtml,
-               ti->subtreeUserSampleIds->name);
+               htmlEncode(ti->subtreeUserSampleIds->name)); // uploaded sample name, escape (XSS)
         if (subtreeUserSampleCount > 10)
             printf(" and %d other samples", subtreeUserSampleCount - 1);
         else
             {
             struct slName *sln;
             for (sln = ti->subtreeUserSampleIds->next;  sln != NULL;  sln = sln->next)
-                printf(", %s", sln->name);
+                printf(", %s", htmlEncode(sln->name)); // uploaded sample name, escape (XSS)
             }
         puts(" (Newick file)</a>");
         printf("<li><a href='%s' download>Auspice JSON for subtree with %s",
-               jsonTns[ix]->forHtml, ti->subtreeUserSampleIds->name);
+               jsonTns[ix]->forHtml, htmlEncode(ti->subtreeUserSampleIds->name)); // escape (XSS)
         if (subtreeUserSampleCount > 10)
             printf(" and %d other samples", subtreeUserSampleCount - 1);
         else
             {
             struct slName *sln;
             for (sln = ti->subtreeUserSampleIds->next;  sln != NULL;  sln = sln->next)
-                printf(", %s", sln->name);
+                printf(", %s", htmlEncode(sln->name)); // uploaded sample name, escape (XSS)
             }
         puts(" (JSON file)</a>");
         }
     puts("</ul>");
 
     if (ctTn != NULL)
         {
         // Notify in opposite order of custom track creation.
         puts("<h3>Custom tracks for viewing in the Genome Browser</h3>");
         printf("<p>Added custom track of uploaded samples.</p>\n");
         if (subtreeCount > 0 && subtreeCount <= MAX_SUBTREE_CTS)
             printf("<p>Added %d subtree custom track%s.</p>\n",
                    subtreeCount, (subtreeCount > 1 ? "s" : ""));
         ctFile = urlFromTn(ctTn);
         cartSaveSession(cart);
@@ -4204,31 +4204,31 @@
     if (! fileExists(nextcladeIndex))
         errAbort("config for '%s' specifies nextcladeIndex file '%s' but it does not exist",
                  org, nextcladeIndex);
     struct refMatch *refFiles = NULL;
     struct slName *noMatches = NULL;
     int startTime = clock1000();
     if (lfLooksLikeFasta(lf))
         refFiles = matchSamplesWithReferences(org, nextcladeIndex, lf, &noMatches, &startTime);
     else
         refFiles = matchNamesWithReference(org, lf, &noMatches, &startTime);
     if (noMatches != NULL)
         {
         printf("<br>No reference was found for the following sequences:\n<ul>\n");
         struct slName *noMatch;
         for (noMatch = noMatches;  noMatch != NULL;  noMatch = noMatch->next)
-            printf("<li>%s\n", noMatch->name);
+            printf("<li>%s\n", htmlEncode(noMatch->name)); // uploaded sample name, escape (XSS)
         puts("</ul>");
         }
     int refCount = slCount(refFiles);
     boolean doNav = (refCount > 1);
     struct refMatch *ref;
     if (doNav)
         {
         // Make some navigation links at the top
         puts("<a name='resultNavTop'></a>");
         printf("<p>Your uploaded sequences matched %d different reference sequences.  "
                "Click on these links to jump to the results for each reference.\n",
                refCount);
         puts("<ul>");
         for (ref = refFiles;  ref != NULL;  ref = ref->next)
             printf("<li><a href='#results_%s'>%s</a>\n", ref->acc, ref->description);