690cc018255362c9d1af6ed5f4fa15ae7a0fc81d
max
  Wed Aug 5 11:26:22 2026 -0700
changes after automated code review

hgLogin: sign the pending social identity with hmacMd5() instead of a hand-built
cookieSalt-prefix + MD5. The salt now keys the HMAC rather than being prepended to
the hashed message. This is the signature that closes the account-takeover hole in
the OAuth account chooser, so it should use a real MAC. It also now fails closed:
with login.cookieSalt unset the old code hashed an empty salt, so anyone could
compute a valid signature and the protection was silently absent - hgLogin refuses
to run a social login without the salt. Ordinary login pages never reach this check.
Pending identities in flight across the upgrade no longer validate; those users are
asked to sign in again.

lib/hmac.c: hmacSha1() and hmacMd5() sized their hex buffers at exactly 40 and 32
chars, so the last sprintf wrote its terminating zero one byte past the array and
the following strlen read out of bounds. Grow both by one and use cloneString().
Output is unchanged, verified against openssl dgst -hmac.

userAccounts.css: drop the #helpBox rule, unused since the last id="helpBox" was
removed from hgLogin.c.

mirrorManual, ex.hg.conf: the login section claimed "three" extra ways to sign in
when there are two (external provider, one-time email link). Also document that
login.cookieSalt is required for social sign-in, now that the check fails closed.
Edited mirrorManual.txt and regenerated the html with mirrorDocs/make, since the
html is pandoc output and hand edits there do not survive.

refs #37984

diff --git src/product/mirrorManual.txt src/product/mirrorManual.txt
index 38d50177ff0..9a1d401da09 100644
--- src/product/mirrorManual.txt
+++ src/product/mirrorManual.txt
@@ -1408,32 +1408,33 @@
   through the CSP/nonce system. Feel free to send us the security report from
   your scanner when in doubt.
 * Our CSP/nonce system also secures the Javascript libraries. We use customized jquery libraries 
   from the mid-2000s. Some of these are outdated, so some security scanners deem
   them unsafe, but our use of CSP and a nonce means that possible XSS problems cannot be exploited. The 
   security scanners also cannot detect that. To reduce these warnings, we are in the process of upgrading our 
   Javascript dependencies where possible.
 
 # User login: social sign-in and email sign-in links
 
 User accounts on the Genome Browser are handled by the hgLogin CGI, with the accounts stored
 in the gbMembers table of the hgcentral database. An account lets a user save and share named
 sessions. Enabling the login system itself (the login.systemName, wiki.host and cookie
 settings) is described at http://genomewiki.ucsc.edu/index.php/Enabling_hgLogin and in the
 comments of the example hg.conf. Once login is enabled, users can register with a username
-and password. The settings below add three optional, and by default disabled, extra ways to
-sign in.
+and password. The settings below add two more ways to sign in, both optional and both off
+until you configure them: with an external identity provider, or with a one-time link sent by
+email.
 
 A note on secrets first: the file cgi-bin/hg.conf is part of the source tree and is not
 private, so it must never contain passwords or OAuth client secrets. Put all secrets in
 cgi-bin/hg.conf.private, a file that hg.conf includes and that is not shared. Non-secret
 settings such as the provider list, labels and endpoint URLs can live in either file.
 
 ## Social sign-in (Google, ORCID, GitHub, or any OpenID Connect provider)
 
 You can let users sign in with an external identity provider using OAuth 2.0 / OpenID
 Connect. First register an OAuth client with each provider and set its "redirect" or
 "callback" URL to your hgLogin URL exactly, for example
 https://yourbrowser.university.edu/cgi-bin/hgLogin . You create the client id and secret at
 each provider's developer console:
 
     Google:  https://console.cloud.google.com/auth/clients
@@ -1466,30 +1467,33 @@
     login.oauth.myuni.clientSecret=xxxx
     login.oauth.myuni.issuer=https://idp.myuni.edu
     # ... or, instead of the issuer line, spell out the endpoints:
     login.oauth.myuni.authUrl=https://idp.myuni.edu/authorize
     login.oauth.myuni.tokenUrl=https://idp.myuni.edu/token
     login.oauth.myuni.userinfoUrl=https://idp.myuni.edu/userinfo
     login.oauth.myuni.scopes=openid email profile
 
 Two OpenID Connect federations are useful for reaching many universities at once, without
 registering separately with each institution. Register with them and configure them like any
 other OIDC provider above (a label, client id/secret and issuer):
 
     CILogon, for US universities:        https://cilogon.org/oauth2/register
     LifeScience RI, for EU universities: https://services.aai.lifescience-ri.eu/spreg/auth
 
+login.cookieSalt must also be set, to a secret random string: it is the key that signs the
+identity handed back from the provider, so hgLogin refuses to run a social login without it.
+
 A provider's button appears on the login and sign-up pages only when both its client id and
 secret are set, so unconfigured mirrors are unaffected. When a provider reports an email
 address that it has verified, and that address matches an existing account, that account is
 automatically linked to the new identity and the user is signed in; if the address matches
 several accounts, the user is asked which one to use. GitHub is a plain OAuth 2.0 provider
 rather than OpenID Connect and is handled as a special case; it is the only non-OIDC provider
 supported without extra code. (The older, un-prefixed keys login.google.clientId and
 login.orcid.clientId are still recognized for backward compatibility.)
 
 ## Passwordless email sign-in link, and changing the account email
 
     login.emailLink=on
 
 When this is on, the login page offers an "Email me a sign-in link" option: the user types
 their email address and receives a one-time link that signs them in without a password, which