1a85cdaa59f02695ccecc1aac8a9d40bac188064
max
  Sun Sep 6 07:10:47 2026 -0700
hgTrackUi: tighten the dataVersion path check, refs #38268

checkDataVersion() decides whether a dataVersion setting that looks like a local
path may be opened.  Do that test on the path after $D substitution rather than
on the raw setting, and accept only a plain path in the /gbdb tree.  A quickLifted
track no longer needs a case of its own, its file is under /gbdb as well.

diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index 134c01e9acc..41aba5d4191 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -10789,58 +10789,77 @@
 AllocArray(fieldNames, fieldCount);
 AllocArray(fieldVals, fieldCount);
 int i;
 struct slPair *field;
 for (i=0, field=fields; i<fieldCount; i++, field=field->next)
     {
     char buf[64];
     safef(buf, sizeof buf, "$<%s>", field->name);
     fieldNames[i] = cloneString(buf);
     fieldVals[i] = (char *)field->val;
     }
 struct dyString *fUrl = subMulti(eUrl->string, fieldCount, fieldNames, fieldVals);
     return fUrl->string;
 }
 
+static boolean isPublicDataPath(char *path)
+/* Return TRUE if path names a file in the /gbdb tree.  Only a plain path counts, a ".."
+ * component makes the name mean something outside that tree, so it is not accepted. */
+{
+if (!startsWith("/gbdb/", path))
+    return FALSE;
+char *s = path + strlen("/gbdb/");
+while (s != NULL && s[0] != '\0')
+    {
+    if (s[0] == '.' && s[1] == '.' && (s[2] == '/' || s[2] == '\0'))
+        return FALSE;
+    s = strchr(s, '/');
+    if (s != NULL)
+        s += 1;
+    }
+return TRUE;
+}
+
 char *checkDataVersion(char *database, struct trackDb *tdb)
 /* see if trackDb has a dataVersion setting and check that file for version */
 {
 // try the metadata
 metadataForTable(database, tdb, NULL);
 char *version = (char *)metadataFindValue(tdb, "dataVersion");
 
 // try trackDb itself, this automatically will go up the hierarchy
 if (version == NULL)
     version = trackDbSetting(tdb, "dataVersion");
 
 if (version != NULL && startsWith("/", version))
     {
     // dataVersion can also be the path to a local file, for otto tracks.
     // For quickLifted tracks the file lives on the source assembly, so
     // substitute $D using quickLiftDb rather than the destination database.
     char *liftDb = trackDbSetting(tdb, "quickLiftDb");
     char *resolveDb = trackHubSkipHubName(liftDb ? liftDb : database);
-    // A hub is user-supplied, so a hub track may not name just any local file.
-    // Paths under /gbdb are the exception: that tree is public data, mirrored on
-    // hgdownload, so reading one discloses nothing.  Curated-hub assemblies need
-    // this - hs1 and friends are served to the browser as a hub, which makes their
-    // otto tracks hub tracks, and without it hgTrackUi prints the raw path where
-    // the version should be.
-    if (liftDb != NULL || startsWith("/gbdb/", version) ||
+    char *path = replaceInUrl(version, "", NULL, resolveDb, "", 0, 0, tdb->track, FALSE, NULL);
+    // A hub is user-supplied, so a hub track may not name just any local file.  Paths under
+    // /gbdb are the exception: that tree is public data, mirrored on hgdownload, so reading
+    // one discloses nothing.  Curated-hub assemblies need this - hs1 and friends are served
+    // to the browser as a hub, which makes their otto tracks hub tracks, and without it
+    // hgTrackUi prints the raw path where the version should be.  quickLifted tracks land
+    // here too, their dataVersion file is under /gbdb on the source assembly.  $D is
+    // substituted before the test, since on a quickLifted track it comes from the hub.
+    if (isPublicDataPath(path) ||
         (!trackHubDatabase(database) && !isHubTrack(tdb->table)))
         {
-        char *path = replaceInUrl(version, "", NULL, resolveDb, "", 0, 0, tdb->track, FALSE, NULL);
         struct lineFile* lf = lineFileMayOpen(path, TRUE);
         if (lf)
             version = lineFileReadAll(lf);
         else
             version = NULL;
         lineFileClose(&lf);
         }
     }
 return version;
 }
 
 void printDataVersion(char *database, struct trackDb *tdb)
 /* If this annotation has a dataVersion setting, print it.
  * check hgFixed.trackVersion, meta data and trackDb 'dataVersion'. */
 {