Add StartProxyError for status-report-safe errors, and use for proxy download

This commit is contained in:
Michael B. Gale
2026-01-29 12:38:04 +00:00
parent 4dcc8a9cdc
commit f1588cde0c
4 changed files with 144 additions and 22 deletions
+31 -13
View File
@@ -115932,7 +115932,7 @@ var require_cache5 = __commonJS({
var import_child_process = require("child_process");
var path = __toESM(require("path"));
var core11 = __toESM(require_core());
var toolcache = __toESM(require_tool_cache());
var toolcache2 = __toESM(require_tool_cache());
var import_node_forge = __toESM(require_lib2());
// src/actions-util.ts
@@ -118929,6 +118929,7 @@ function getActionsLogger() {
// src/start-proxy.ts
var core10 = __toESM(require_core());
var toolcache = __toESM(require_tool_cache());
// src/artifact-scanner.ts
var exec = __toESM(require_exec());
@@ -119573,6 +119574,11 @@ async function sendUnhandledErrorStatusReport(actionName, actionStartedAt, error
}
// src/start-proxy.ts
var StartProxyError = class extends Error {
constructor(errorType) {
super(errorType);
}
};
async function sendSuccessStatusReport(startedAt, config, registry_types, logger) {
const statusReportBase = await createStatusReportBase(
"start-proxy" /* StartProxy */,
@@ -119590,9 +119596,16 @@ async function sendSuccessStatusReport(startedAt, config, registry_types, logger
await sendStatusReport(statusReport);
}
}
function getSafeErrorMessage(error3) {
if (error3 instanceof StartProxyError) {
return error3.message;
}
return `Error from start-proxy Action omitted (${typeof error3}).`;
}
async function sendFailedStatusReport(logger, startedAt, language, unwrappedError) {
const error3 = wrapError(unwrappedError);
core10.setFailed(`start-proxy action failed: ${error3.message}`);
const statusReportMessage = getSafeErrorMessage(error3);
const errorStatusReportBase = await createStatusReportBase(
"start-proxy" /* StartProxy */,
getActionsStatus(error3),
@@ -119602,7 +119615,7 @@ async function sendFailedStatusReport(logger, startedAt, language, unwrappedErro
},
await checkDiskUsage(logger),
logger,
"Error from start-proxy Action omitted"
statusReportMessage
);
if (errorStatusReportBase !== void 0) {
await sendStatusReport(errorStatusReportBase);
@@ -119761,6 +119774,18 @@ async function getDownloadUrl(logger) {
function credentialToStr(c) {
return `Type: ${c.type}; Host: ${c.host}; Url: ${c.url} Username: ${c.username}; Password: ${c.password !== void 0}; Token: ${c.token !== void 0}`;
}
async function downloadProxy(logger, url, authorization) {
try {
return toolcache.downloadTool(url, void 0, authorization, {
accept: "application/octet-stream"
});
} catch (error3) {
logger.error(
`Failed to download proxy archive from ${url}: ${getErrorMessage(error3)}`
);
throw new StartProxyError("Failed to download proxy archive." /* DownloadFailed */);
}
}
// src/start-proxy-action.ts
var KEY_SIZE = 2048;
@@ -119915,7 +119940,7 @@ async function startProxy(binPath, config, logFilePath, logger) {
async function getProxyBinaryPath(logger) {
const proxyFileName = process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY;
const proxyInfo = await getDownloadUrl(logger);
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
let proxyBin = toolcache2.find(proxyFileName, proxyInfo.version);
if (!proxyBin) {
const apiDetails = getApiDetails();
const authorization = getAuthorizationHeaderFor(
@@ -119923,16 +119948,9 @@ async function getProxyBinaryPath(logger) {
apiDetails,
proxyInfo.url
);
const temp = await toolcache.downloadTool(
proxyInfo.url,
void 0,
authorization,
{
accept: "application/octet-stream"
}
);
const extracted = await toolcache.extractTar(temp);
proxyBin = await toolcache.cacheDir(
const temp = await downloadProxy(logger, proxyInfo.url, authorization);
const extracted = await toolcache2.extractTar(temp);
proxyBin = await toolcache2.cacheDir(
extracted,
proxyFileName,
proxyInfo.version
+2 -8
View File
@@ -12,6 +12,7 @@ import { getActionsLogger, Logger } from "./logging";
import {
Credential,
credentialToStr,
downloadProxy,
getCredentials,
getDownloadUrl,
parseLanguage,
@@ -238,14 +239,7 @@ async function getProxyBinaryPath(logger: Logger): Promise<string> {
apiDetails,
proxyInfo.url,
);
const temp = await toolcache.downloadTool(
proxyInfo.url,
undefined,
authorization,
{
accept: "application/octet-stream",
},
);
const temp = await downloadProxy(logger, proxyInfo.url, authorization);
const extracted = await toolcache.extractTar(temp);
proxyBin = await toolcache.cacheDir(
extracted,
+45
View File
@@ -1,3 +1,4 @@
import * as toolcache from "@actions/tool-cache";
import test from "ava";
import sinon from "sinon";
@@ -318,3 +319,47 @@ test("credentialToStr - hides passwords/tokens", (t) => {
.includes(secret),
);
});
test("getSafeErrorMessage - returns actual message for `StartProxyError`", (t) => {
const error = new startProxyExports.StartProxyError(
startProxyExports.StartProxyErrorType.DownloadFailed,
);
t.is(startProxyExports.getSafeErrorMessage(error), error.message);
});
test("getSafeErrorMessage - does not return message for arbitrary errors", (t) => {
const error = new Error(startProxyExports.StartProxyErrorType.DownloadFailed);
const message = startProxyExports.getSafeErrorMessage(error);
t.not(message, error.message);
t.assert(message.startsWith("Error from start-proxy Action omitted"));
t.assert(message.includes(typeof error));
});
test("downloadProxy - returns file path on success", async (t) => {
const loggedMessages = [];
const logger = getRecordingLogger(loggedMessages);
const testPath = "/some/path";
sinon.stub(toolcache, "downloadTool").resolves(testPath);
const result = await startProxyExports.downloadProxy(
logger,
"url",
undefined,
);
t.is(result, testPath);
});
test("downloadProxy - wraps errors on failure", async (t) => {
const loggedMessages = [];
const logger = getRecordingLogger(loggedMessages);
sinon.stub(toolcache, "downloadTool").throws();
await t.throwsAsync(
startProxyExports.downloadProxy(logger, "url", undefined),
{
instanceOf: startProxyExports.StartProxyError,
},
);
});
+66 -1
View File
@@ -1,4 +1,5 @@
import * as core from "@actions/core";
import * as toolcache from "@actions/tool-cache";
import { getApiClient } from "./api-client";
import * as artifactScanner from "./artifact-scanner";
@@ -16,6 +17,26 @@ import {
import * as util from "./util";
import { ConfigurationError, getErrorMessage, isDefined } from "./util";
/**
* Enumerates specific error types, along with suitable error messages, for errors
* that we want to track in status reports.
*/
export enum StartProxyErrorType {
DownloadFailed = "Failed to download proxy archive.",
}
/**
* We want to avoid accidentally leaking secrets that may be contained in exception
* messages in the `start-proxy` action. Consequently, we don't report the messages
* of arbitrary exceptions. This type of error ensures that the message is one from
* `StartProxyErrorType` and therefore safe to include in a status report.
*/
export class StartProxyError extends Error {
constructor(errorType: StartProxyErrorType) {
super(errorType);
}
}
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.
@@ -53,6 +74,23 @@ export async function sendSuccessStatusReport(
}
}
/**
* Returns an error message for `error` that can safely be reported in a status report,
* i.e. that does not contain sensitive information.
*
* @param error The error for which to get an error message.
*/
export function getSafeErrorMessage(error: Error): string {
// If the error is a `StartProxyError`, the constructor ensures that the
// message comes from `StartProxyErrorType`.
if (error instanceof StartProxyError) {
return error.message;
}
// Otherwise, omit the actual error message.
return `Error from start-proxy Action omitted (${typeof error}).`;
}
/**
* Sends a status report for the `start-proxy` action indicating a failure.
*
@@ -70,6 +108,8 @@ export async function sendFailedStatusReport(
const error = util.wrapError(unwrappedError);
core.setFailed(`start-proxy action failed: ${error.message}`);
const statusReportMessage = getSafeErrorMessage(error);
// We skip sending the error message and stack trace here to avoid the possibility
// of leaking any sensitive information into the telemetry.
const errorStatusReportBase = await createStatusReportBase(
@@ -81,7 +121,7 @@ export async function sendFailedStatusReport(
},
await util.checkDiskUsage(logger),
logger,
"Error from start-proxy Action omitted",
statusReportMessage,
);
if (errorStatusReportBase !== undefined) {
await sendStatusReport(errorStatusReportBase);
@@ -369,3 +409,28 @@ export function credentialToStr(c: Credential): string {
c.username
}; Password: ${c.password !== undefined}; Token: ${c.token !== undefined}`;
}
/**
* Attempts to download a file from `url` into the toolcache.
*
* @param logger THe logger to use.
* @param url The URL to download the proxy binary from.
* @param authorization The authorization information to use.
* @returns If successful, the path to the downloaded file.
*/
export async function downloadProxy(
logger: Logger,
url: string,
authorization: string | undefined,
) {
try {
return toolcache.downloadTool(url, undefined, authorization, {
accept: "application/octet-stream",
});
} catch (error) {
logger.error(
`Failed to download proxy archive from ${url}: ${getErrorMessage(error)}`,
);
throw new StartProxyError(StartProxyErrorType.DownloadFailed);
}
}