From dab0ab7cb7869ab4383e9bcef08aada6ee7cbaf7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Jun 2026 19:24:34 +0100 Subject: [PATCH 01/15] Make `undici` an explicit dependency --- package-lock.json | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 208964cbcc..7acded671f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "long": "^5.3.2", "node-forge": "^1.4.0", "semver": "^7.8.5", + "undici": "^6.24.0", "uuid": "^14.0.1" }, "devDependencies": { @@ -9363,7 +9364,6 @@ "version": "6.27.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", - "license": "MIT", "engines": { "node": ">=18.17" } diff --git a/package.json b/package.json index b1190b0438..e979911d3b 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "long": "^5.3.2", "node-forge": "^1.4.0", "semver": "^7.8.5", - "uuid": "^14.0.1" + "uuid": "^14.0.1", + "undici": "^6.24.0" }, "devDependencies": { "@ava/typescript": "6.0.0", From 8763bac62542e9336524c8ccd20b3f713681605d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 15:51:15 +0100 Subject: [PATCH 02/15] Add `RegistryProxyVars` enum --- src/environment.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/environment.ts b/src/environment.ts index 80759d6d61..9d52a903ff 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -1,3 +1,13 @@ +/** + * Environment variables used by Default Setup to communicate the private registry proxy configuration. + */ +export enum RegistryProxyVars { + PROXY_HOST = "CODEQL_PROXY_HOST", + PROXY_PORT = "CODEQL_PROXY_PORT", + PROXY_CA_CERTIFICATE = "CODEQL_PROXY_CA_CERTIFICATE", + PROXY_URLS = "CODEQL_PROXY_URLS", +} + /** * Environment variables used by the CodeQL Action. * @@ -202,7 +212,7 @@ export enum ActionsEnvVars { } /** A type representing all known environment variables. */ -export type KnownEnvVar = EnvVar | ActionsEnvVars; +export type KnownEnvVar = EnvVar | ActionsEnvVars | RegistryProxyVars; /** * Gets an environment variable, but throws an error if it is not set. From 5172487de5b6b3080b199f2bc2a6665a85893dba Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 15:51:33 +0100 Subject: [PATCH 03/15] Allow `getTestEnv` to be parameterised over initial env --- src/testing-utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 60e8fb5540..f630082be4 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -178,8 +178,7 @@ export function makeMacro( return wrapper; } -export function getTestEnv(): Env { - const testEnv: NodeJS.ProcessEnv = {}; +export function getTestEnv(testEnv: NodeJS.ProcessEnv = {}): Env { return getEnv(testEnv); } From a29dee455ce5a1f1c42c8f99f90d7914fa1b8060 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 8 Jul 2026 16:24:23 +0100 Subject: [PATCH 04/15] Add minimal proxy init code --- lib/entry-points.js | 39 +++++++++++++++++++++++------ src/api-client.test.ts | 36 +++++++++++++++++++++++++-- src/api-client.ts | 56 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index ee64cb6a5e..5d9420a9f2 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -8941,7 +8941,7 @@ var require_proxy_agent = __commonJS({ return this.#client.destroy(err); } }; - var ProxyAgent = class extends DispatcherBase { + var ProxyAgent2 = class extends DispatcherBase { constructor(opts) { super(); if (!opts || typeof opts === "object" && !(opts instanceof URL2) && !opts.uri) { @@ -9082,7 +9082,7 @@ var require_proxy_agent = __commonJS({ throw new InvalidArgumentError("Proxy-Authorization should be sent in ProxyAgent constructor"); } } - module2.exports = ProxyAgent; + module2.exports = ProxyAgent2; } }); @@ -9092,7 +9092,7 @@ var require_env_http_proxy_agent = __commonJS({ "use strict"; var DispatcherBase = require_dispatcher_base(); var { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require_symbols(); - var ProxyAgent = require_proxy_agent(); + var ProxyAgent2 = require_proxy_agent(); var Agent = require_agent(); var DEFAULT_PORTS = { "http:": 80, @@ -9116,13 +9116,13 @@ var require_env_http_proxy_agent = __commonJS({ this[kNoProxyAgent] = new Agent(agentOpts); const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY; if (HTTP_PROXY) { - this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY }); + this[kHttpProxyAgent] = new ProxyAgent2({ ...agentOpts, uri: HTTP_PROXY }); } else { this[kHttpProxyAgent] = this[kNoProxyAgent]; } const HTTPS_PROXY = httpsProxy ?? process.env.https_proxy ?? process.env.HTTPS_PROXY; if (HTTPS_PROXY) { - this[kHttpsProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTPS_PROXY }); + this[kHttpsProxyAgent] = new ProxyAgent2({ ...agentOpts, uri: HTTPS_PROXY }); } else { this[kHttpsProxyAgent] = this[kHttpProxyAgent]; } @@ -18906,7 +18906,7 @@ var require_undici = __commonJS({ var Pool = require_pool(); var BalancedPool = require_balanced_pool(); var Agent = require_agent(); - var ProxyAgent = require_proxy_agent(); + var ProxyAgent2 = require_proxy_agent(); var EnvHttpProxyAgent = require_env_http_proxy_agent(); var RetryAgent = require_retry_agent(); var errors = require_errors(); @@ -18929,7 +18929,7 @@ var require_undici = __commonJS({ module2.exports.Pool = Pool; module2.exports.BalancedPool = BalancedPool; module2.exports.Agent = Agent; - module2.exports.ProxyAgent = ProxyAgent; + module2.exports.ProxyAgent = ProxyAgent2; module2.exports.EnvHttpProxyAgent = EnvHttpProxyAgent; module2.exports.RetryAgent = RetryAgent; module2.exports.RetryHandler = RetryHandler; @@ -145590,6 +145590,9 @@ function retry(octokit, octokitOptions) { } retry.VERSION = VERSION7; +// src/api-client.ts +var import_undici = __toESM(require_undici()); + // src/repository.ts function getRepositoryNwo() { return getRepositoryNwoFromEnv("GITHUB_REPOSITORY"); @@ -145617,6 +145620,27 @@ function parseRepositoryNwo(input) { // src/api-client.ts var GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"; var DO_NOT_RETRY_STATUSES = [400, 410, 422, 451]; +function getRegistryProxy(env) { + const host = env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */); + const port = env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); + const cert = env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); + if (host && port) { + return new import_undici.ProxyAgent({ + uri: `http://${host}:${port}`, + keepAliveTimeout: 10, + keepAliveMaxTimeout: 10, + requestTls: cert ? { ca: cert } : void 0 + }); + } + return void 0; +} +function getApiFetch(env) { + const dispatcher = getRegistryProxy(env); + const proxiedFetch = (req, init2) => { + return (0, import_undici.fetch)(req, { ...init2, dispatcher }); + }; + return proxiedFetch; +} function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) { const auth2 = allowExternal && apiDetails.externalRepoAuth || apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry); @@ -145630,6 +145654,7 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) warn: core5.warning, error: core5.error }, + request: { fetch: getApiFetch(getEnv()) }, retry: { doNotRetry: DO_NOT_RETRY_STATUSES } diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 34a72f14e5..4c8342691e 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -6,7 +6,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import { DO_NOT_RETRY_STATUSES } from "./api-client"; -import { ActionsEnvVars } from "./environment"; +import { ActionsEnvVars, RegistryProxyVars } from "./environment"; import { getTestEnv, setupTests } from "./testing-utils"; import * as util from "./util"; @@ -27,14 +27,17 @@ test.serial("getApiClient", async (t) => { sinon.stub(actionsUtil, "getRequiredInput").withArgs("token").returns("xyz"); - api.getApiClient(env); + const apiClient = api.getApiClient(env); + t.truthy(apiClient); + t.true(githubStub.calledOnce); t.assert( githubStub.calledOnceWithExactly({ auth: "token xyz", baseUrl: "http://api.github.localhost", log: sinon.match.any, userAgent: `CodeQL-Action/${actionsUtil.getActionVersion()}`, + request: sinon.match.any, retry: { doNotRetry: DO_NOT_RETRY_STATUSES, }, @@ -204,3 +207,32 @@ test.serial( } }, ); + +test("getRegistryProxy - returns undefined if the proxy is not configured", async (t) => { + // Empty environment. + t.is(api.getRegistryProxy(getTestEnv()), undefined); + // Only the host. + t.is( + api.getRegistryProxy( + getTestEnv({ [RegistryProxyVars.PROXY_HOST]: "localhost" }), + ), + undefined, + ); + // Only the port. + t.is( + api.getRegistryProxy( + getTestEnv({ [RegistryProxyVars.PROXY_PORT]: "1234" }), + ), + undefined, + ); +}); + +test("getRegistryProxy - returns value when both vars are set", async (t) => { + const proxy = api.getRegistryProxy( + getTestEnv({ + [RegistryProxyVars.PROXY_HOST]: "localhost", + [RegistryProxyVars.PROXY_PORT]: "1234", + }), + ); + t.truthy(proxy); +}); diff --git a/src/api-client.ts b/src/api-client.ts index eafac7ec76..70ac17c1d7 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -1,9 +1,21 @@ import * as core from "@actions/core"; import * as githubUtils from "@actions/github/lib/utils"; import * as retry from "@octokit/plugin-retry"; +import { + ProxyAgent, + RequestInfo, + RequestInit, + fetch as undiciFetch, +} from "undici"; import { getActionVersion, getRequiredInput } from "./actions-util"; -import { EnvVar, ReadOnlyEnv, ActionsEnvVars, getEnv } from "./environment"; +import { + ActionsEnvVars, + EnvVar, + ReadOnlyEnv, + RegistryProxyVars, + getEnv, +} from "./environment"; import { Logger } from "./logging"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { @@ -43,6 +55,47 @@ export interface GitHubApiExternalRepoDetails { apiURL: string | undefined; } +/** + * Gets the configuration for the private registry authentication proxy, + * if it is available in the environment. + * + * @param env The environment to query for the proxy host and port. + * @returns A `ProxyAgent` corresponding to the private registry proxy, + * or `undefined` if we couldn't retrieve the host and port. + */ +export function getRegistryProxy(env: ReadOnlyEnv): ProxyAgent | undefined { + const host = env.getOptional(RegistryProxyVars.PROXY_HOST); + const port = env.getOptional(RegistryProxyVars.PROXY_PORT); + const cert = env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); + + if (host && port) { + return new ProxyAgent({ + uri: `http://${host}:${port}`, + keepAliveTimeout: 10, + keepAliveMaxTimeout: 10, + requestTls: cert ? { ca: cert } : undefined, + }); + } + + return undefined; +} + +/** + * Returns an implementation of `fetch` to use for API requests. + * This will run API requests through the private registry authentication proxy + * if it is configured. + * + * @param env The environment to query for the proxy host and port. + */ +export function getApiFetch(env: ReadOnlyEnv): typeof undiciFetch { + const dispatcher = getRegistryProxy(env); + + const proxiedFetch = (req: RequestInfo, init?: RequestInit) => { + return undiciFetch(req, { ...init, dispatcher }); + }; + return proxiedFetch; +} + function createApiClientWithDetails( apiDetails: GitHubApiCombinedDetails, { allowExternal = false } = {}, @@ -60,6 +113,7 @@ function createApiClientWithDetails( warn: core.warning, error: core.error, }, + request: { fetch: getApiFetch(getEnv()) }, retry: { doNotRetry: DO_NOT_RETRY_STATUSES, }, From e387ec1de1e303bbf1438ed546160c3154e3598d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 16:00:17 +0100 Subject: [PATCH 05/15] Move `init` step at the end of `start-proxy` test workflow --- .github/workflows/__start-proxy.yml | 10 +++++----- pr-checks/checks/start-proxy.yml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index 7ac2dede30..d8331470f0 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -57,11 +57,6 @@ jobs: version: ${{ matrix.version }} use-all-platform-bundle: 'false' setup-kotlin: 'true' - - uses: ./../action/init - with: - languages: csharp - tools: ${{ steps.prepare-test.outputs.tools-url }} - - name: Setup proxy for registries id: proxy uses: ./../action/start-proxy @@ -94,5 +89,10 @@ jobs: || !contains(steps.proxy.outputs.proxy_urls, 'https://repo.maven.apache.org/maven2/') || !contains(steps.proxy.outputs.proxy_urls, 'https://repo1.maven.org/maven2') run: exit 1 + + - uses: ./../action/init + with: + languages: csharp + tools: ${{ steps.prepare-test.outputs.tools-url }} env: CODEQL_ACTION_TEST_MODE: true diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index a4bf794873..ac9e494b09 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -7,11 +7,6 @@ operatingSystems: versions: - linked steps: - - uses: ./../action/init - with: - languages: csharp - tools: ${{ steps.prepare-test.outputs.tools-url }} - - name: Setup proxy for registries id: proxy uses: ./../action/start-proxy @@ -44,3 +39,8 @@ steps: || !contains(steps.proxy.outputs.proxy_urls, 'https://repo.maven.apache.org/maven2/') || !contains(steps.proxy.outputs.proxy_urls, 'https://repo1.maven.org/maven2') run: exit 1 + + - uses: ./../action/init + with: + languages: csharp + tools: ${{ steps.prepare-test.outputs.tools-url }} From b0eaa56a8f14c24a7065276c9bff0ed5f95a7722 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 16:00:51 +0100 Subject: [PATCH 06/15] Set language inputs in `start-proxy` check workflow --- .github/workflows/__start-proxy.yml | 3 ++- pr-checks/checks/start-proxy.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index d8331470f0..4dbea700f5 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -61,6 +61,7 @@ jobs: id: proxy uses: ./../action/start-proxy with: + language: java registry_secrets: | [ { @@ -92,7 +93,7 @@ jobs: - uses: ./../action/init with: - languages: csharp + languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} env: CODEQL_ACTION_TEST_MODE: true diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index ac9e494b09..2d4670f1eb 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -11,6 +11,7 @@ steps: id: proxy uses: ./../action/start-proxy with: + language: java registry_secrets: | [ { @@ -42,5 +43,5 @@ steps: - uses: ./../action/init with: - languages: csharp + languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} From b6d92e33f7afdb3f88b1dc4267abe3077a5fdddb Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 16:05:15 +0100 Subject: [PATCH 07/15] Set `CODEQL_PROXY_*` vars for `init` step in test workflow --- .github/workflows/__start-proxy.yml | 4 ++++ pr-checks/checks/start-proxy.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index 4dbea700f5..ddc8eaba15 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -92,6 +92,10 @@ jobs: run: exit 1 - uses: ./../action/init + env: + CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} + CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} + CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index 2d4670f1eb..10bf0bd808 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -42,6 +42,10 @@ steps: run: exit 1 - uses: ./../action/init + env: + CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} + CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} + CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} From 50b3687dd7f86d681a9ef4af6a04b32f52f905ca Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 16:24:54 +0100 Subject: [PATCH 08/15] Specify custom `config-file` in `start-proxy` check --- .github/workflows/__start-proxy.yml | 2 ++ pr-checks/checks/start-proxy.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index ddc8eaba15..30ca74fd9c 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -96,8 +96,10 @@ jobs: CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} + CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES: 'true' with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} + config-file: codeql-action@main:tests/multi-language-repo/.github/codeql/custom-queries.yml env: CODEQL_ACTION_TEST_MODE: true diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index 10bf0bd808..dedf5633ea 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -46,6 +46,8 @@ steps: CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} + CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES: "true" with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} + config-file: codeql-action@main:tests/multi-language-repo/.github/codeql/custom-queries.yml From 80599cc5d9430d42a6d963e12d90ee141d4616c5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 16:07:08 +0100 Subject: [PATCH 09/15] Allow proxy to be threaded into `createApiClientWithDetails` This will make it easier to gate the `getRegistryProxy` with a FF, without the significant refactoring that is needed to thread the FFs into `createApiClientWithDetails` --- lib/entry-points.js | 22 ++++++++++++---------- src/api-client.ts | 31 ++++++++++++++++++++++++++++--- src/config/file.ts | 3 ++- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 5d9420a9f2..0bf6c44be6 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145634,16 +145634,17 @@ function getRegistryProxy(env) { } return void 0; } -function getApiFetch(env) { - const dispatcher = getRegistryProxy(env); - const proxiedFetch = (req, init2) => { - return (0, import_undici.fetch)(req, { ...init2, dispatcher }); +function makeProxyRequestOptions(dispatcher) { + return { + fetch: (req, init2) => { + return (0, import_undici.fetch)(req, { ...init2, dispatcher }); + } }; - return proxiedFetch; } -function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) { +function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = void 0 } = {}) { const auth2 = allowExternal && apiDetails.externalRepoAuth || apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry); + const requestOptions = proxy === void 0 ? void 0 : makeProxyRequestOptions(proxy); return new retryingOctokit( githubUtils.getOctokitOptions(auth2, { baseUrl: apiDetails.apiURL, @@ -145654,7 +145655,7 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) warn: core5.warning, error: core5.error }, - request: { fetch: getApiFetch(getEnv()) }, + request: requestOptions, retry: { doNotRetry: DO_NOT_RETRY_STATUSES } @@ -145671,8 +145672,8 @@ function getApiDetails(env = getEnv()) { function getApiClient(env = getEnv()) { return createApiClientWithDetails(getApiDetails(env)); } -function getApiClientWithExternalAuth(apiDetails) { - return createApiClientWithDetails(apiDetails, { allowExternal: true }); +function getApiClientWithExternalAuth(apiDetails, proxy) { + return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy }); } function getAuthorizationHeaderFor(logger, apiDetails, url2) { if (url2.startsWith(`${apiDetails.url}/`) || apiDetails.apiURL && url2.startsWith(`${apiDetails.apiURL}/`)) { @@ -148082,7 +148083,8 @@ async function getConfigFileInput({ } async function getRemoteConfig(actionState, configFile, apiDetails) { const address = await parseRemoteFileAddress(actionState, configFile); - const response = await getApiClientWithExternalAuth(apiDetails).rest.repos.getContent({ + const proxy = getRegistryProxy(actionState.env); + const response = await getApiClientWithExternalAuth(apiDetails, proxy).rest.repos.getContent({ owner: address.owner, repo: address.repo, path: address.path, diff --git a/src/api-client.ts b/src/api-client.ts index 70ac17c1d7..87e6416689 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -1,6 +1,7 @@ import * as core from "@actions/core"; import * as githubUtils from "@actions/github/lib/utils"; import * as retry from "@octokit/plugin-retry"; +import { RequestRequestOptions } from "@octokit/types"; import { ProxyAgent, RequestInfo, @@ -80,6 +81,22 @@ export function getRegistryProxy(env: ReadOnlyEnv): ProxyAgent | undefined { return undefined; } +/** + * Constructs a `RequestRequestOptions` with a custom `fetch` implementation + * that uses `dispatcher` as a proxy for requests. + * + * @param dispatcher The proxy to use. + */ +export function makeProxyRequestOptions( + dispatcher: ProxyAgent, +): RequestRequestOptions { + return { + fetch: (req: RequestInfo, init?: RequestInit) => { + return undiciFetch(req, { ...init, dispatcher }); + }, + }; +} + /** * Returns an implementation of `fetch` to use for API requests. * This will run API requests through the private registry authentication proxy @@ -96,13 +113,20 @@ export function getApiFetch(env: ReadOnlyEnv): typeof undiciFetch { return proxiedFetch; } +interface CreateApiClientOptions { + allowExternal?: boolean; + proxy?: ProxyAgent; +} + function createApiClientWithDetails( apiDetails: GitHubApiCombinedDetails, - { allowExternal = false } = {}, + { allowExternal = false, proxy = undefined }: CreateApiClientOptions = {}, ) { const auth = (allowExternal && apiDetails.externalRepoAuth) || apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry.retry); + const requestOptions = + proxy === undefined ? undefined : makeProxyRequestOptions(proxy); return new retryingOctokit( githubUtils.getOctokitOptions(auth, { baseUrl: apiDetails.apiURL, @@ -113,7 +137,7 @@ function createApiClientWithDetails( warn: core.warning, error: core.error, }, - request: { fetch: getApiFetch(getEnv()) }, + request: requestOptions, retry: { doNotRetry: DO_NOT_RETRY_STATUSES, }, @@ -135,8 +159,9 @@ export function getApiClient(env: ReadOnlyEnv = getEnv()) { export function getApiClientWithExternalAuth( apiDetails: GitHubApiCombinedDetails, + proxy?: ProxyAgent, ) { - return createApiClientWithDetails(apiDetails, { allowExternal: true }); + return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy }); } /** diff --git a/src/config/file.ts b/src/config/file.ts index 5ff2dee082..b17e1c9d8b 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -68,9 +68,10 @@ export async function getRemoteConfig( apiDetails: api.GitHubApiCombinedDetails, ): Promise { const address = await parseRemoteFileAddress(actionState, configFile); + const proxy = api.getRegistryProxy(actionState.env); const response = await api - .getApiClientWithExternalAuth(apiDetails) + .getApiClientWithExternalAuth(apiDetails, proxy) .rest.repos.getContent({ owner: address.owner, repo: address.repo, From 6d70593fb72b91319b8274998372e3f12946935a Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 16:12:38 +0100 Subject: [PATCH 10/15] Gate proxy usage behind FF --- .github/workflows/__start-proxy.yml | 1 + lib/entry-points.js | 10 +++++++++- pr-checks/checks/start-proxy.yml | 2 ++ src/config/file.ts | 8 +++++++- src/feature-flags.ts | 7 +++++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index 30ca74fd9c..9a782865bf 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -102,4 +102,5 @@ jobs: tools: ${{ steps.prepare-test.outputs.tools-url }} config-file: codeql-action@main:tests/multi-language-repo/.github/codeql/custom-queries.yml env: + CODEQL_ACTION_PROXY_API_REQUESTS: 'true' CODEQL_ACTION_TEST_MODE: true diff --git a/lib/entry-points.js b/lib/entry-points.js index 0bf6c44be6..bf8d274512 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146762,6 +146762,11 @@ var featureConfig = { legacyApi: true, minimumVersion: void 0 }, + ["proxy_api_requests" /* ProxyApiRequests */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_PROXY_API_REQUESTS", + minimumVersion: void 0 + }, ["skip_file_coverage_on_prs" /* SkipFileCoverageOnPrs */]: { defaultValue: false, envVar: "CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS", @@ -148083,7 +148088,10 @@ async function getConfigFileInput({ } async function getRemoteConfig(actionState, configFile, apiDetails) { const address = await parseRemoteFileAddress(actionState, configFile); - const proxy = getRegistryProxy(actionState.env); + const shouldProxyRequest = await actionState.features.getValue( + "proxy_api_requests" /* ProxyApiRequests */ + ); + const proxy = shouldProxyRequest ? getRegistryProxy(actionState.env) : void 0; const response = await getApiClientWithExternalAuth(apiDetails, proxy).rest.repos.getContent({ owner: address.owner, repo: address.repo, diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index dedf5633ea..c4bc02f736 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -6,6 +6,8 @@ operatingSystems: - windows versions: - linked +env: + CODEQL_ACTION_PROXY_API_REQUESTS: "true" steps: - name: Setup proxy for registries id: proxy diff --git a/src/config/file.ts b/src/config/file.ts index b17e1c9d8b..38f9b19fd4 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -68,7 +68,13 @@ export async function getRemoteConfig( apiDetails: api.GitHubApiCombinedDetails, ): Promise { const address = await parseRemoteFileAddress(actionState, configFile); - const proxy = api.getRegistryProxy(actionState.env); + + const shouldProxyRequest = await actionState.features.getValue( + Feature.ProxyApiRequests, + ); + const proxy = shouldProxyRequest + ? api.getRegistryProxy(actionState.env) + : undefined; const response = await api .getApiClientWithExternalAuth(apiDetails, proxy) diff --git a/src/feature-flags.ts b/src/feature-flags.ts index dcc5c9d7bd..7f689f2a44 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -136,6 +136,8 @@ export enum Feature { /** Controls whether overlay build failures on the default branch are stored in the Actions cache. */ OverlayAnalysisStatusSave = "overlay_analysis_status_save", QaTelemetryEnabled = "qa_telemetry_enabled", + /** Routes (some) API requests through the registry proxy. */ + ProxyApiRequests = "proxy_api_requests", /** Note that this currently only disables baseline file coverage information. */ SkipFileCoverageOnPrs = "skip_file_coverage_on_prs", StartProxyUseFeaturesRelease = "start_proxy_use_features_release", @@ -382,6 +384,11 @@ export const featureConfig = { legacyApi: true, minimumVersion: undefined, }, + [Feature.ProxyApiRequests]: { + defaultValue: false, + envVar: "CODEQL_ACTION_PROXY_API_REQUESTS", + minimumVersion: undefined, + }, [Feature.SkipFileCoverageOnPrs]: { defaultValue: false, envVar: "CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS", From 205b37b035c53a89e2f107054a65d024fcf0d6f2 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 16:17:51 +0100 Subject: [PATCH 11/15] Use `ActionState` for `getRegistryProxy` --- lib/entry-points.js | 10 +++++----- src/api-client.test.ts | 40 +++++++++++++++++++--------------------- src/api-client.ts | 19 +++++++++++-------- src/config/file.ts | 2 +- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index bf8d274512..0cf4dc2bc0 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145620,10 +145620,10 @@ function parseRepositoryNwo(input) { // src/api-client.ts var GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"; var DO_NOT_RETRY_STATUSES = [400, 410, 422, 451]; -function getRegistryProxy(env) { - const host = env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */); - const port = env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); - const cert = env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); +function getRegistryProxy(action) { + const host = action.env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */); + const port = action.env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); + const cert = action.env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); if (host && port) { return new import_undici.ProxyAgent({ uri: `http://${host}:${port}`, @@ -148091,7 +148091,7 @@ async function getRemoteConfig(actionState, configFile, apiDetails) { const shouldProxyRequest = await actionState.features.getValue( "proxy_api_requests" /* ProxyApiRequests */ ); - const proxy = shouldProxyRequest ? getRegistryProxy(actionState.env) : void 0; + const proxy = shouldProxyRequest ? getRegistryProxy(actionState) : void 0; const response = await getApiClientWithExternalAuth(apiDetails, proxy).rest.repos.getContent({ owner: address.owner, repo: address.repo, diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 4c8342691e..8282847145 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -7,7 +7,7 @@ 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 { getTestEnv, setupTests } from "./testing-utils"; +import { callee, getTestEnv, setupTests } from "./testing-utils"; import * as util from "./util"; setupTests(test); @@ -209,30 +209,28 @@ test.serial( ); test("getRegistryProxy - returns undefined if the proxy is not configured", async (t) => { + const target = callee(api.getRegistryProxy).withArgs(); + // Empty environment. - t.is(api.getRegistryProxy(getTestEnv()), undefined); + await target.passes(t.is, undefined); // Only the host. - t.is( - api.getRegistryProxy( - getTestEnv({ [RegistryProxyVars.PROXY_HOST]: "localhost" }), - ), - undefined, - ); + await target + .withEnv(getTestEnv({ [RegistryProxyVars.PROXY_HOST]: "localhost" })) + .passes(t.is, undefined); // Only the port. - t.is( - api.getRegistryProxy( - getTestEnv({ [RegistryProxyVars.PROXY_PORT]: "1234" }), - ), - undefined, - ); + await target + .withEnv(getTestEnv({ [RegistryProxyVars.PROXY_PORT]: "1234" })) + .passes(t.is, undefined); }); test("getRegistryProxy - returns value when both vars are set", async (t) => { - const proxy = api.getRegistryProxy( - getTestEnv({ - [RegistryProxyVars.PROXY_HOST]: "localhost", - [RegistryProxyVars.PROXY_PORT]: "1234", - }), - ); - t.truthy(proxy); + await callee(api.getRegistryProxy) + .withArgs() + .withEnv( + getTestEnv({ + [RegistryProxyVars.PROXY_HOST]: "localhost", + [RegistryProxyVars.PROXY_PORT]: "1234", + }), + ) + .passes(t.truthy); }); diff --git a/src/api-client.ts b/src/api-client.ts index 87e6416689..261e0a557e 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -9,6 +9,7 @@ import { fetch as undiciFetch, } from "undici"; +import type { ActionState } from "./action-common"; import { getActionVersion, getRequiredInput } from "./actions-util"; import { ActionsEnvVars, @@ -60,14 +61,16 @@ export interface GitHubApiExternalRepoDetails { * Gets the configuration for the private registry authentication proxy, * if it is available in the environment. * - * @param env The environment to query for the proxy host and port. + * @param action The required Action state. * @returns A `ProxyAgent` corresponding to the private registry proxy, * or `undefined` if we couldn't retrieve the host and port. */ -export function getRegistryProxy(env: ReadOnlyEnv): ProxyAgent | undefined { - const host = env.getOptional(RegistryProxyVars.PROXY_HOST); - const port = env.getOptional(RegistryProxyVars.PROXY_PORT); - const cert = env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); +export function getRegistryProxy( + action: ActionState<["Env"]>, +): ProxyAgent | undefined { + const host = action.env.getOptional(RegistryProxyVars.PROXY_HOST); + const port = action.env.getOptional(RegistryProxyVars.PROXY_PORT); + const cert = action.env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); if (host && port) { return new ProxyAgent({ @@ -102,10 +105,10 @@ export function makeProxyRequestOptions( * This will run API requests through the private registry authentication proxy * if it is configured. * - * @param env The environment to query for the proxy host and port. + * @param action The required Action state. */ -export function getApiFetch(env: ReadOnlyEnv): typeof undiciFetch { - const dispatcher = getRegistryProxy(env); +export function getApiFetch(action: ActionState<["Env"]>): typeof undiciFetch { + const dispatcher = getRegistryProxy(action); const proxiedFetch = (req: RequestInfo, init?: RequestInit) => { return undiciFetch(req, { ...init, dispatcher }); diff --git a/src/config/file.ts b/src/config/file.ts index 38f9b19fd4..5ca723a462 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -73,7 +73,7 @@ export async function getRemoteConfig( Feature.ProxyApiRequests, ); const proxy = shouldProxyRequest - ? api.getRegistryProxy(actionState.env) + ? api.getRegistryProxy(actionState) : undefined; const response = await api From b946565527528c534f210541ae538484e228a3ce Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 16:59:30 +0100 Subject: [PATCH 12/15] Add and use `clone` method for `ReadOnlyEnv` --- lib/entry-points.js | 4 ++++ src/environment.ts | 5 +++++ src/testing-utils.ts | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 0cf4dc2bc0..2fcf8c198c 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -141488,6 +141488,10 @@ var ReadOnlyEnv = class { this.vars = vars; } vars; + /** Clones the object while detaching the underlying environment from the original. */ + clone() { + return Object.create(this, { vars: { value: { ...this.vars } } }); + } /** Tries to get the value for `name` and throws if there isn't one. */ getRequired(name) { return getRequiredEnvVar(this.vars, name); diff --git a/src/environment.ts b/src/environment.ts index 9d52a903ff..1b00ab7cfb 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -265,6 +265,11 @@ export function getOptionalEnvVar(paramName: string): string | undefined { export class ReadOnlyEnv { constructor(protected readonly vars: Record) {} + /** Clones the object while detaching the underlying environment from the original. */ + public clone(): this { + return Object.create(this, { vars: { value: { ...this.vars } } }) as this; + } + /** Tries to get the value for `name` and throws if there isn't one. */ public getRequired(name: string): string { return getRequiredEnvVar(this.vars, name); diff --git a/src/testing-utils.ts b/src/testing-utils.ts index f630082be4..92fad068e1 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -240,7 +240,7 @@ abstract class BaseEnvBuilder< cloneFrom !== undefined ? ({ ...cloneFrom.state, - env: Object.create(cloneFrom.state.env), + env: cloneFrom.state.env.clone(), actions: Object.create(cloneFrom.state.actions), logger: this.logger, } satisfies ActionState) From 47e9c299985f55d8764baec1d5c86f4f2a0c8d50 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 17:02:07 +0100 Subject: [PATCH 13/15] Log whether the proxy is picked up on --- lib/entry-points.js | 6 +++++- src/api-client.ts | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 2fcf8c198c..93f0143c53 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145629,8 +145629,12 @@ function getRegistryProxy(action) { const port = action.env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); const cert = action.env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); if (host && port) { + const uri = `http://${host}:${port}`; + action.logger.debug( + `Using private registry proxy at '${uri}' for API client.` + ); return new import_undici.ProxyAgent({ - uri: `http://${host}:${port}`, + uri, keepAliveTimeout: 10, keepAliveMaxTimeout: 10, requestTls: cert ? { ca: cert } : void 0 diff --git a/src/api-client.ts b/src/api-client.ts index 261e0a557e..3782bc2cff 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -66,15 +66,19 @@ export interface GitHubApiExternalRepoDetails { * or `undefined` if we couldn't retrieve the host and port. */ export function getRegistryProxy( - action: ActionState<["Env"]>, + action: ActionState<["Logger", "Env"]>, ): ProxyAgent | undefined { const host = action.env.getOptional(RegistryProxyVars.PROXY_HOST); const port = action.env.getOptional(RegistryProxyVars.PROXY_PORT); const cert = action.env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); if (host && port) { + const uri = `http://${host}:${port}`; + action.logger.debug( + `Using private registry proxy at '${uri}' for API client.`, + ); return new ProxyAgent({ - uri: `http://${host}:${port}`, + uri, keepAliveTimeout: 10, keepAliveMaxTimeout: 10, requestTls: cert ? { ca: cert } : undefined, @@ -107,7 +111,9 @@ export function makeProxyRequestOptions( * * @param action The required Action state. */ -export function getApiFetch(action: ActionState<["Env"]>): typeof undiciFetch { +export function getApiFetch( + action: ActionState<["Logger", "Env"]>, +): typeof undiciFetch { const dispatcher = getRegistryProxy(action); const proxiedFetch = (req: RequestInfo, init?: RequestInit) => { From a1c676d2f63f55655fd3e8b4ce2738bb06669e1b Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 17:02:22 +0100 Subject: [PATCH 14/15] Test proxy usage for `getRemoteConfig` --- src/config/file.test.ts | 55 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/config/file.test.ts b/src/config/file.test.ts index 4b7b88c9ca..b07ea71ba2 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -1,11 +1,18 @@ +import * as github from "@actions/github"; import test from "ava"; import sinon from "sinon"; +import * as api from "../api-client"; +import { RegistryProxyVars } from "../environment"; import { Feature } from "../feature-flags"; import { RepositoryPropertyName } from "../feature-flags/properties"; -import { callee, setupTests } from "../testing-utils"; +import { + callee, + SAMPLE_DOTCOM_API_DETAILS, + setupTests, +} from "../testing-utils"; -import { getConfigFileInput } from "./file"; +import { getConfigFileInput, getRemoteConfig } from "./file"; setupTests(test); @@ -67,3 +74,47 @@ test("getConfigFileInput ignores repository property value when FF is off", asyn ) .passes(t.is, undefined); }); + +test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { + const client = github.getOctokit("123"); + const response = { + data: { + content: Buffer.from("disable-default-queries: false").toString("base64"), + }, + }; + sinon + .stub(client.rest.repos, "getContent") + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + .resolves(response as any); + sinon.stub(api, "getApiClientWithExternalAuth").value(() => client); + + const target = callee(getRemoteConfig) + .withDefaultActionsEnv() + .withArgs("file.yml", SAMPLE_DOTCOM_API_DETAILS); + + // Should use it when the FF is enabled and the environment variables are set. + await target + .withFeatures([Feature.ProxyApiRequests, Feature.NewRemoteFileAddresses]) + .withEnv((env) => { + env.set(RegistryProxyVars.PROXY_HOST, "localhost"); + env.set(RegistryProxyVars.PROXY_PORT, "1234"); + }) + .logs(t, "Using private registry proxy at 'http://localhost:1234'") + .passes(t.truthy); + + // But not when the FF is not enabled. + await target + .withFeatures([Feature.NewRemoteFileAddresses]) + .withEnv((env) => { + env.set(RegistryProxyVars.PROXY_HOST, "localhost"); + env.set(RegistryProxyVars.PROXY_PORT, "1234"); + }) + .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") + .passes(t.truthy); + + // And not when the environment variables aren't set. + await target + .withFeatures([Feature.ProxyApiRequests, Feature.NewRemoteFileAddresses]) + .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") + .passes(t.truthy); +}); From 1a0eca3555da206b41fe4b8abbba9ef343abcf0f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 19:33:01 +0100 Subject: [PATCH 15/15] Validate `proxy` argument in tests --- src/config/file.test.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/config/file.test.ts b/src/config/file.test.ts index b07ea71ba2..6f6b4a5d91 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -86,7 +86,22 @@ test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { .stub(client.rest.repos, "getContent") // eslint-disable-next-line @typescript-eslint/no-unsafe-argument .resolves(response as any); - sinon.stub(api, "getApiClientWithExternalAuth").value(() => client); + + // We stub `getApiClientWithExternalAuth` so that it throws if no + // proxy is provided and returns the client otherwise. This allows us + // to verify the result in the following test cases. + const errorMessage = "No `proxy` was provided by the caller."; + sinon + .stub(api, "getApiClientWithExternalAuth") + .callsFake((_details, proxy) => { + // Throw if proxy isn't defined. + if (proxy === undefined) { + throw new Error(errorMessage); + } + // Otherwise return the client object. + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return client as unknown as any; + }); const target = callee(getRemoteConfig) .withDefaultActionsEnv() @@ -110,11 +125,11 @@ test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { env.set(RegistryProxyVars.PROXY_PORT, "1234"); }) .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") - .passes(t.truthy); + .throws(t, { message: errorMessage }); // And not when the environment variables aren't set. await target .withFeatures([Feature.ProxyApiRequests, Feature.NewRemoteFileAddresses]) .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") - .passes(t.truthy); + .throws(t, { message: errorMessage }); });