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("

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("
"); variantPathPrint(variantPath); if (makeCollapsible) endCollapsibleSpan(); puts("
"); } else puts("(None; your sample was placed at the root of the phylogenetic tree)"); puts("

"); } @@ -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("%d identical samples:\n"); } else { - printf("%s\n", info->sampleId); + printf("%s\n", htmlEncode(info->sampleId)); // uploaded sample name, escape (XSS) ref = ref->next; } refsToGo = ref; displaySampleMuts(info, refAcc); if (info->imputedBases) { puts("

Base values imputed by parsimony:\n

"); puts("

"); } displayVariantPath(info->variantPath, clumpSize == 1 ? info->sampleId : "samples"); displayNearestNeighbors(info, source, db); @@ -2229,31 +2229,31 @@ else if (isRsv) puts("RGCC 2023 clade (Nextclade)" TOOLTIP("The RGCC clade assigned by Nextclade " "to the nearest neighboring sample already in the tree") ""); else puts("Nextclade lineage" TOOLTIP("The lineage assigned by Nextclade " "to the nearest neighboring sample already in the tree") ""); puts("subtree"); struct slName *si; for (si = sampleIds; si != NULL; si = si->next) { puts(""); - printf("%s", replaceChars(si->name, "|", " | ")); + printf("%s", htmlEncode(replaceChars(si->name, "|", " | "))); // escape (XSS) struct placementInfo *pi = hashFindVal(results->samplePlacements, si->name); if (pi) { if (gotClades) printf("%s", pi->nextClade ? pi->nextClade : "n/a"); if (gotLineages) printLineageTd(pi->pangoLineage, "n/a", db); } else { if (gotClades) printf("n/a"); if (gotLineages) printf("n/a"); } @@ -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("

"); struct slPair *fail; for (fail = failedSeqs; fail != NULL; fail = fail->next) - printf("%s
\n", fail->name); + printf("%s
\n", htmlEncode(fail->name)); // message quotes seq name, escape (XSS) puts("

"); } if (failedPsls) { puts("

"); struct slPair *fail; for (fail = failedPsls; fail != NULL; fail = fail->next) - printf("%s
\n", fail->name); + printf("%s
\n", htmlEncode(fail->name)); // message quotes seq name, escape (XSS) puts("

"); } if (seqInfoList == NULL) printf("

Sorry, could not align any sequences to reference well enough to place in " "the phylogenetic tree.

\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("
  • phylogenetic tree " "with your samples (Newick file)\n", results->bigTreePlusTn->forHtml); printf("
  • TSV summary of sequences and placements\n", tsvTn->forHtml); printf("
  • TSV summary of S (Spike) gene changes\n", sTsvTn->forHtml); } printf("
  • ZIP archive of subtree Newick and JSON files\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("
  • 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)"); printf("
  • 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)"); } puts(""); if (ctTn != NULL) { // Notify in opposite order of custom track creation. puts("

    Custom tracks for viewing in the Genome Browser

    "); printf("

    Added custom track of uploaded samples.

    \n"); if (subtreeCount > 0 && subtreeCount <= MAX_SUBTREE_CTS) printf("

    Added %d subtree custom track%s.

    \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("
    No reference was found for the following sequences:\n"); } int refCount = slCount(refFiles); boolean doNav = (refCount > 1); struct refMatch *ref; if (doNav) { // Make some navigation links at the top puts(""); printf("

    Your uploaded sequences matched %d different reference sequences. " "Click on these links to jump to the results for each reference.\n", refCount); puts("