c40c049765198e994676eb5b0d8f5f63fb970896
hiram
  Wed Aug 12 16:38:41 2026 -0700
fix per code review refs #38005

diff --git src/hg/hubApi/apiUtils.c src/hg/hubApi/apiUtils.c
index 9896faba317..a288be641e7 100644
--- src/hg/hubApi/apiUtils.c
+++ src/hg/hubApi/apiUtils.c
@@ -979,31 +979,33 @@
 hDisconnectCentral(&conn);
 return status;
 }
 
 char *relaySubmitOttoRequest(char *requestType, char *fromDb, char *toDb, char *email, char *comment)
 /* Relay an ottoRequest submission to genome.ucsc.edu's /submitOttoRequest
  * endpoint, for hosts that lack local hgcentral write grants (see
  * inUcscEduDomain()/onGenomeRRMachine()).  Authenticates with the shared
  * hg.conf secret 'hubApi.relaySecret', which must also be configured on
  * genome.ucsc.edu.  Returns the same status vocabulary as
  * submitOttoRequest(): "disabled", "duplicate", "rateLimited", "accepted",
  * or "error" (including when the secret isn't configured locally, or the
  * relay call itself fails). */
 {
 char *secret = cfgOption("hubApi.relaySecret");
-if (isEmpty(secret))
+/* Guard against CR/LF in the configured secret: it goes verbatim into an
+ * HTTP header below, and a stray newline there would inject headers. */
+if (isEmpty(secret) || strpbrk(secret, "\r\n"))
     return "error";
 
 struct dyString *url = dyStringNew(0);
 dyStringPrintf(url, "https://genome.ucsc.edu/cgi-bin/hubApi/submitOttoRequest"
     "?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s",
     argRequestType, cgiEncode(requestType),
     argFromGenome, cgiEncode(fromDb),
     argToGenome, cgiEncode(toDb),
     argEmail, cgiEncode(email),
     argComment, cgiEncode(comment));
 
 struct dyString *header = dyStringNew(0);
 dyStringPrintf(header, "X-Relay-Secret: %s\r\n", secret);
 int sd = netOpenHttpExt(dyStringContents(url), "GET", dyStringContents(header));
 dyStringFree(&header);