9b6210bc4e86c38aff5a505b3c40cb90e3a4b9b8 hiram Fri Jul 31 13:37:13 2026 -0700 pass relay secret string in http headers instead of in the URL and fixup a bare SQL injection refs #31811 diff --git src/hg/hubApi/apiUtils.c src/hg/hubApi/apiUtils.c index a1c68258f60..9ddde8a37df 100644 --- src/hg/hubApi/apiUtils.c +++ src/hg/hubApi/apiUtils.c @@ -923,31 +923,33 @@ else { /* Atomic insert with duplicate re-check, closing the race window * between the SELECT above and this INSERT. */ struct dyString *update = dyStringNew(0); sqlDyStringPrintf(update, "INSERT INTO %s (requestType, fromDb, toDb, email, comment, requestTime, status, buildDir) " "SELECT 'liftOver', '%s', '%s', '%s', '%s', now(), 0, '' " "WHERE NOT EXISTS (" " SELECT 1 FROM %s WHERE requestType='liftOver' AND " " ((fromDb='%s' AND toDb='%s') OR (fromDb='%s' AND toDb='%s'))" ")", ottoTable, fromDb, toDb, email, comment, ottoTable, fromDb, toDb, toDb, fromDb); sqlUpdate(conn, dyStringCannibalize(&update)); - int rowsAffected = sqlQuickNum(conn, "SELECT ROW_COUNT()"); + char rowCountQuery[64]; + sqlSafef(rowCountQuery, sizeof(rowCountQuery), "SELECT ROW_COUNT()"); + int rowsAffected = sqlQuickNum(conn, rowCountQuery); status = (rowsAffected > 0) ? "accepted" : "duplicate"; } } } else if (sameString(requestType, "assembly")) { struct dyString *update = dyStringNew(0); sqlDyStringPrintf(update, "INSERT INTO %s (requestType, fromDb, toDb, email, comment, requestTime, status, buildDir) " "VALUES ('assembly', '%s', '%s', '%s', '%s', now(), 0, '')", ottoTable, fromDb, toDb, email, comment); sqlUpdate(conn, dyStringCannibalize(&update)); status = "accepted"; } hDisconnectCentral(&conn); @@ -958,39 +960,41 @@ /* 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)) 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&%s=%s", + "?%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), - argRelaySecret, cgiEncode(secret)); + argComment, cgiEncode(comment)); -int sd = netUrlOpen(dyStringContents(url)); +struct dyString *header = dyStringNew(0); +dyStringPrintf(header, "X-Relay-Secret: %s\r\n", secret); +int sd = netOpenHttpExt(dyStringContents(url), "GET", dyStringContents(header)); +dyStringFree(&header); if (sd < 0) { dyStringFree(&url); return "error"; } char *redirectedUrl = NULL; if (!netSkipHttpHeaderLinesWithRedirect(sd, dyStringContents(url), &redirectedUrl)) { close(sd); dyStringFree(&url); return "error"; } dyStringFree(&url); struct dyString *body = netSlurpFile(sd); close(sd); @@ -1002,31 +1006,31 @@ return isNotEmpty(status) ? cloneString(status) : "error"; } void apiSubmitOttoRequest(char *words[MAX_PATH_INFO]) /* Internal server-to-server endpoint: record a row in the ottoRequest table * on behalf of a hubApi host that lacks its own hgcentral write grants (see * relaySubmitOttoRequest()). Requires the shared hg.conf secret * 'hubApi.relaySecret' to match, since this performs the actual database * write without the bot-challenge cookie check that /liftRequest and * /assemblyRequest do locally -- that check already ran on the relaying * host before it got here. Always answers 200 + a JSON "status" field; * never apiErrAbort()s for a duplicate/rateLimited outcome, so the relay * caller can distinguish it from a hard failure. */ { char *secret = cfgOption("hubApi.relaySecret"); -char *givenSecret = cgiOptionalString(argRelaySecret); +char *givenSecret = getenv("HTTP_X_RELAY_SECRET"); if (isEmpty(secret) || isEmpty(givenSecret) || !sameString(secret, givenSecret)) apiErrAbort(err403, err403Msg, "not authorized for endpoint '/submitOttoRequest"); char *requestType = cgiOptionalString(argRequestType); char *fromDb = cgiOptionalString(argFromGenome); char *toDb = cgiOptionalString(argToGenome); char *email = cgiOptionalString(argEmail); char *comment = cgiOptionalString(argComment); if (isEmpty(requestType) || isEmpty(fromDb) || isEmpty(toDb) || isEmpty(email) || isEmpty(comment)) apiErrAbort(err400, err400Msg, "must have all arguments: %s, %s, %s, %s, %s for endpoint '/submitOttoRequest", argRequestType, argFromGenome, argToGenome, argEmail, argComment); if (!sameString(requestType, "liftOver") && !sameString(requestType, "assembly")) apiErrAbort(err400, err400Msg, "unrecognized %s '%s' for endpoint '/submitOttoRequest", argRequestType, requestType); char *status = submitOttoRequest(requestType, fromDb, toDb, email, comment);