9ad04e0a0b06ec3c4f09ef1b6c3ce6be79b61c68
braney
  Sun Aug 16 11:56:56 2026 -0700
cart: validate file names read back out of the cart

Several cart variables hold the name of a file the server created for a user.
Route them through one shared check, isServerUserFilePath(), which accepts the
trash directory, the session-data directories and myVariantsDataDir, and apply
it both where values enter the cart and where the file names are used.

A few of these variables may instead hold a remote URL.  Those get their own
list and isServerUserFileOrUrl(), because the code that reads them chooses
between a fetch and a local open by looking for a protocol.

Consolidates two hand-rolled copies of the same test in blatShare.c and
customFactory.c, and drops the weaker private copy in sessionData.c.

Adds hg/utils/cartFileVarCatalog, a registry that scans the tree for a cart
value reaching a file call and reconciles what it finds against the lists in
cart.c, so a new one of these cannot be added without somebody noticing.  Its
--reconcile is quiet enough for the nightly cron the other catalogs use, and it
is what turned up seven of the names now on those lists.

refs #37623

diff --git src/hg/lib/customTrack.c src/hg/lib/customTrack.c
index fd4f8f563cf..e24a910f6cc 100644
--- src/hg/lib/customTrack.c
+++ src/hg/lib/customTrack.c
@@ -955,30 +955,36 @@
              errCatch->message->string);
         loadFailed = TRUE;
         }
     errCatchFree(&errCatch);
     // If there was a failure in loading the custom tracks, return immediately -- don't try to
     // add or merge in new custom tracks.  The cartRemove statements below will be skipped, so we
     // can try again next click.
     if (loadFailed)
         return NULL;
     }
 
 /* Layer in the user's myVariants tracks from mvCtfile_<db>. */
 char mvVar[256];
 safef(mvVar, sizeof mvVar, MYVARIANTS_FILE_VAR_PREFIX "%s", genomeDb);
 char *mvFile = cartOptionalString(cart, mvVar);
+if (isNotEmpty(mvFile) && !isServerUserFilePath(mvFile))
+    {
+    /* not a file we made;  drop the pointer rather than parse it */
+    cartRemove(cart, mvVar);
+    mvFile = NULL;
+    }
 if (isNotEmpty(mvFile) && fileExists(mvFile))
     {
     struct customTrack *mvList = NULL;
     struct errCatch *mvCatch = errCatchNew();
     if (errCatchStart(mvCatch))
         mvList = customFactoryParse(genomeDb, mvFile, TRUE, fileName, NULL);
     errCatchEnd(mvCatch);
     if (mvCatch->gotError)
         {
         if (isNotEmpty(mvCatch->message->string))
             warn("myVariants load error: %s", mvCatch->message->string);
         cartRemove(cart, mvVar);
         mvList = NULL;
         }
     errCatchFree(&mvCatch);
@@ -1111,37 +1117,37 @@
 struct customTrack *ctList =
     customTracksParseCartDetailed(genomeDb, cart, retBrowserLines, retCtFileName,
                                         NULL, NULL, &err, NULL);
 if (err)
     warn("%s", err);
 return ctList;
 }
 
 boolean customTracksExistDb(struct cart *cart, char *db, char **retCtFileName)
 /* determine if there are any custom tracks for db.  Cleanup from expired tracks */
 {
 char *ctFileVar = customTrackFileVar(db);
 char *ctFileName = cartOptionalString(cart, ctFileVar);
 if (ctFileName)
     {
-    if (fileExists(ctFileName))
+    if (isServerUserFilePath(ctFileName) && fileExists(ctFileName))
         {
         if (retCtFileName)
             *retCtFileName = ctFileName;
         return TRUE;
         }
-    /* expired custom tracks file */
+    /* expired custom tracks file, or not a file we made */
     cartRemove(cart, ctFileVar);
     cartRemovePrefix(cart, CT_PREFIX);
     }
 return FALSE;
 }
 
 boolean customTracksExist(struct cart *cart, char **retCtFileName)
 /* determine if there are any custom tracks.  Cleanup from expired tracks */
 {
 return customTracksExistDb(cart, cartString(cart, "db"), retCtFileName);
 }
 
 boolean isCustomTrack(char *track)
 /* determine if track name refers to a custom track */
 {