From de57c4a441d83a777b077184c67bfa79f5bd4457 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 10:38:08 +0100 Subject: [PATCH 1/7] Move `registry_types` to `StatusReportBase` --- src/start-proxy.ts | 8 +------- src/status-report.ts | 6 ++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 74e0b498c4..caa1b3054a 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -83,12 +83,6 @@ export class StartProxyError extends Error { } } -interface StartProxyStatus extends StatusReportBase { - // A comma-separated list of registry types which are configured for CodeQL. - // This only includes registry types we support, not all that are configured. - registry_types: string; -} - /** * Sends a status report for the `start-proxy` action indicating a successful outcome. * @@ -112,7 +106,7 @@ export async function sendSuccessStatusReport( logger, ); if (statusReportBase !== undefined) { - const statusReport: StartProxyStatus = { + const statusReport: StatusReportBase = { ...statusReportBase, registry_types: registry_types.join(","), }; diff --git a/src/status-report.ts b/src/status-report.ts index d9d2a7ba4c..c61bbb828b 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -159,6 +159,12 @@ export interface StatusReportBase { ml_powered_javascript_queries?: string; /** Ref that the workflow was triggered on. */ ref: string; + /** + * A comma-separated list of private registry types which are configured for CodeQL. + * This only includes registry types we support (as determined by the `start-proxy` action), + * not all that are configured. + */ + registry_types?: string; /** Action runner hardware architecture (context runner.arch). */ runner_arch?: string; /** Available disk space on the runner, in bytes. */ From aac07d2a4154cf30c74193cd5c01955a5a0d817e Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 10:55:46 +0100 Subject: [PATCH 2/7] Include `registry_types` whenever `CODEQL_PROXY_URLS` is set --- lib/entry-points.js | 17 ++++++++++++++ src/status-report.test.ts | 47 ++++++++++++++++++++++++++++++++++++++- src/status-report.ts | 33 ++++++++++++++++++++++++++- 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 46c44a8183..24a7eb5c70 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146396,6 +146396,22 @@ function setJobStatusIfUnsuccessful(actionStatus) { ); } } +function getRegistryTypesFromEnv(logger, env = getEnv()) { + const value = env.getOptional("CODEQL_PROXY_URLS" /* PROXY_URLS */); + if (value === void 0) { + return void 0; + } + try { + const data = JSON.parse(value); + const types2 = new Set(data.map((r) => r.type)); + return Array.from(types2).sort().join(","); + } catch (err) { + logger.debug( + `Failed to parse '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}' containing '${value}': ${getErrorMessage(err)}.` + ); + return void 0; + } +} async function createStatusReportBase(actionName, status, actionStartedAt, config, diskInfo, logger, cause, exception) { try { const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || ""; @@ -146435,6 +146451,7 @@ async function createStatusReportBase(actionName, status, actionStartedAt, confi job_name: jobName, job_run_uuid: jobRunUUID, ref, + registry_types: getRegistryTypesFromEnv(logger), runner_os: runnerOs, started_at: workflowStartedAt, status, diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 9086dd34ef..d8ce1b40b4 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -3,15 +3,17 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import { Config } from "./config-utils"; -import { EnvVar } from "./environment"; +import { EnvVar, RegistryProxyVars } from "./environment"; import { BuiltInLanguage } from "./languages"; import { getRunnerLogger } from "./logging"; import { ToolsSource } from "./setup-codeql"; +import type { Registry } from "./start-proxy"; import { ActionName, createInitWithConfigStatusReport, createStatusReportBase, getActionsStatus, + getRegistryTypesFromEnv, InitStatusReport, InitWithConfigStatusReport, } from "./status-report"; @@ -20,11 +22,54 @@ import { setupActionsVars, createTestConfig, makeMacro, + getTestEnv, + RecordingLogger, } from "./testing-utils"; import { BuildMode, ConfigurationError, withTmpDir, wrapError } from "./util"; setupTests(test); +test("getRegistryTypesFromEnv - gets unique registry types from environment", async (t) => { + const logger = new RecordingLogger(true); + const env = getTestEnv({ + [RegistryProxyVars.PROXY_URLS]: JSON.stringify([ + { type: "git_source", url: "https://example.com" }, + { type: "git_source", url: "https://github.com" }, + { type: "docker_registry", url: "https://registry.example.com" }, + ] satisfies Array>), + }); + + const result = getRegistryTypesFromEnv(logger, env); + t.deepEqual(result, ["git_source", "docker_registry"].sort().join(",")); +}); + +test("getRegistryTypesFromEnv - returns undefined if the env var is not set", async (t) => { + const logger = new RecordingLogger(true); + const env = getTestEnv({}); + + const result = getRegistryTypesFromEnv(logger, env); + t.is(result, undefined); +}); + +test("getRegistryTypesFromEnv - returns undefined if the env var is not valid JSON", async (t) => { + const logger = new RecordingLogger(true); + const env = getTestEnv({ [RegistryProxyVars.PROXY_URLS]: "[" }); + + const result = getRegistryTypesFromEnv(logger, env); + t.is(result, undefined); +}); + +test("getRegistryTypesFromEnv - returns undefined if the env var is unexpected JSON", async (t) => { + const logger = new RecordingLogger(true); + const env = getTestEnv({ + // Top-level object rather than an array of objects. + [RegistryProxyVars.PROXY_URLS]: JSON.stringify({ type: "git_source" }), + }); + + const result = getRegistryTypesFromEnv(logger, env); + t.is(result, undefined); +}); + function setupEnvironmentAndStub(tmpDir: string) { setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic", diff --git a/src/status-report.ts b/src/status-report.ts index c61bbb828b..5778081153 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -17,12 +17,13 @@ import type { ComputedInput, InputName } from "./config/inputs"; import { parseRegistriesWithoutCredentials } from "./config/pack-registries"; import type { DependencyCacheRestoreStatusReport } from "./dependency-caching"; import { DocUrl } from "./doc-url"; -import { EnvVar } from "./environment"; +import { EnvVar, getEnv, ReadOnlyEnv, RegistryProxyVars } from "./environment"; import { getRef } from "./git-utils"; import type { Logger } from "./logging"; import type { OverlayBaseDatabaseDownloadStats } from "./overlay/caching"; import { getRepositoryNwo } from "./repository"; import type { ToolsSource } from "./setup-codeql"; +import type { Registry } from "./start-proxy"; import { ConfigurationError, getRequiredEnvParam, @@ -268,6 +269,35 @@ export interface EventReport { started_at: string; } +/** + * Attempts to retrieve a list of private registry types from the `CODEQL_PROXY_URLS` environment + * variable and returns it as a comma-separated string if successful. Returns `undefined` otherwise. + */ +export function getRegistryTypesFromEnv( + logger: Logger, + env: ReadOnlyEnv = getEnv(), +): string | undefined { + // Try to get the value of the environment variable. + const value = env.getOptional(RegistryProxyVars.PROXY_URLS); + + if (value === undefined) { + return undefined; + } + + // Try to parse the JSON we expect to find in it and return the comma-separated list of + // (unique) registry types. + try { + const data = JSON.parse(value) as Registry[]; + const types = new Set(data.map((r) => r.type)); + return Array.from(types).sort().join(","); + } catch (err) { + logger.debug( + `Failed to parse '${RegistryProxyVars.PROXY_URLS}' containing '${value}': ${getErrorMessage(err)}.`, + ); + return undefined; + } +} + /** * Compose a StatusReport. * @@ -330,6 +360,7 @@ export async function createStatusReportBase( job_name: jobName, job_run_uuid: jobRunUUID, ref, + registry_types: getRegistryTypesFromEnv(logger), runner_os: runnerOs, started_at: workflowStartedAt, status, From eb692f8b49def92b0d25277bd2be0639251c8a81 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 10:57:52 +0100 Subject: [PATCH 3/7] Add check to `createStatusReportBase` test --- src/status-report.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/status-report.test.ts b/src/status-report.test.ts index d8ce1b40b4..9d3ce0f555 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -79,6 +79,9 @@ function setupEnvironmentAndStub(tmpDir: string) { process.env[EnvVar.ANALYSIS_KEY] = "analysis-key"; process.env["ImageVersion"] = "2023.05.19.1"; + process.env[RegistryProxyVars.PROXY_URLS] = JSON.stringify([ + { type: "maven_repository" }, + ] satisfies Array>); const getRequiredInput = sinon.stub(actionsUtil, "getRequiredInput"); getRequiredInput.withArgs("matrix").resolves("input/matrix"); @@ -122,6 +125,7 @@ test.serial("createStatusReportBase", async (t) => { t.is(typeof statusReport.job_run_uuid, "string"); t.is(statusReport.languages, "java,swift"); t.is(statusReport.ref, process.env["GITHUB_REF"]!); + t.is(statusReport.registry_types, "maven_repository"); t.is(statusReport.runner_available_disk_space_bytes, 100); t.is(statusReport.runner_image_version, process.env["ImageVersion"]); t.is(statusReport.runner_os, process.env["RUNNER_OS"]!); From e893985e8b57c9f9c845bc3320a4fb540653da70 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:19:21 +0100 Subject: [PATCH 4/7] Fix `makeValidator` returning `required: boolean` --- lib/entry-points.js | 4 ++-- src/json/index.ts | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 24a7eb5c70..a69c9c02fd 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -144598,11 +144598,11 @@ function isStringOrUndefined(value) { function defaultCheck(validate) { return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) }); } -function makeValidator(validate, required = true) { +function makeValidator(validate) { return { validate, check: defaultCheck(validate), - required + required: true }; } var string = makeValidator(isString); diff --git a/src/json/index.ts b/src/json/index.ts index 78923f8bac..f040acc932 100644 --- a/src/json/index.ts +++ b/src/json/index.ts @@ -62,14 +62,11 @@ function defaultCheck( return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) }); } -function makeValidator( - validate: (arg: unknown) => arg is T, - required: boolean = true, -) { +function makeValidator(validate: (arg: unknown) => arg is T) { return { validate, check: defaultCheck(validate), - required, + required: true, } as const satisfies Validator; } From 51d51e81216d2a2764c063e5c4ca37c12aa92eb9 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:19:57 +0100 Subject: [PATCH 5/7] Add `boolean` `Validator` to `json` module --- lib/entry-points.js | 8 ++++++-- src/json/index.ts | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index a69c9c02fd..302bff49af 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -92812,10 +92812,10 @@ var require_util12 = __commonJS({ return objectToString(arg) === "[object Array]"; } exports2.isArray = isArray2; - function isBoolean(arg) { + function isBoolean2(arg) { return typeof arg === "boolean"; } - exports2.isBoolean = isBoolean; + exports2.isBoolean = isBoolean2; function isNull(arg) { return arg === null; } @@ -144592,6 +144592,9 @@ function isString(value) { function isNumber(value) { return typeof value === "number"; } +function isBoolean(value) { + return typeof value === "boolean"; +} function isStringOrUndefined(value) { return value === void 0 || isString(value); } @@ -144607,6 +144610,7 @@ function makeValidator(validate) { } var string = makeValidator(isString); var number = makeValidator(isNumber); +var boolean = makeValidator(isBoolean); function array(validator) { const validate = (val) => { return isArray(val) && val.every((e) => validator.validate(e)); diff --git a/src/json/index.ts b/src/json/index.ts index f040acc932..d3d3abac0c 100644 --- a/src/json/index.ts +++ b/src/json/index.ts @@ -35,6 +35,11 @@ export function isNumber(value: unknown): value is number { return typeof value === "number"; } +/** Asserts that `value` is a boolean. */ +export function isBoolean(value: unknown): value is boolean { + return typeof value === "boolean"; +} + /** Asserts that `value` is either a string or undefined. */ export function isStringOrUndefined( value: unknown, @@ -79,6 +84,9 @@ export const string = makeValidator(isString); /** A validator for number fields in schemas. */ export const number = makeValidator(isNumber); +/** A validator for boolean fields in schemas. */ +export const boolean = makeValidator(isBoolean); + /** A validator for arrays. */ export function array(validator: Validator) { const validate = (val: unknown) => { From e55a57b808525a6830cbf9c336f7ae221169a3a3 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:21:19 +0100 Subject: [PATCH 6/7] Add `RegistryBase` schema and type --- lib/entry-points.js | 6 ++++++ src/start-proxy/types.ts | 16 +++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 302bff49af..d52d8169cd 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -161989,6 +161989,12 @@ function credentialToStr(credential) { } return result; } +var registryBaseSchema = { + /** The type of the package registry. */ + type: string, + /** Whether the registry replaces the base registry for the ecosystem. */ + "replaces-base": optional(boolean) +}; function getAddressString(address) { if (address.url === void 0) { return address.host; diff --git a/src/start-proxy/types.ts b/src/start-proxy/types.ts index 13369edbfa..17803e9126 100644 --- a/src/start-proxy/types.ts +++ b/src/start-proxy/types.ts @@ -254,13 +254,19 @@ export function credentialToStr(credential: Credential): string { return result; } -/** A package registry is identified by its type and address. */ -export type Registry = { +/** The schema for `RegistryBase` objects. */ +export const registryBaseSchema = { /** The type of the package registry. */ - type: string; + type: json.string, /** Whether the registry replaces the base registry for the ecosystem. */ - "replaces-base"?: boolean; -} & Address; + "replaces-base": json.optional(json.boolean), +} as const satisfies json.Schema; + +/** Information about a registry, other than its address. */ +export type RegistryBase = json.FromSchema; + +/** A package registry is identified by its type and address. */ +export type Registry = RegistryBase & Address; // If a registry has an `url`, then that takes precedence over the `host` which may or may // not be defined. From 13d4882649ba1a2a6abb6c2303df10658daa41f7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 29 Jul 2026 11:35:46 +0100 Subject: [PATCH 7/7] Validate JSON more --- lib/entry-points.js | 316 ++++++++++++++++++++------------------ src/json/index.ts | 17 ++ src/status-report.test.ts | 26 +++- src/status-report.ts | 22 ++- 4 files changed, 222 insertions(+), 159 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index d52d8169cd..53b7f491af 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -96885,7 +96885,7 @@ var require_validators = __commonJS({ throw new ERR_INVALID_ARG_TYPE(name, "a dictionary", value); } }); - var validateArray = hideStackFrames((value, name, minLength = 0) => { + var validateArray2 = hideStackFrames((value, name, minLength = 0) => { if (!ArrayIsArray(value)) { throw new ERR_INVALID_ARG_TYPE(name, "Array", value); } @@ -96895,19 +96895,19 @@ var require_validators = __commonJS({ } }); function validateStringArray(value, name) { - validateArray(value, name); + validateArray2(value, name); for (let i = 0; i < value.length; i++) { validateString(value[i], `${name}[${i}]`); } } function validateBooleanArray(value, name) { - validateArray(value, name); + validateArray2(value, name); for (let i = 0; i < value.length; i++) { validateBoolean(value[i], `${name}[${i}]`); } } function validateAbortSignalArray(value, name) { - validateArray(value, name); + validateArray2(value, name); for (let i = 0; i < value.length; i++) { const signal = value[i]; const indexedName = `${name}[${i}]`; @@ -97003,7 +97003,7 @@ var require_validators = __commonJS({ isInt32, isUint32, parseFileMode, - validateArray, + validateArray: validateArray2, validateStringArray, validateBooleanArray, validateAbortSignalArray, @@ -144692,6 +144692,10 @@ function validateSchema(schema, obj) { const result = checkSchema(schema, obj, { failFast: true }); return result.valid; } +function validateArray(elementSchema, arr) { + const elementValidator = object(elementSchema); + return array(elementValidator).validate(arr); +} function successfulCheckSchema() { return { valid: true, @@ -146343,6 +146347,148 @@ async function getGeneratedFiles(workingDirectory) { return generatedFiles; } +// src/start-proxy/types.ts +var usernameSchema = { + /** The username needed to authenticate to the package registry, if any. */ + username: optionalOrNull(string) +}; +function hasUsername(config) { + return "username" in config; +} +var usernamePasswordSchema = { + /** The password needed to authenticate to the package registry, if any. */ + password: optionalOrNull(string), + ...usernameSchema +}; +function hasUsernameAndPassword(config) { + return hasUsername(config) && "password" in config; +} +var tokenSchema = { + /** The token needed to authenticate to the package registry, if any. */ + token: optionalOrNull(string), + ...usernameSchema +}; +function hasToken(config) { + return "token" in config; +} +function isToken(config) { + return "token" in config && validateSchema(tokenSchema, config); +} +var azureConfigSchema = { + "tenant-id": string, + "client-id": string +}; +function isAzureConfig(config) { + return validateSchema(azureConfigSchema, config); +} +var awsConfigSchema = { + "aws-region": string, + "account-id": string, + "role-name": string, + domain: string, + "domain-owner": string, + audience: optionalOrNull(string) +}; +function isAWSConfig(config) { + return validateSchema(awsConfigSchema, config); +} +var jfrogConfigSchema = { + "jfrog-oidc-provider-name": string, + audience: optionalOrNull(string), + "identity-mapping-name": optionalOrNull(string) +}; +function isJFrogConfig(config) { + return validateSchema(jfrogConfigSchema, config); +} +var cloudsmithConfigSchema = { + namespace: string, + "service-slug": string, + "api-host": string +}; +function isCloudsmithConfig(config) { + return validateSchema(cloudsmithConfigSchema, config); +} +var gcpConfigSchema = { + "workload-identity-provider": string, + "service-account": optionalOrNull(string), + audience: optionalOrNull(string) +}; +function isGCPConfig(config) { + return validateSchema(gcpConfigSchema, config); +} +var oidcSchemas = [ + { schema: azureConfigSchema, name: "Azure" }, + { schema: awsConfigSchema, name: "AWS" }, + { schema: jfrogConfigSchema, name: "JFrog" }, + { schema: cloudsmithConfigSchema, name: "Cloudsmith" }, + { schema: gcpConfigSchema, name: "GCP" } +]; +function credentialToStr(credential) { + let result = `Type: ${credential.type};`; + const appendIfDefined = (name, val) => { + if (isDefined2(val)) { + result += ` ${name}: ${val};`; + } + }; + appendIfDefined("Url", credential.url); + appendIfDefined("Host", credential.host); + if (hasUsername(credential)) { + appendIfDefined("Username", credential.username); + } + if ("password" in credential) { + appendIfDefined( + "Password", + isDefined2(credential.password) ? "***" : void 0 + ); + } + if (hasToken(credential)) { + appendIfDefined("Token", isDefined2(credential.token) ? "***" : void 0); + } + if (isAzureConfig(credential)) { + appendIfDefined("Tenant", credential["tenant-id"]); + appendIfDefined("Client", credential["client-id"]); + } else if (isAWSConfig(credential)) { + appendIfDefined("AWS Region", credential["aws-region"]); + appendIfDefined("AWS Account", credential["account-id"]); + appendIfDefined("AWS Role", credential["role-name"]); + appendIfDefined("AWS Domain", credential.domain); + appendIfDefined("AWS Domain Owner", credential["domain-owner"]); + appendIfDefined("AWS Audience", credential.audience); + } else if (isJFrogConfig(credential)) { + appendIfDefined("JFrog Provider", credential["jfrog-oidc-provider-name"]); + appendIfDefined( + "JFrog Identity Mapping", + credential["identity-mapping-name"] + ); + appendIfDefined("JFrog Audience", credential.audience); + } else if (isCloudsmithConfig(credential)) { + appendIfDefined("Cloudsmith Namespace", credential.namespace); + appendIfDefined("Cloudsmith Service Slug", credential["service-slug"]); + appendIfDefined("Cloudsmith API Host", credential["api-host"]); + } else if (isGCPConfig(credential)) { + appendIfDefined( + "GCP Workload Identity Provider", + credential["workload-identity-provider"] + ); + appendIfDefined("GCP Service Account", credential["service-account"]); + appendIfDefined("GCP Audience", credential.audience); + } + return result; +} +var registryBaseSchema = { + /** The type of the package registry. */ + type: string, + /** Whether the registry replaces the base registry for the ecosystem. */ + "replaces-base": optional(boolean) +}; +function getAddressString(address) { + if (address.url === void 0) { + return address.host; + } else { + return address.url; + } +} + // src/status-report.ts function getDisplayActionName(actionName) { if (actionName === "finish" /* Analyze */) { @@ -146407,11 +146553,23 @@ function getRegistryTypesFromEnv(logger, env = getEnv()) { } try { const data = JSON.parse(value); + if (!isArray(data)) { + logger.debug( + `Expected '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}' to contain a JSON array, but got '${typeof data}'.` + ); + return void 0; + } + if (!validateArray(registryBaseSchema, data)) { + logger.debug( + `Expected '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}' to contain a JSON array of registry objects, but got something else.` + ); + return void 0; + } const types2 = new Set(data.map((r) => r.type)); return Array.from(types2).sort().join(","); } catch (err) { logger.debug( - `Failed to parse '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}' containing '${value}': ${getErrorMessage(err)}.` + `Failed to parse '${"CODEQL_PROXY_URLS" /* PROXY_URLS */}': ${getErrorMessage(err)}.` ); return void 0; } @@ -157400,7 +157558,7 @@ var import_async = __toESM(require_async(), 1); var import_path6 = require("path"); // node_modules/archiver/lib/error.js -var import_util33 = __toESM(require("util"), 1); +var import_util34 = __toESM(require("util"), 1); var ERROR_CODES = { ABORTED: "archive was aborted", DIRECTORYDIRPATHREQUIRED: "diretory dirpath argument must be a non-empty string value", @@ -157425,7 +157583,7 @@ function ArchiverError(code, data) { this.code = code; this.data = data; } -import_util33.default.inherits(ArchiverError, Error); +import_util34.default.inherits(ArchiverError, Error); // node_modules/archiver/lib/core.js var import_readable_stream2 = __toESM(require_ours(), 1); @@ -161861,148 +162019,6 @@ var path26 = __toESM(require("path")); var core26 = __toESM(require_core()); var toolcache4 = __toESM(require_tool_cache()); -// src/start-proxy/types.ts -var usernameSchema = { - /** The username needed to authenticate to the package registry, if any. */ - username: optionalOrNull(string) -}; -function hasUsername(config) { - return "username" in config; -} -var usernamePasswordSchema = { - /** The password needed to authenticate to the package registry, if any. */ - password: optionalOrNull(string), - ...usernameSchema -}; -function hasUsernameAndPassword(config) { - return hasUsername(config) && "password" in config; -} -var tokenSchema = { - /** The token needed to authenticate to the package registry, if any. */ - token: optionalOrNull(string), - ...usernameSchema -}; -function hasToken(config) { - return "token" in config; -} -function isToken(config) { - return "token" in config && validateSchema(tokenSchema, config); -} -var azureConfigSchema = { - "tenant-id": string, - "client-id": string -}; -function isAzureConfig(config) { - return validateSchema(azureConfigSchema, config); -} -var awsConfigSchema = { - "aws-region": string, - "account-id": string, - "role-name": string, - domain: string, - "domain-owner": string, - audience: optionalOrNull(string) -}; -function isAWSConfig(config) { - return validateSchema(awsConfigSchema, config); -} -var jfrogConfigSchema = { - "jfrog-oidc-provider-name": string, - audience: optionalOrNull(string), - "identity-mapping-name": optionalOrNull(string) -}; -function isJFrogConfig(config) { - return validateSchema(jfrogConfigSchema, config); -} -var cloudsmithConfigSchema = { - namespace: string, - "service-slug": string, - "api-host": string -}; -function isCloudsmithConfig(config) { - return validateSchema(cloudsmithConfigSchema, config); -} -var gcpConfigSchema = { - "workload-identity-provider": string, - "service-account": optionalOrNull(string), - audience: optionalOrNull(string) -}; -function isGCPConfig(config) { - return validateSchema(gcpConfigSchema, config); -} -var oidcSchemas = [ - { schema: azureConfigSchema, name: "Azure" }, - { schema: awsConfigSchema, name: "AWS" }, - { schema: jfrogConfigSchema, name: "JFrog" }, - { schema: cloudsmithConfigSchema, name: "Cloudsmith" }, - { schema: gcpConfigSchema, name: "GCP" } -]; -function credentialToStr(credential) { - let result = `Type: ${credential.type};`; - const appendIfDefined = (name, val) => { - if (isDefined2(val)) { - result += ` ${name}: ${val};`; - } - }; - appendIfDefined("Url", credential.url); - appendIfDefined("Host", credential.host); - if (hasUsername(credential)) { - appendIfDefined("Username", credential.username); - } - if ("password" in credential) { - appendIfDefined( - "Password", - isDefined2(credential.password) ? "***" : void 0 - ); - } - if (hasToken(credential)) { - appendIfDefined("Token", isDefined2(credential.token) ? "***" : void 0); - } - if (isAzureConfig(credential)) { - appendIfDefined("Tenant", credential["tenant-id"]); - appendIfDefined("Client", credential["client-id"]); - } else if (isAWSConfig(credential)) { - appendIfDefined("AWS Region", credential["aws-region"]); - appendIfDefined("AWS Account", credential["account-id"]); - appendIfDefined("AWS Role", credential["role-name"]); - appendIfDefined("AWS Domain", credential.domain); - appendIfDefined("AWS Domain Owner", credential["domain-owner"]); - appendIfDefined("AWS Audience", credential.audience); - } else if (isJFrogConfig(credential)) { - appendIfDefined("JFrog Provider", credential["jfrog-oidc-provider-name"]); - appendIfDefined( - "JFrog Identity Mapping", - credential["identity-mapping-name"] - ); - appendIfDefined("JFrog Audience", credential.audience); - } else if (isCloudsmithConfig(credential)) { - appendIfDefined("Cloudsmith Namespace", credential.namespace); - appendIfDefined("Cloudsmith Service Slug", credential["service-slug"]); - appendIfDefined("Cloudsmith API Host", credential["api-host"]); - } else if (isGCPConfig(credential)) { - appendIfDefined( - "GCP Workload Identity Provider", - credential["workload-identity-provider"] - ); - appendIfDefined("GCP Service Account", credential["service-account"]); - appendIfDefined("GCP Audience", credential.audience); - } - return result; -} -var registryBaseSchema = { - /** The type of the package registry. */ - type: string, - /** Whether the registry replaces the base registry for the ecosystem. */ - "replaces-base": optional(boolean) -}; -function getAddressString(address) { - if (address.url === void 0) { - return address.host; - } else { - return address.url; - } -} - // src/start-proxy/validation.ts var core25 = __toESM(require_core()); function cloneCredential(schema, obj) { diff --git a/src/json/index.ts b/src/json/index.ts index d3d3abac0c..d8764ec478 100644 --- a/src/json/index.ts +++ b/src/json/index.ts @@ -226,6 +226,23 @@ export function validateSchema< return result.valid; } +/** + * Validates that `arr` is an array whose elements satisfy at least `elementSchema`. + * Additional keys are accepted in each element. + * + * @param elementSchema The schema to validate the elements against. + * @param arr The array to validate. + * @returns Asserts that `arr` has elements of `schema`'s type if validation is successful. + */ +export function validateArray< + S extends Schema, + T extends UnvalidatedArray = Array>, +>(elementSchema: S, arr: UnvalidatedArray): arr is T { + const elementValidator = object(elementSchema); + + return array(elementValidator).validate(arr); +} + export interface CheckSchemaOptions { /** Whether to stop validation after the first error. */ failFast?: boolean; diff --git a/src/status-report.test.ts b/src/status-report.test.ts index 9d3ce0f555..917a2e4d8e 100644 --- a/src/status-report.test.ts +++ b/src/status-report.test.ts @@ -61,13 +61,27 @@ test("getRegistryTypesFromEnv - returns undefined if the env var is not valid JS test("getRegistryTypesFromEnv - returns undefined if the env var is unexpected JSON", async (t) => { const logger = new RecordingLogger(true); - const env = getTestEnv({ - // Top-level object rather than an array of objects. - [RegistryProxyVars.PROXY_URLS]: JSON.stringify({ type: "git_source" }), - }); - const result = getRegistryTypesFromEnv(logger, env); - t.is(result, undefined); + t.is( + getRegistryTypesFromEnv( + logger, + getTestEnv({ + // Top-level object rather than an array of objects. + [RegistryProxyVars.PROXY_URLS]: JSON.stringify({ type: "git_source" }), + }), + ), + undefined, + ); + t.is( + getRegistryTypesFromEnv( + logger, + getTestEnv({ + // Object has no "type" key. + [RegistryProxyVars.PROXY_URLS]: JSON.stringify([{}]), + }), + ), + undefined, + ); }); function setupEnvironmentAndStub(tmpDir: string) { diff --git a/src/status-report.ts b/src/status-report.ts index 5778081153..a6b263ee08 100644 --- a/src/status-report.ts +++ b/src/status-report.ts @@ -19,11 +19,12 @@ import type { DependencyCacheRestoreStatusReport } from "./dependency-caching"; import { DocUrl } from "./doc-url"; import { EnvVar, getEnv, ReadOnlyEnv, RegistryProxyVars } from "./environment"; import { getRef } from "./git-utils"; +import * as json from "./json"; import type { Logger } from "./logging"; import type { OverlayBaseDatabaseDownloadStats } from "./overlay/caching"; import { getRepositoryNwo } from "./repository"; import type { ToolsSource } from "./setup-codeql"; -import type { Registry } from "./start-proxy"; +import { registryBaseSchema } from "./start-proxy/types"; import { ConfigurationError, getRequiredEnvParam, @@ -287,12 +288,27 @@ export function getRegistryTypesFromEnv( // Try to parse the JSON we expect to find in it and return the comma-separated list of // (unique) registry types. try { - const data = JSON.parse(value) as Registry[]; + const data = JSON.parse(value) as unknown; + + // Check that the parsed JSON meets our expectations. + if (!json.isArray(data)) { + logger.debug( + `Expected '${RegistryProxyVars.PROXY_URLS}' to contain a JSON array, but got '${typeof data}'.`, + ); + return undefined; + } + if (!json.validateArray(registryBaseSchema, data)) { + logger.debug( + `Expected '${RegistryProxyVars.PROXY_URLS}' to contain a JSON array of registry objects, but got something else.`, + ); + return undefined; + } + const types = new Set(data.map((r) => r.type)); return Array.from(types).sort().join(","); } catch (err) { logger.debug( - `Failed to parse '${RegistryProxyVars.PROXY_URLS}' containing '${value}': ${getErrorMessage(err)}.`, + `Failed to parse '${RegistryProxyVars.PROXY_URLS}': ${getErrorMessage(err)}.`, ); return undefined; }