From 752e8025e90f2ee4c4ed56f49bfdef8ac4ff1fc5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 14:24:12 +0100 Subject: [PATCH 01/11] Improve `GitHubApiDetails` types and documentation --- src/api-client.ts | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/api-client.ts b/src/api-client.ts index 6a5e99468c..80872c31eb 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -43,20 +43,38 @@ const GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"; */ export const DO_NOT_RETRY_STATUSES = [400, 410, 422, 451]; +/** + * Represents information about a GitHub instance, how to authenticate to it, + * and how to authenticate to other repos on it. + */ export type GitHubApiCombinedDetails = GitHubApiDetails & GitHubApiExternalRepoDetails; -export interface GitHubApiDetails { - auth: string; +/** Represents information about a GitHub instance. */ +export type GitHubInstance = { + /** The base URL of the GitHub instance, such as `https://github.com`. */ url: string; + /** + * The API base URL of the GitHub instance, such as `https://api.github.com`. + * Typically this is the `api.` subdomain of the `url` domain. + */ apiURL: string | undefined; -} +}; + +/** Represents a {@link GitHubInstance} with a token for accessing the API. */ +export type GitHubApiDetails = GitHubInstance & { + /** The token to use for accessing the API of the GitHub instance. */ + auth: string; +}; -export interface GitHubApiExternalRepoDetails { +/** + * Represents information for accessing a repository on the same GitHub instance as the Action + * is currently running on. + */ +export type GitHubApiExternalRepoDetails = { + /** An optional token for accessing repo(s) on the same GitHub instance. */ externalRepoAuth?: string; - url: string; - apiURL: string | undefined; -} +}; /** * Gets the configuration for the private registry authentication proxy, From 7538fa2081b0390132157106cb1de472d1a192d9 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 14:42:12 +0100 Subject: [PATCH 02/11] Move some definitions out of `util.ts` into a new `github.ts` module --- lib/entry-points.js | 90 +++++++++++++++++++++--------------------- src/api-client.ts | 3 +- src/database-upload.ts | 3 +- src/github.test.ts | 69 ++++++++++++++++++++++++++++++++ src/github.ts | 62 +++++++++++++++++++++++++++++ src/setup-codeql.ts | 5 ++- src/status-report.ts | 2 +- src/util.test.ts | 60 ---------------------------- src/util.ts | 48 ---------------------- 9 files changed, 184 insertions(+), 158 deletions(-) create mode 100644 src/github.test.ts create mode 100644 src/github.ts diff --git a/lib/entry-points.js b/lib/entry-points.js index 4bda956850..da8986fc50 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -42967,11 +42967,11 @@ var require_tracingPolicy = __commonJS({ var import_log = require_log3(); var import_core_util = require_commonjs4(); var import_restError = require_restError3(); - var import_util58 = require_internal3(); + var import_util59 = require_internal3(); var tracingPolicyName2 = "tracingPolicy"; function tracingPolicy2(options = {}) { const userAgentPromise = (0, import_userAgent.getUserAgentValue)(options.userAgentPrefix); - const sanitizer = new import_util58.Sanitizer({ + const sanitizer = new import_util59.Sanitizer({ additionalAllowedQueryParameters: options.additionalAllowedQueryParameters }); const tracingClient = tryCreateTracingClient(); @@ -46597,11 +46597,11 @@ var require_response2 = __commonJS({ }); module2.exports = __toCommonJS2(response_exports); var import_core_rest_pipeline = require_commonjs6(); - var import_util58 = require_util9(); + var import_util59 = require_util9(); var originalResponse = /* @__PURE__ */ Symbol("Original FullOperationResponse"); function toCompatResponse2(response, options) { - let request3 = (0, import_util58.toWebResourceLike)(response.request); - let headers = (0, import_util58.toHttpHeadersLike)(response.headers); + let request3 = (0, import_util59.toWebResourceLike)(response.request); + let headers = (0, import_util59.toHttpHeadersLike)(response.headers); if (options?.createProxy) { return new Proxy(response, { get(target, prop, receiver) { @@ -46642,7 +46642,7 @@ var require_response2 = __commonJS({ return { ...compatResponse, headers, - request: (0, import_util58.toPipelineRequest)(compatResponse.request) + request: (0, import_util59.toPipelineRequest)(compatResponse.request) }; } } @@ -46749,7 +46749,7 @@ var require_requestPolicyFactoryPolicy = __commonJS({ requestPolicyFactoryPolicyName: () => requestPolicyFactoryPolicyName2 }); module2.exports = __toCommonJS2(requestPolicyFactoryPolicy_exports); - var import_util58 = require_util9(); + var import_util59 = require_util9(); var import_response = require_response2(); var HttpPipelineLogLevel2 = /* @__PURE__ */ ((HttpPipelineLogLevel22) => { HttpPipelineLogLevel22[HttpPipelineLogLevel22["ERROR"] = 1] = "ERROR"; @@ -46773,14 +46773,14 @@ var require_requestPolicyFactoryPolicy = __commonJS({ async sendRequest(request3, next) { let httpPipeline = { async sendRequest(httpRequest) { - const response2 = await next((0, import_util58.toPipelineRequest)(httpRequest)); + const response2 = await next((0, import_util59.toPipelineRequest)(httpRequest)); return (0, import_response.toCompatResponse)(response2, { createProxy: true }); } }; for (const factory of orderedFactories) { httpPipeline = factory.create(httpPipeline, mockRequestPolicyOptions); } - const webResourceLike = (0, import_util58.toWebResourceLike)(request3, { createProxy: true }); + const webResourceLike = (0, import_util59.toWebResourceLike)(request3, { createProxy: true }); const response = await httpPipeline.sendRequest(webResourceLike); return (0, import_response.toPipelineResponse)(response); } @@ -46815,12 +46815,12 @@ var require_httpClientAdapter = __commonJS({ }); module2.exports = __toCommonJS2(httpClientAdapter_exports); var import_response = require_response2(); - var import_util58 = require_util9(); + var import_util59 = require_util9(); function convertHttpClient2(requestPolicyClient) { return { sendRequest: async (request3) => { const response = await requestPolicyClient.sendRequest( - (0, import_util58.toWebResourceLike)(request3, { createProxy: true }) + (0, import_util59.toWebResourceLike)(request3, { createProxy: true }) ); return (0, import_response.toPipelineResponse)(response); } @@ -46858,7 +46858,7 @@ var require_commonjs9 = __commonJS({ disableKeepAlivePolicyName: () => import_disableKeepAlivePolicy.disableKeepAlivePolicyName, requestPolicyFactoryPolicyName: () => import_requestPolicyFactoryPolicy.requestPolicyFactoryPolicyName, toCompatResponse: () => import_response.toCompatResponse, - toHttpHeadersLike: () => import_util58.toHttpHeadersLike + toHttpHeadersLike: () => import_util59.toHttpHeadersLike }); module2.exports = __toCommonJS2(src_exports); var import_extendedClient = require_extendedClient(); @@ -46866,7 +46866,7 @@ var require_commonjs9 = __commonJS({ var import_requestPolicyFactoryPolicy = require_requestPolicyFactoryPolicy(); var import_disableKeepAlivePolicy = require_disableKeepAlivePolicy(); var import_httpClientAdapter = require_httpClientAdapter(); - var import_util58 = require_util9(); + var import_util59 = require_util9(); } }); @@ -169587,7 +169587,6 @@ function checkSchema(schema, obj, options = {}, path30 = "") { // src/util.ts var BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; var BROKEN_VERSIONS = ["0.0.0-20211207"]; -var GITHUB_DOTCOM_URL = "https://github.com"; var DEFAULT_DEBUG_ARTIFACT_NAME = "debug-artifacts"; var DEFAULT_DEBUG_DATABASE_NAME = "db"; var DEFAULT_RESERVED_RAM_SCALING_FACTOR = 0.05; @@ -169803,34 +169802,6 @@ function getGeneratedSuitePath(config, language) { "config-queries.qls" ); } -function parseGitHubUrl(inputUrl) { - const originalUrl = inputUrl; - if (inputUrl.indexOf("://") === -1) { - inputUrl = `https://${inputUrl}`; - } - if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) { - throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`); - } - let url2; - try { - url2 = new URL(inputUrl); - } catch { - throw new ConfigurationError(`"${originalUrl}" is not a valid URL`); - } - if (url2.hostname === "github.com" || url2.hostname === "api.github.com") { - return GITHUB_DOTCOM_URL; - } - if (url2.pathname.indexOf("/api/v3") !== -1) { - url2.pathname = url2.pathname.substring(0, url2.pathname.indexOf("/api/v3")); - } - if (url2.hostname.startsWith("api.")) { - url2.hostname = url2.hostname.substring(4); - } - if (!url2.pathname.endsWith("/")) { - url2.pathname = `${url2.pathname}/`; - } - return url2.toString(); -} var CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR = "CODEQL_ACTION_WARNED_ABOUT_VERSION"; var hasBeenWarnedAboutVersion = false; function checkGitHubVersionInRange(version, logger2) { @@ -170688,6 +170659,37 @@ retry.VERSION = VERSION7; // src/api-client.ts var import_undici = __toESM(require_undici()); +// src/github.ts +var GITHUB_DOTCOM_URL = "https://github.com"; +function parseGitHubUrl(inputUrl) { + const originalUrl = inputUrl; + if (inputUrl.indexOf("://") === -1) { + inputUrl = `https://${inputUrl}`; + } + if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) { + throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`); + } + let url2; + try { + url2 = new URL(inputUrl); + } catch { + throw new ConfigurationError(`"${originalUrl}" is not a valid URL`); + } + if (url2.hostname === "github.com" || url2.hostname === "api.github.com") { + return GITHUB_DOTCOM_URL; + } + if (url2.pathname.indexOf("/api/v3") !== -1) { + url2.pathname = url2.pathname.substring(0, url2.pathname.indexOf("/api/v3")); + } + if (url2.hostname.startsWith("api.")) { + url2.hostname = url2.hostname.substring(4); + } + if (!url2.pathname.endsWith("/")) { + url2.pathname = `${url2.pathname}/`; + } + return url2.toString(); +} + // src/repository.ts function getRepositoryNwo() { return getRepositoryNwoFromEnv("GITHUB_REPOSITORY"); @@ -183046,7 +183048,7 @@ var import_async = __toESM(require_async(), 1); var import_path7 = require("path"); // node_modules/archiver/lib/error.js -var import_util36 = __toESM(require("util"), 1); +var import_util37 = __toESM(require("util"), 1); var ERROR_CODES = { ABORTED: "archive was aborted", DIRECTORYDIRPATHREQUIRED: "diretory dirpath argument must be a non-empty string value", @@ -183071,7 +183073,7 @@ function ArchiverError(code, data) { this.code = code; this.data = data; } -import_util36.default.inherits(ArchiverError, Error); +import_util37.default.inherits(ArchiverError, Error); // node_modules/archiver/lib/core.js var import_readable_stream2 = __toESM(require_ours(), 1); diff --git a/src/api-client.ts b/src/api-client.ts index 80872c31eb..8f35d77718 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -19,16 +19,15 @@ import { getEnv, exportEnvVar, } from "./environment"; +import { GITHUB_DOTCOM_URL, parseGitHubUrl } from "./github"; import { Logger } from "./logging"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { asHTTPError, ConfigurationError, getRequiredEnvParam, - GITHUB_DOTCOM_URL, GitHubVariant, GitHubVersion, - parseGitHubUrl, parseMatrixInput, } from "./util"; diff --git a/src/database-upload.ts b/src/database-upload.ts index 9e4339fd47..a79669436b 100644 --- a/src/database-upload.ts +++ b/src/database-upload.ts @@ -12,11 +12,12 @@ import { type CodeQL } from "./codeql"; import { Config } from "./config-utils"; import { Feature } from "./feature-flags"; import * as gitUtils from "./git-utils"; +import { parseGitHubUrl } from "./github"; import { Logger, withGroupAsync } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import { RepositoryNwo } from "./repository"; import * as util from "./util"; -import { asHTTPError, bundleDb, CleanupLevel, parseGitHubUrl } from "./util"; +import { asHTTPError, bundleDb, CleanupLevel } from "./util"; /** Information about a database upload. */ export interface DatabaseUploadResult { diff --git a/src/github.test.ts b/src/github.test.ts new file mode 100644 index 0000000000..8645f493bb --- /dev/null +++ b/src/github.test.ts @@ -0,0 +1,69 @@ +import test from "ava"; + +import * as github from "./github"; +import { setupTests } from "./testing-utils"; + +setupTests(test); + +test("parseGitHubUrl", (t) => { + t.deepEqual(github.parseGitHubUrl("github.com"), "https://github.com"); + t.deepEqual( + github.parseGitHubUrl("https://github.com"), + "https://github.com", + ); + t.deepEqual( + github.parseGitHubUrl("https://api.github.com"), + "https://github.com", + ); + t.deepEqual( + github.parseGitHubUrl("https://github.com/foo/bar"), + "https://github.com", + ); + + t.deepEqual( + github.parseGitHubUrl("github.example.com"), + "https://github.example.com/", + ); + t.deepEqual( + github.parseGitHubUrl("https://github.example.com"), + "https://github.example.com/", + ); + t.deepEqual( + github.parseGitHubUrl("https://api.github.example.com"), + "https://github.example.com/", + ); + t.deepEqual( + github.parseGitHubUrl("https://github.example.com/api/v3"), + "https://github.example.com/", + ); + t.deepEqual( + github.parseGitHubUrl("https://github.example.com:1234"), + "https://github.example.com:1234/", + ); + t.deepEqual( + github.parseGitHubUrl("https://api.github.example.com:1234"), + "https://github.example.com:1234/", + ); + t.deepEqual( + github.parseGitHubUrl("https://github.example.com:1234/api/v3"), + "https://github.example.com:1234/", + ); + t.deepEqual( + github.parseGitHubUrl("https://github.example.com/base/path"), + "https://github.example.com/base/path/", + ); + t.deepEqual( + github.parseGitHubUrl("https://github.example.com/base/path/api/v3"), + "https://github.example.com/base/path/", + ); + + t.throws(() => github.parseGitHubUrl(""), { + message: '"" is not a valid URL', + }); + t.throws(() => github.parseGitHubUrl("ssh://github.com"), { + message: '"ssh://github.com" is not a http or https URL', + }); + t.throws(() => github.parseGitHubUrl("http:///::::433"), { + message: '"http:///::::433" is not a valid URL', + }); +}); diff --git a/src/github.ts b/src/github.ts new file mode 100644 index 0000000000..00d2f34351 --- /dev/null +++ b/src/github.ts @@ -0,0 +1,62 @@ +import type { GitHubInstance } from "./api-client"; +import { ActionsEnvVars, type ReadOnlyEnv } from "./environment"; +import { ConfigurationError } from "./util"; + +/** A string representing a GitHub instance address. */ +export type InstanceURL = string; + +/** + * The URL for github.com. + */ +export const GITHUB_DOTCOM_URL: InstanceURL = "https://github.com"; + +/** A string representing a GitHub API instance address. */ +export type InstanceApiURL = string; + +/** + * The URL for api.github.com. + */ +export const GITHUB_DOTCOM_API_URL: InstanceApiURL = "https://api.github.com"; + +/** + * Parses user input of a github.com or GHES URL to a canonical form. + * Removes any API prefix or suffix if one is present. + */ +export function parseGitHubUrl(inputUrl: string): string { + const originalUrl = inputUrl; + if (inputUrl.indexOf("://") === -1) { + inputUrl = `https://${inputUrl}`; + } + if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) { + throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`); + } + + let url: URL; + try { + url = new URL(inputUrl); + } catch { + throw new ConfigurationError(`"${originalUrl}" is not a valid URL`); + } + + // If we detect this is trying to be to github.com + // then return with a fixed canonical URL. + if (url.hostname === "github.com" || url.hostname === "api.github.com") { + return GITHUB_DOTCOM_URL; + } + + // Remove the API prefix if it's present + if (url.pathname.indexOf("/api/v3") !== -1) { + url.pathname = url.pathname.substring(0, url.pathname.indexOf("/api/v3")); + } + // Also consider subdomain isolation on GHES + if (url.hostname.startsWith("api.")) { + url.hostname = url.hostname.substring(4); + } + + // Normalise path to having a trailing slash for consistency + if (!url.pathname.endsWith("/")) { + url.pathname = `${url.pathname}/`; + } + + return url.toString(); +} diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index daaba174c4..c6504f7790 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -36,6 +36,7 @@ import { Feature, FeatureEnablement, } from "./feature-flags"; +import { GITHUB_DOTCOM_URL } from "./github"; import { BuiltInLanguage } from "./languages"; import { Logger } from "./logging"; import { getCodeQlVersionsForOverlayBaseDatabases } from "./overlay/caching"; @@ -100,7 +101,7 @@ async function getCodeQLBundleDownloadURL( // This GitHub instance, and the canonical Action. [apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY], // GitHub.com, and the canonical Action. - [util.GITHUB_DOTCOM_URL, CODEQL_DEFAULT_ACTION_REPOSITORY], + [GITHUB_DOTCOM_URL, CODEQL_DEFAULT_ACTION_REPOSITORY], ]; // We now filter out any duplicates. // Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork. @@ -113,7 +114,7 @@ async function getCodeQLBundleDownloadURL( const [apiURL, repository] = downloadSource; // If we've reached the final case, short-circuit the API check since we know the bundle exists and is public. if ( - apiURL === util.GITHUB_DOTCOM_URL && + apiURL === GITHUB_DOTCOM_URL && repository === CODEQL_DEFAULT_ACTION_REPOSITORY ) { break; diff --git a/src/status-report.ts b/src/status-report.ts index fc45e82d67..32418ed920 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -31,6 +31,7 @@ import { exportEnvVar, } from "./environment"; import { getRef } from "./git-utils"; +import { GITHUB_DOTCOM_URL } from "./github"; import * as json from "./json"; import type { Logger } from "./logging"; import type { OverlayBaseDatabaseDownloadStats } from "./overlay/caching"; @@ -41,7 +42,6 @@ import { ConfigurationError, getRequiredEnvParam, isInTestMode, - GITHUB_DOTCOM_URL, DiskUsage, assertNever, BuildMode, diff --git a/src/util.test.ts b/src/util.test.ts index 074310279f..bca2b452e7 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -164,66 +164,6 @@ test.serial("getExtraOptionsEnvParam() fails on invalid JSON", (t) => { process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions; }); -test("parseGitHubUrl", (t) => { - t.deepEqual(util.parseGitHubUrl("github.com"), "https://github.com"); - t.deepEqual(util.parseGitHubUrl("https://github.com"), "https://github.com"); - t.deepEqual( - util.parseGitHubUrl("https://api.github.com"), - "https://github.com", - ); - t.deepEqual( - util.parseGitHubUrl("https://github.com/foo/bar"), - "https://github.com", - ); - - t.deepEqual( - util.parseGitHubUrl("github.example.com"), - "https://github.example.com/", - ); - t.deepEqual( - util.parseGitHubUrl("https://github.example.com"), - "https://github.example.com/", - ); - t.deepEqual( - util.parseGitHubUrl("https://api.github.example.com"), - "https://github.example.com/", - ); - t.deepEqual( - util.parseGitHubUrl("https://github.example.com/api/v3"), - "https://github.example.com/", - ); - t.deepEqual( - util.parseGitHubUrl("https://github.example.com:1234"), - "https://github.example.com:1234/", - ); - t.deepEqual( - util.parseGitHubUrl("https://api.github.example.com:1234"), - "https://github.example.com:1234/", - ); - t.deepEqual( - util.parseGitHubUrl("https://github.example.com:1234/api/v3"), - "https://github.example.com:1234/", - ); - t.deepEqual( - util.parseGitHubUrl("https://github.example.com/base/path"), - "https://github.example.com/base/path/", - ); - t.deepEqual( - util.parseGitHubUrl("https://github.example.com/base/path/api/v3"), - "https://github.example.com/base/path/", - ); - - t.throws(() => util.parseGitHubUrl(""), { - message: '"" is not a valid URL', - }); - t.throws(() => util.parseGitHubUrl("ssh://github.com"), { - message: '"ssh://github.com" is not a http or https URL', - }); - t.throws(() => util.parseGitHubUrl("http:///::::433"), { - message: '"http:///::::433" is not a valid URL', - }); -}); - test("allowed API versions", async (t) => { t.is(util.apiVersionInRange("1.33.0", "1.33", "2.0"), undefined); t.is(util.apiVersionInRange("1.33.1", "1.33", "2.0"), undefined); diff --git a/src/util.ts b/src/util.ts index 566386de62..dc6ed18724 100644 --- a/src/util.ts +++ b/src/util.ts @@ -45,11 +45,6 @@ const BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; */ const BROKEN_VERSIONS = ["0.0.0-20211207"]; -/** - * The URL for github.com. - */ -export const GITHUB_DOTCOM_URL = "https://github.com"; - /** * Default name of the debugging artifact. */ @@ -465,49 +460,6 @@ export function getGeneratedSuitePath(config: Config, language: Language) { ); } -/** - * Parses user input of a github.com or GHES URL to a canonical form. - * Removes any API prefix or suffix if one is present. - */ -export function parseGitHubUrl(inputUrl: string): string { - const originalUrl = inputUrl; - if (inputUrl.indexOf("://") === -1) { - inputUrl = `https://${inputUrl}`; - } - if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) { - throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`); - } - - let url: URL; - try { - url = new URL(inputUrl); - } catch { - throw new ConfigurationError(`"${originalUrl}" is not a valid URL`); - } - - // If we detect this is trying to be to github.com - // then return with a fixed canonical URL. - if (url.hostname === "github.com" || url.hostname === "api.github.com") { - return GITHUB_DOTCOM_URL; - } - - // Remove the API prefix if it's present - if (url.pathname.indexOf("/api/v3") !== -1) { - url.pathname = url.pathname.substring(0, url.pathname.indexOf("/api/v3")); - } - // Also consider subdomain isolation on GHES - if (url.hostname.startsWith("api.")) { - url.hostname = url.hostname.substring(4); - } - - // Normalise path to having a trailing slash for consistency - if (!url.pathname.endsWith("/")) { - url.pathname = `${url.pathname}/`; - } - - return url.toString(); -} - const CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR = "CODEQL_ACTION_WARNED_ABOUT_VERSION"; From a06da1322bb5853c976c8ef9f818c13afd9d04e7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 14:48:24 +0100 Subject: [PATCH 03/11] Add functions to get the current and dotcom instance --- src/github.test.ts | 44 +++++++++++++++++++++++++++++++++++++++++++- src/github.ts | 16 ++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/github.test.ts b/src/github.test.ts index 8645f493bb..79dbb23311 100644 --- a/src/github.test.ts +++ b/src/github.test.ts @@ -1,7 +1,8 @@ import test from "ava"; +import { ActionsEnvVars } from "./environment"; import * as github from "./github"; -import { setupTests } from "./testing-utils"; +import { getTestEnv, setupTests } from "./testing-utils"; setupTests(test); @@ -67,3 +68,44 @@ test("parseGitHubUrl", (t) => { message: '"http:///::::433" is not a valid URL', }); }); + +test("getCurrentInstance throws if required env vars aren't set", (t) => { + t.throws(() => github.getCurrentInstance(getTestEnv())); + t.throws(() => { + const env = getTestEnv({ + [ActionsEnvVars.GITHUB_SERVER_URL]: "https://localhost", + }); + github.getCurrentInstance(env); + }); + t.throws(() => { + const env = getTestEnv({ + [ActionsEnvVars.GITHUB_API_URL]: "https://api.localhost", + }); + github.getCurrentInstance(env); + }); +}); + +test("getCurrentInstance constructs instance from environment", (t) => { + const url = "https://localhost"; + const apiUrl = "https://api.localhost"; + + const env = getTestEnv({ + [ActionsEnvVars.GITHUB_SERVER_URL]: url, + [ActionsEnvVars.GITHUB_API_URL]: apiUrl, + }); + const instance = github.getCurrentInstance(env); + + t.is(instance.url, url); + t.is(instance.apiURL, apiUrl); +}); + +test("getCurrentInstance constructs dotcom instance with dotcom env vars", (t) => { + const env = getTestEnv({ + [ActionsEnvVars.GITHUB_SERVER_URL]: github.GITHUB_DOTCOM_URL, + [ActionsEnvVars.GITHUB_API_URL]: github.GITHUB_DOTCOM_API_URL, + }); + const instance = github.getCurrentInstance(env); + const dotcomInstance = github.getDotcomInstance(); + + t.deepEqual(dotcomInstance, instance); +}); diff --git a/src/github.ts b/src/github.ts index 00d2f34351..8a5a2c389c 100644 --- a/src/github.ts +++ b/src/github.ts @@ -60,3 +60,19 @@ export function parseGitHubUrl(inputUrl: string): string { return url.toString(); } + +/** Retrieves a {@link GitHubInstance} describing the instance we are running on. */ +export function getCurrentInstance(env: ReadOnlyEnv): GitHubInstance { + return { + url: env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), + apiURL: env.getRequired(ActionsEnvVars.GITHUB_API_URL), + }; +} + +/** Retrieves a {@link GitHubInstance} describing the GitHub.com instance. */ +export function getDotcomInstance(): GitHubInstance { + return { + url: GITHUB_DOTCOM_URL, + apiURL: GITHUB_DOTCOM_API_URL, + }; +} From 1d407759feedf10b151ac0edf70da42a028fd719 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 17:16:32 +0100 Subject: [PATCH 04/11] Move `GitHubApiDetails` to `github.ts` --- src/api-client.ts | 42 ++++++++---------------------------------- src/github.ts | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/api-client.ts b/src/api-client.ts index 8f35d77718..c734261bf6 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -19,7 +19,12 @@ import { getEnv, exportEnvVar, } from "./environment"; -import { GITHUB_DOTCOM_URL, parseGitHubUrl } from "./github"; +import { + GITHUB_DOTCOM_URL, + parseGitHubUrl, + type GitHubApiCombinedDetails, + type GitHubApiDetails, +} from "./github"; import { Logger } from "./logging"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { @@ -31,6 +36,8 @@ import { parseMatrixInput, } from "./util"; +export type { GitHubApiDetails, GitHubApiCombinedDetails } from "./github"; + const GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"; /** @@ -42,39 +49,6 @@ const GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"; */ export const DO_NOT_RETRY_STATUSES = [400, 410, 422, 451]; -/** - * Represents information about a GitHub instance, how to authenticate to it, - * and how to authenticate to other repos on it. - */ -export type GitHubApiCombinedDetails = GitHubApiDetails & - GitHubApiExternalRepoDetails; - -/** Represents information about a GitHub instance. */ -export type GitHubInstance = { - /** The base URL of the GitHub instance, such as `https://github.com`. */ - url: string; - /** - * The API base URL of the GitHub instance, such as `https://api.github.com`. - * Typically this is the `api.` subdomain of the `url` domain. - */ - apiURL: string | undefined; -}; - -/** Represents a {@link GitHubInstance} with a token for accessing the API. */ -export type GitHubApiDetails = GitHubInstance & { - /** The token to use for accessing the API of the GitHub instance. */ - auth: string; -}; - -/** - * Represents information for accessing a repository on the same GitHub instance as the Action - * is currently running on. - */ -export type GitHubApiExternalRepoDetails = { - /** An optional token for accessing repo(s) on the same GitHub instance. */ - externalRepoAuth?: string; -}; - /** * Gets the configuration for the private registry authentication proxy, * if it is available in the environment. diff --git a/src/github.ts b/src/github.ts index 8a5a2c389c..4eef113de8 100644 --- a/src/github.ts +++ b/src/github.ts @@ -1,4 +1,3 @@ -import type { GitHubInstance } from "./api-client"; import { ActionsEnvVars, type ReadOnlyEnv } from "./environment"; import { ConfigurationError } from "./util"; @@ -18,6 +17,39 @@ export type InstanceApiURL = string; */ export const GITHUB_DOTCOM_API_URL: InstanceApiURL = "https://api.github.com"; +/** + * Represents information about a GitHub instance, how to authenticate to it, + * and how to authenticate to other repos on it. + */ +export type GitHubApiCombinedDetails = GitHubApiDetails & + GitHubApiExternalRepoDetails; + +/** Represents information about a GitHub instance. */ +export type GitHubInstance = { + /** The base URL of the GitHub instance, such as `https://github.com`. */ + url: string; + /** + * The API base URL of the GitHub instance, such as `https://api.github.com`. + * Typically this is the `api.` subdomain of the `url` domain. + */ + apiURL: string | undefined; +}; + +/** Represents a {@link GitHubInstance} with a token for accessing the API. */ +export type GitHubApiDetails = GitHubInstance & { + /** The token to use for accessing the API of the GitHub instance. */ + auth: string; +}; + +/** + * Represents information for accessing a repository on the same GitHub instance as the Action + * is currently running on. + */ +export type GitHubApiExternalRepoDetails = { + /** An optional token for accessing repo(s) on the same GitHub instance. */ + externalRepoAuth?: string; +}; + /** * Parses user input of a github.com or GHES URL to a canonical form. * Removes any API prefix or suffix if one is present. From 8feed81de01583318042a73d3790e75b825c2e8a Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 15:32:43 +0100 Subject: [PATCH 05/11] Make `auth` property on `GitHubApiDetails` optional --- lib/entry-points.js | 40 ++++++++++++++++++++-------------------- src/api-client.ts | 35 +++++++++++++++++++---------------- src/codeql.test.ts | 2 +- src/github.ts | 2 +- 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index da8986fc50..7a365551ee 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -26401,7 +26401,7 @@ var require_utils4 = __commonJS({ })(); Object.defineProperty(exports2, "__esModule", { value: true }); exports2.GitHub = exports2.defaults = exports2.context = void 0; - exports2.getOctokitOptions = getOctokitOptions2; + exports2.getOctokitOptions = getOctokitOptions; var Context = __importStar2(require_context()); var Utils = __importStar2(require_utils3()); var core_1 = (init_dist_src2(), __toCommonJS(dist_src_exports)); @@ -26417,7 +26417,7 @@ var require_utils4 = __commonJS({ } }; exports2.GitHub = core_1.Octokit.plugin(plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_paginate_rest_1.paginateRest).defaults(exports2.defaults); - function getOctokitOptions2(token, options) { + function getOctokitOptions(token, options) { const opts = Object.assign({}, options || {}); const auth2 = Utils.getAuthString(token, opts); if (auth2) { @@ -140045,7 +140045,7 @@ var require_utils10 = __commonJS({ } }; exports2.GitHub = core_1.Octokit.plugin(plugin_rest_endpoint_methods_1.restEndpointMethods, plugin_paginate_rest_1.paginateRest).defaults(exports2.defaults); - function getOctokitOptions2(token, options) { + function getOctokitOptions(token, options) { const opts = Object.assign({}, options || {}); const auth2 = Utils.getAuthString(token, opts); if (auth2) { @@ -140053,7 +140053,7 @@ var require_utils10 = __commonJS({ } return opts; } - exports2.getOctokitOptions = getOctokitOptions2; + exports2.getOctokitOptions = getOctokitOptions; } }); @@ -170755,22 +170755,22 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = const auth2 = allowExternal && apiDetails.externalRepoAuth || apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry); const requestOptions = makeProxyRequestOptions(proxy); - return new retryingOctokit( - githubUtils.getOctokitOptions(auth2, { - baseUrl: apiDetails.apiURL, - userAgent: `CodeQL-Action/${getActionVersion()}`, - log: { - debug: core6.debug, - info: core6.info, - warn: core6.warning, - error: core6.error - }, - request: requestOptions, - retry: { - doNotRetry: DO_NOT_RETRY_STATUSES - } - }) - ); + const opts = { + auth: auth2 !== void 0 ? `token ${auth2}` : void 0, + baseUrl: apiDetails.apiURL, + userAgent: `CodeQL-Action/${getActionVersion()}`, + log: { + debug: core6.debug, + info: core6.info, + warn: core6.warning, + error: core6.error + }, + request: requestOptions, + retry: { + doNotRetry: DO_NOT_RETRY_STATUSES + } + }; + return new retryingOctokit(opts); } function getApiDetails(env = getEnv()) { return { diff --git a/src/api-client.ts b/src/api-client.ts index c734261bf6..e1e5dcc0a4 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -1,5 +1,6 @@ import * as core from "@actions/core"; import * as githubUtils from "@actions/github/lib/utils"; +import type { OctokitOptions } from "@octokit/core"; import * as retry from "@octokit/plugin-retry"; import { RequestRequestOptions } from "@octokit/types"; import { @@ -133,22 +134,24 @@ function createApiClientWithDetails( (allowExternal && apiDetails.externalRepoAuth) || apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry.retry); const requestOptions = makeProxyRequestOptions(proxy); - return new retryingOctokit( - githubUtils.getOctokitOptions(auth, { - baseUrl: apiDetails.apiURL, - userAgent: `CodeQL-Action/${getActionVersion()}`, - log: { - debug: core.debug, - info: core.info, - warn: core.warning, - error: core.error, - }, - request: requestOptions, - retry: { - doNotRetry: DO_NOT_RETRY_STATUSES, - }, - }), - ); + + const opts: OctokitOptions = { + auth: auth !== undefined ? `token ${auth}` : undefined, + baseUrl: apiDetails.apiURL, + userAgent: `CodeQL-Action/${getActionVersion()}`, + log: { + debug: core.debug, + info: core.info, + warn: core.warning, + error: core.error, + }, + request: requestOptions, + retry: { + doNotRetry: DO_NOT_RETRY_STATUSES, + }, + }; + + return new retryingOctokit(opts); } export function getApiDetails(env: ReadOnlyEnv = getEnv()): GitHubApiDetails { diff --git a/src/codeql.test.ts b/src/codeql.test.ts index 3237d57723..e70dcd99f9 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -126,7 +126,7 @@ function mockReleaseApi({ }); } -function mockApiDetails(apiDetails: GitHubApiDetails) { +function mockApiDetails(apiDetails: Required) { // This is a workaround to mock `api.getApiDetails()` since it doesn't seem to be possible to // mock this directly. The difficulty is that `getApiDetails()` is called locally in // `api-client.ts`, but `sinon.stub(api, "getApiDetails")` only affects calls to diff --git a/src/github.ts b/src/github.ts index 4eef113de8..6350fb3f30 100644 --- a/src/github.ts +++ b/src/github.ts @@ -38,7 +38,7 @@ export type GitHubInstance = { /** Represents a {@link GitHubInstance} with a token for accessing the API. */ export type GitHubApiDetails = GitHubInstance & { /** The token to use for accessing the API of the GitHub instance. */ - auth: string; + auth?: string; }; /** From 3ccff0b63b712952c5bbe0a45909e0d2791f0139 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 17:28:17 +0100 Subject: [PATCH 06/11] Use `getCurrentInstance()` --- lib/entry-points.js | 19 +++++++++++-------- src/api-client.ts | 10 +++++++--- src/init-action.ts | 4 ++-- src/setup-codeql-action.ts | 6 +++--- src/upload-lib.ts | 7 ++++--- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 7a365551ee..57dd025418 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170689,6 +170689,12 @@ function parseGitHubUrl(inputUrl) { } return url2.toString(); } +function getCurrentInstance(env) { + return { + url: env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), + apiURL: env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) + }; +} // src/repository.ts function getRepositoryNwo() { @@ -170775,8 +170781,7 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = function getApiDetails(env = getEnv()) { return { auth: getRequiredInput("token"), - url: env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), - apiURL: env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) + ...getCurrentInstance(env) }; } function getApiClient(env = getEnv()) { @@ -179972,6 +179977,7 @@ async function shouldDisableCombineSarifFiles(sarifObjects, githubVersion) { return true; } async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, logger2) { + const env = getEnv(); logger2.info("Combining SARIF files using the CodeQL CLI"); const sarifObjects = sarifFiles.map(readSarifFile); const deprecationWarningMessage = gitHubVersion.type === "GitHub Enterprise Server" /* GHES */ ? "and will be removed in GitHub Enterprise Server 3.18" : "and will be removed in July 2025"; @@ -180004,8 +180010,7 @@ async function combineSarifFilesUsingCLI(sarifFiles, gitHubVersion, features, lo externalRepoAuth: getOptionalInput( "external-repository-token" ), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL") + ...getCurrentInstance(env) }; const codeQLDefaultVersionInfo = await features.getEnabledDefaultCliVersions(gitHubVersion.type); const initCodeQLResult = await initCodeQL( @@ -186406,8 +186411,7 @@ async function run3(actionState) { apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: actionState.env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), - apiURL: actionState.env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) + ...getCurrentInstance(actionState.env) }; const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger2); @@ -187441,8 +187445,7 @@ async function run6(actionState) { const apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: actionState.env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), - apiURL: actionState.env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) + ...getCurrentInstance(actionState.env) }; const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger2); diff --git a/src/api-client.ts b/src/api-client.ts index e1e5dcc0a4..48313dbba9 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -13,7 +13,6 @@ import { import type { ActionState } from "./action-common"; import { getActionVersion, getRequiredInput } from "./actions-util"; import { - ActionsEnvVars, EnvVar, ReadOnlyEnv, RegistryProxyVars, @@ -21,6 +20,7 @@ import { exportEnvVar, } from "./environment"; import { + getCurrentInstance, GITHUB_DOTCOM_URL, parseGitHubUrl, type GitHubApiCombinedDetails, @@ -154,11 +154,15 @@ function createApiClientWithDetails( return new retryingOctokit(opts); } +/** + * Gets a `GitHubApiDetails` object describing the GitHub instance we are currently running on. + * + * @param env The environment variables to read. + */ export function getApiDetails(env: ReadOnlyEnv = getEnv()): GitHubApiDetails { return { auth: getRequiredInput("token"), - url: env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), - apiURL: env.getRequired(ActionsEnvVars.GITHUB_API_URL), + ...getCurrentInstance(env), }; } diff --git a/src/init-action.ts b/src/init-action.ts index 5376763b52..f58d4ccca7 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -50,6 +50,7 @@ import { loadRepositoryProperties, RepositoryPropertyName, } from "./feature-flags/properties"; +import * as github from "./github"; import { checkInstallPython311, checkPacksForOverlayCompatibility, @@ -233,8 +234,7 @@ async function run( apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), - apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL), + ...github.getCurrentInstance(actionState.env), }; const gitHubVersion = await getGitHubVersion(); diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index 1344b66b70..757b26fac5 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -17,9 +17,10 @@ import { getGitHubVersion } from "./api-client"; import { CodeQL } from "./codeql"; import { ComputedInput, getToolsInput } from "./config/inputs"; import { getRawLanguagesNoAutodetect } from "./config-utils"; -import { ActionsEnvVars, EnvVar } from "./environment"; +import { EnvVar } from "./environment"; import { initFeatures } from "./feature-flags"; import { loadRepositoryProperties } from "./feature-flags/properties"; +import * as github from "./github"; import { initCodeQL } from "./init"; import { Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; @@ -112,8 +113,7 @@ async function run( const apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), - apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL), + ...github.getCurrentInstance(actionState.env), }; const gitHubVersion = await getGitHubVersion(); diff --git a/src/upload-lib.ts b/src/upload-lib.ts index 9792e0225e..844c549960 100644 --- a/src/upload-lib.ts +++ b/src/upload-lib.ts @@ -18,6 +18,7 @@ import { EnvVar, exportEnvVar } from "./environment"; import { FeatureEnablement } from "./feature-flags"; import * as fingerprints from "./fingerprints"; import * as gitUtils from "./git-utils"; +import * as github from "./github"; import { initCodeQL } from "./init"; import { Logger } from "./logging"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; @@ -33,7 +34,6 @@ import * as util from "./util"; import { ConfigurationError, getErrorMessage, - getRequiredEnvParam, GitHubVariant, GitHubVersion, satisfiesGHESVersion, @@ -104,6 +104,8 @@ async function combineSarifFilesUsingCLI( features: FeatureEnablement, logger: Logger, ): Promise> { + const env = util.getEnv(); + logger.info("Combining SARIF files using the CodeQL CLI"); const sarifObjects = sarifFiles.map(sarif.readSarifFile); @@ -152,8 +154,7 @@ async function combineSarifFilesUsingCLI( externalRepoAuth: actionsUtil.getOptionalInput( "external-repository-token", ), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL"), + ...github.getCurrentInstance(env), }; const codeQLDefaultVersionInfo = From f09e27bdf46cb84c1478ad577875cf5a13b0fcef Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 18:06:52 +0100 Subject: [PATCH 07/11] Make `externalRepoAuth` check more consistent --- lib/entry-points.js | 2 +- src/api-client.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 57dd025418..ebe7f9d173 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170758,7 +170758,7 @@ function makeProxyRequestOptions(dispatcher) { }; } function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = void 0 } = {}) { - const auth2 = allowExternal && apiDetails.externalRepoAuth || apiDetails.auth; + const auth2 = allowExternal && apiDetails.externalRepoAuth !== void 0 ? apiDetails.externalRepoAuth : apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry); const requestOptions = makeProxyRequestOptions(proxy); const opts = { diff --git a/src/api-client.ts b/src/api-client.ts index 48313dbba9..31169480af 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -131,7 +131,9 @@ function createApiClientWithDetails( { allowExternal = false, proxy = undefined }: CreateApiClientOptions = {}, ): ApiClient { const auth = - (allowExternal && apiDetails.externalRepoAuth) || apiDetails.auth; + allowExternal && apiDetails.externalRepoAuth !== undefined + ? apiDetails.externalRepoAuth + : apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry.retry); const requestOptions = makeProxyRequestOptions(proxy); From ccac407b4e9f5a047b2d19e1f8efe641d8c57041 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 13 Oct 2026 18:05:49 +0100 Subject: [PATCH 08/11] Guard against blank `auth` value This shouldn't be possible, but was previously handled by the library --- lib/entry-points.js | 2 +- src/api-client.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index ebe7f9d173..d9047f6775 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170762,7 +170762,7 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = const retryingOctokit = githubUtils.GitHub.plugin(retry); const requestOptions = makeProxyRequestOptions(proxy); const opts = { - auth: auth2 !== void 0 ? `token ${auth2}` : void 0, + auth: auth2 !== void 0 && auth2.trim() !== "" ? `token ${auth2}` : void 0, baseUrl: apiDetails.apiURL, userAgent: `CodeQL-Action/${getActionVersion()}`, log: { diff --git a/src/api-client.ts b/src/api-client.ts index 31169480af..4d0a999206 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -138,7 +138,8 @@ function createApiClientWithDetails( const requestOptions = makeProxyRequestOptions(proxy); const opts: OctokitOptions = { - auth: auth !== undefined ? `token ${auth}` : undefined, + auth: + auth !== undefined && auth.trim() !== "" ? `token ${auth}` : undefined, baseUrl: apiDetails.apiURL, userAgent: `CodeQL-Action/${getActionVersion()}`, log: { From d6f4244c4baf061f32f1556ba4210a0d870c6bf3 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 14 Oct 2026 11:13:41 +0100 Subject: [PATCH 09/11] Check `auth` value in `getAuthorizationHeaderFor` Also add some tests for it --- lib/entry-points.js | 6 +++- src/api-client.test.ts | 80 +++++++++++++++++++++++++++++++++++++++++- src/api-client.ts | 8 ++++- 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index d9047f6775..d871e6f49a 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -170791,9 +170791,13 @@ function getApiClientWithExternalAuth(apiDetails, proxy) { return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy }); } function getAuthorizationHeaderFor(logger2, apiDetails, url2) { + const token = apiDetails.auth?.trim(); + if (token === void 0 || token === "") { + return void 0; + } if (url2.startsWith(`${apiDetails.url}/`) || apiDetails.apiURL && url2.startsWith(`${apiDetails.apiURL}/`)) { logger2.debug(`Providing an authorization token.`); - return `token ${apiDetails.auth}`; + return `token ${token}`; } logger2.debug(`Not using an authorization token.`); return void 0; diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 12fe819856..1a37425e4c 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -8,7 +8,14 @@ import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import { DO_NOT_RETRY_STATUSES } from "./api-client"; import { ActionsEnvVars, RegistryProxyVars } from "./environment"; -import { callee, getTestEnv, setupTests } from "./testing-utils"; +import { GITHUB_DOTCOM_API_URL, GITHUB_DOTCOM_URL } from "./github"; +import { + callee, + getTestEnv, + RecordingLogger, + SAMPLE_DOTCOM_API_DETAILS, + setupTests, +} from "./testing-utils"; import * as util from "./util"; setupTests(test); @@ -284,3 +291,74 @@ test("makeProxyRequestOptions - returns fetch with custom proxy", async (t) => { githubUtils.defaults.request, ); }); + +test("getAuthorizationHeaderFor - returns undefined if there's no token", (t) => { + const logger = new RecordingLogger(); + + t.is( + api.getAuthorizationHeaderFor( + logger, + { ...SAMPLE_DOTCOM_API_DETAILS, auth: undefined }, + `${GITHUB_DOTCOM_URL}/github`, + ), + undefined, + ); + t.is( + api.getAuthorizationHeaderFor( + logger, + { ...SAMPLE_DOTCOM_API_DETAILS, auth: "" }, + `${GITHUB_DOTCOM_URL}/github`, + ), + undefined, + ); + t.is( + api.getAuthorizationHeaderFor( + logger, + { ...SAMPLE_DOTCOM_API_DETAILS, auth: " " }, + `${GITHUB_DOTCOM_URL}/github`, + ), + undefined, + ); +}); + +test("getAuthorizationHeaderFor - returns header value with token if url is allowed", (t) => { + const logger = new RecordingLogger(); + + t.is( + api.getAuthorizationHeaderFor( + logger, + SAMPLE_DOTCOM_API_DETAILS, + `${GITHUB_DOTCOM_URL}/github`, + ), + `token ${SAMPLE_DOTCOM_API_DETAILS.auth}`, + ); + t.is( + api.getAuthorizationHeaderFor( + logger, + SAMPLE_DOTCOM_API_DETAILS, + `${GITHUB_DOTCOM_API_URL}/github`, + ), + `token ${SAMPLE_DOTCOM_API_DETAILS.auth}`, + ); +}); + +test("getAuthorizationHeaderFor - returns undefined if url is not allowed", (t) => { + const logger = new RecordingLogger(); + + t.is( + api.getAuthorizationHeaderFor( + logger, + SAMPLE_DOTCOM_API_DETAILS, + `https://localhost/github`, + ), + undefined, + ); + t.is( + api.getAuthorizationHeaderFor( + logger, + SAMPLE_DOTCOM_API_DETAILS, + `https://api.localhost/github`, + ), + undefined, + ); +}); diff --git a/src/api-client.ts b/src/api-client.ts index 4d0a999206..b75aacbb79 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -195,6 +195,12 @@ export function getAuthorizationHeaderFor( apiDetails: GitHubApiDetails, url: string, ): string | undefined { + // We don't have to check the `url` if we don't have a token to begin with. + const token = apiDetails.auth?.trim(); + if (token === undefined || token === "") { + return undefined; + } + // We only want to provide an authorization header if we are downloading // from the same GitHub instance the Action is running on. // This avoids leaking Enterprise tokens to dotcom. @@ -203,7 +209,7 @@ export function getAuthorizationHeaderFor( (apiDetails.apiURL && url.startsWith(`${apiDetails.apiURL}/`)) ) { logger.debug(`Providing an authorization token.`); - return `token ${apiDetails.auth}`; + return `token ${token}`; } logger.debug(`Not using an authorization token.`); From f4d1fd4519c24e28b50e5bd48e19675b6d748b69 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 14 Oct 2026 11:21:44 +0100 Subject: [PATCH 10/11] Check token value in `uploadBundledDatabase` --- lib/entry-points.js | 5 ++++- src/database-upload.ts | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index d871e6f49a..b79a327ea7 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -178468,6 +178468,9 @@ async function uploadBundledDatabase(repositoryNwo, language, commitOid, bundled if (uploadsBaseUrl.endsWith("/")) { uploadsBaseUrl = uploadsBaseUrl.slice(0, -1); } + const token = apiDetails.auth?.trim(); + const hasToken2 = token !== void 0 && token !== ""; + const authorization = hasToken2 ? `token ${token}` : void 0; const bundledDbReadStream = fs18.createReadStream(bundledDb); try { const startTime = performance.now(); @@ -178482,7 +178485,7 @@ async function uploadBundledDatabase(repositoryNwo, language, commitOid, bundled commit_oid: commitOid, data: bundledDbReadStream, headers: { - authorization: `token ${apiDetails.auth}`, + authorization, "Content-Type": "application/zip", "Content-Length": bundledDbSize }, diff --git a/src/database-upload.ts b/src/database-upload.ts index a79669436b..df3796e505 100644 --- a/src/database-upload.ts +++ b/src/database-upload.ts @@ -279,6 +279,10 @@ async function uploadBundledDatabase( uploadsBaseUrl = uploadsBaseUrl.slice(0, -1); } + const token = apiDetails.auth?.trim(); + const hasToken = token !== undefined && token !== ""; + const authorization = hasToken ? `token ${token}` : undefined; + const bundledDbReadStream = fs.createReadStream(bundledDb); try { const startTime = performance.now(); @@ -293,7 +297,7 @@ async function uploadBundledDatabase( commit_oid: commitOid, data: bundledDbReadStream, headers: { - authorization: `token ${apiDetails.auth}`, + authorization, "Content-Type": "application/zip", "Content-Length": bundledDbSize, }, From 6315034db1c0ed1919a24505049adf2abe146a21 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 14 Oct 2026 13:41:12 +0100 Subject: [PATCH 11/11] Add very basic `getApiClientWithExternalAuth` test --- src/api-client.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 1a37425e4c..2bb878f3b7 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -292,6 +292,19 @@ test("makeProxyRequestOptions - returns fetch with custom proxy", async (t) => { ); }); +test.serial( + "getApiClientWithExternalAuth constructs clients with or without tokens", + (t) => { + t.notThrows(() => { + api.getApiClientWithExternalAuth(SAMPLE_DOTCOM_API_DETAILS); + api.getApiClientWithExternalAuth({ + ...SAMPLE_DOTCOM_API_DETAILS, + auth: undefined, + }); + }); + }, +); + test("getAuthorizationHeaderFor - returns undefined if there's no token", (t) => { const logger = new RecordingLogger();