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
+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);
}
}