Fix Pre-step hang on agent API timeout + guard against unhandled rejections
- Replace @actions/http-client socketTimeout with fetch + AbortSignal.timeout(3s) on monitor, tls-inspect, policy fetch, policy-store fetch, and addSummary, so DNS + TCP connect + TLS are bounded (was unbounded, causing ~2-4 min hangs) - Add unhandledRejection guard in setup.ts and cleanup.ts so Node 22+ does not silently kill the step on background async errors from third-party deps - Skip Windows post-step cleanup when agent dir is missing (Pre-step crashed before install), instead of throwing ENOENT on post_event.json - Bump @types/node to ^24, typescript to ^5, ts-jest to ^29.4 to match node24 runtime and enable AbortSignal.timeout typings
This commit is contained in:
Vendored
+13
-16
@@ -31980,8 +31980,8 @@ const processLogLine = (line, tableEntries) => {
|
||||
}
|
||||
};
|
||||
function addSummary() {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
if (process.env.STATE_addSummary !== "true") {
|
||||
return;
|
||||
}
|
||||
@@ -32022,7 +32022,9 @@ function addSummary() {
|
||||
// Fetch job summary from API
|
||||
const apiUrl = `${STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/runs/${run_id}/correlation/${correlation_id}/job-markdown-summary`;
|
||||
try {
|
||||
const response = yield fetch(apiUrl);
|
||||
const response = yield fetch(apiUrl, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch job summary: ${response.status} ${response.statusText}`);
|
||||
return;
|
||||
@@ -32087,8 +32089,6 @@ const configs_STEPSECURITY_API_URL = (/* unused pure expression or super */ null
|
||||
const STEPSECURITY_TELEMETRY_URL = "https://prod.app-api.stepsecurity.io/v1";
|
||||
const STEPSECURITY_WEB_URL = "https://app.stepsecurity.io";
|
||||
|
||||
// EXTERNAL MODULE: ./node_modules/@actions/http-client/lib/index.js
|
||||
var lib = __nccwpck_require__(4844);
|
||||
;// CONCATENATED MODULE: ./src/tls-inspect.ts
|
||||
var tls_inspect_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
@@ -32101,28 +32101,25 @@ var tls_inspect_awaiter = (undefined && undefined.__awaiter) || function (thisAr
|
||||
};
|
||||
|
||||
|
||||
|
||||
function isTLSEnabled(owner) {
|
||||
return tls_inspect_awaiter(this, void 0, void 0, function* () {
|
||||
let tlsStatusEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
let httpClient = new HttpClient();
|
||||
httpClient.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
const tlsStatusEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
core.info(`[!] Checking TLS_STATUS: ${owner}`);
|
||||
let isEnabled = false;
|
||||
try {
|
||||
let resp = yield httpClient.get(tlsStatusEndpoint);
|
||||
if (resp.message.statusCode === 200) {
|
||||
isEnabled = true;
|
||||
const resp = yield fetch(tlsStatusEndpoint, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
core.info(`[!] TLS_ENABLED: ${owner}`);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
core.info(`[!] TLS_NOT_ENABLED: ${owner}`);
|
||||
}
|
||||
core.info(`[!] TLS_NOT_ENABLED: ${owner}`);
|
||||
return false;
|
||||
}
|
||||
catch (e) {
|
||||
core.info(`[!] Unable to check TLS_STATUS`);
|
||||
return false;
|
||||
}
|
||||
return isEnabled;
|
||||
});
|
||||
}
|
||||
function isGithubHosted() {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+27
-19
@@ -31874,10 +31874,10 @@ var __webpack_exports__ = {};
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
// EXTERNAL MODULE: external "fs"
|
||||
var external_fs_ = __nccwpck_require__(9896);
|
||||
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js
|
||||
var lib_core = __nccwpck_require__(7484);
|
||||
// EXTERNAL MODULE: external "fs"
|
||||
var external_fs_ = __nccwpck_require__(9896);
|
||||
;// CONCATENATED MODULE: ./src/configs.ts
|
||||
const STEPSECURITY_ENV = "agent"; // agent or int
|
||||
const configs_STEPSECURITY_API_URL = `https://${STEPSECURITY_ENV}.api.stepsecurity.io/v1`;
|
||||
@@ -31986,8 +31986,8 @@ const processLogLine = (line, tableEntries) => {
|
||||
}
|
||||
};
|
||||
function addSummary() {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
if (process.env.STATE_addSummary !== "true") {
|
||||
return;
|
||||
}
|
||||
@@ -32028,7 +32028,9 @@ function addSummary() {
|
||||
// Fetch job summary from API
|
||||
const apiUrl = `${configs_STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/runs/${run_id}/correlation/${correlation_id}/job-markdown-summary`;
|
||||
try {
|
||||
const response = yield fetch(apiUrl);
|
||||
const response = yield fetch(apiUrl, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch job summary: ${response.status} ${response.statusText}`);
|
||||
return;
|
||||
@@ -32135,8 +32137,6 @@ function echo(content) {
|
||||
cp.execFileSync("echo", [content]);
|
||||
}
|
||||
|
||||
// EXTERNAL MODULE: ./node_modules/@actions/http-client/lib/index.js
|
||||
var lib = __nccwpck_require__(4844);
|
||||
;// CONCATENATED MODULE: ./src/tls-inspect.ts
|
||||
var tls_inspect_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
@@ -32149,28 +32149,25 @@ var tls_inspect_awaiter = (undefined && undefined.__awaiter) || function (thisAr
|
||||
};
|
||||
|
||||
|
||||
|
||||
function isTLSEnabled(owner) {
|
||||
return tls_inspect_awaiter(this, void 0, void 0, function* () {
|
||||
let tlsStatusEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
let httpClient = new HttpClient();
|
||||
httpClient.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
const tlsStatusEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
core.info(`[!] Checking TLS_STATUS: ${owner}`);
|
||||
let isEnabled = false;
|
||||
try {
|
||||
let resp = yield httpClient.get(tlsStatusEndpoint);
|
||||
if (resp.message.statusCode === 200) {
|
||||
isEnabled = true;
|
||||
const resp = yield fetch(tlsStatusEndpoint, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
core.info(`[!] TLS_ENABLED: ${owner}`);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
core.info(`[!] TLS_NOT_ENABLED: ${owner}`);
|
||||
}
|
||||
core.info(`[!] TLS_NOT_ENABLED: ${owner}`);
|
||||
return false;
|
||||
}
|
||||
catch (e) {
|
||||
core.info(`[!] Unable to check TLS_STATUS`);
|
||||
return false;
|
||||
}
|
||||
return isEnabled;
|
||||
});
|
||||
}
|
||||
function isGithubHosted() {
|
||||
@@ -32199,6 +32196,11 @@ var cleanup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _
|
||||
|
||||
|
||||
|
||||
|
||||
// See setup.ts for rationale — Node 22+ kills the process on unhandled rejections.
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
lib_core.warning(`Unhandled promise rejection during Post-step: ${reason}`);
|
||||
});
|
||||
(() => cleanup_awaiter(void 0, void 0, void 0, function* () {
|
||||
var _a, _b;
|
||||
console.log("[harden-runner] post-step");
|
||||
@@ -32391,7 +32393,7 @@ function handleMacosCleanup() {
|
||||
console.log("\nSystem log stream for io.stepsecurity.harden-runner:");
|
||||
const logStreamOutput = external_child_process_.execSync("log show --predicate 'subsystem == \"io.stepsecurity.harden-runner\"' --info --last 10m", {
|
||||
encoding: "utf8",
|
||||
maxBuffer: 1024 * 1024 * 10,
|
||||
maxBuffer: 1024 * 1024 * 10, // 10MB buffer
|
||||
timeout: 5000, // 5 seconds timeout
|
||||
});
|
||||
console.log(logStreamOutput);
|
||||
@@ -32406,6 +32408,12 @@ function handleWindowsCleanup() {
|
||||
// windows cleanup
|
||||
const agentDir = process.env.STATE_agentDir || "C:\\agent";
|
||||
const postEventFile = external_path_.join(agentDir, "post_event.json");
|
||||
// If Pre-step crashed before installing the agent, agentDir doesn't exist;
|
||||
// bail out instead of throwing ENOENT on the writeFileSync below.
|
||||
if (!external_fs_.existsSync(agentDir)) {
|
||||
console.log(`Windows cleanup: ${agentDir} not found; agent was not installed (Pre-step likely failed). Skipping.`);
|
||||
return;
|
||||
}
|
||||
if (isGithubHosted() && external_fs_.existsSync(postEventFile)) {
|
||||
console.log("Windows post step already executed, skipping");
|
||||
return;
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+94
-68
@@ -84987,8 +84987,6 @@ var lib_core = __nccwpck_require__(7484);
|
||||
var external_child_process_ = __nccwpck_require__(5317);
|
||||
// EXTERNAL MODULE: external "fs"
|
||||
var external_fs_ = __nccwpck_require__(9896);
|
||||
// EXTERNAL MODULE: ./node_modules/@actions/http-client/lib/index.js
|
||||
var lib = __nccwpck_require__(4844);
|
||||
// EXTERNAL MODULE: external "path"
|
||||
var external_path_ = __nccwpck_require__(6928);
|
||||
// EXTERNAL MODULE: ./node_modules/uuid/dist/index.js
|
||||
@@ -85105,8 +85103,8 @@ const processLogLine = (line, tableEntries) => {
|
||||
}
|
||||
};
|
||||
function addSummary() {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
if (process.env.STATE_addSummary !== "true") {
|
||||
return;
|
||||
}
|
||||
@@ -85147,7 +85145,9 @@ function addSummary() {
|
||||
// Fetch job summary from API
|
||||
const apiUrl = `${STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/runs/${run_id}/correlation/${correlation_id}/job-markdown-summary`;
|
||||
try {
|
||||
const response = yield fetch(apiUrl);
|
||||
const response = yield fetch(apiUrl, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch job summary: ${response.status} ${response.statusText}`);
|
||||
return;
|
||||
@@ -85244,43 +85244,43 @@ var policy_utils_awaiter = (undefined && undefined.__awaiter) || function (thisA
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
class HttpStatusError extends Error {
|
||||
constructor(statusCode, message) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
function fetchPolicy(owner, policyName, idToken) {
|
||||
return policy_utils_awaiter(this, void 0, void 0, function* () {
|
||||
if (idToken === "") {
|
||||
throw new Error("[PolicyFetch]: id-token in empty");
|
||||
}
|
||||
let policyEndpoint = `${configs_STEPSECURITY_API_URL}/github/${owner}/actions/policies/${policyName}`;
|
||||
let httpClient = new lib.HttpClient();
|
||||
let headers = {};
|
||||
headers["Authorization"] = `Bearer ${idToken}`;
|
||||
headers["Source"] = "github-actions";
|
||||
let response = undefined;
|
||||
let err = undefined;
|
||||
let retry = 0;
|
||||
while (retry < 3) {
|
||||
const policyEndpoint = `${configs_STEPSECURITY_API_URL}/github/${owner}/actions/policies/${policyName}`;
|
||||
const headers = {
|
||||
Authorization: `Bearer ${idToken}`,
|
||||
Source: "github-actions",
|
||||
};
|
||||
let result;
|
||||
let err;
|
||||
for (let retry = 0; retry < 3; retry++) {
|
||||
try {
|
||||
console.log(`Attempt: ${retry + 1}`);
|
||||
response = yield httpClient.getJson(policyEndpoint, headers);
|
||||
result = yield getJsonWithTimeout(policyEndpoint, headers);
|
||||
break;
|
||||
}
|
||||
catch (e) {
|
||||
err = e;
|
||||
}
|
||||
retry += 1;
|
||||
yield sleep(1000);
|
||||
}
|
||||
if (response === undefined && err !== undefined) {
|
||||
// Preserve the original error's statusCode if it exists
|
||||
if (result === undefined) {
|
||||
const error = new Error(`[Policy Fetch] ${err}`);
|
||||
if (err.statusCode !== undefined) {
|
||||
if (err && typeof err === "object" && "statusCode" in err) {
|
||||
error.statusCode = err.statusCode;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
else {
|
||||
return response.result;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
function fetchPolicyFromStore(owner, repo, apiKey, workflow, runId, correlationId) {
|
||||
@@ -85288,43 +85288,54 @@ function fetchPolicyFromStore(owner, repo, apiKey, workflow, runId, correlationI
|
||||
if (apiKey === "") {
|
||||
throw new Error("[PolicyStoreFetch]: api-key is empty");
|
||||
}
|
||||
let policyEndpoint = `${configs_STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/policies/workflow-policy?workflow=${encodeURIComponent(workflow)}&run_id=${encodeURIComponent(runId)}&correlationId=${encodeURIComponent(correlationId)}`;
|
||||
let httpClient = new lib.HttpClient();
|
||||
let headers = {};
|
||||
headers["Authorization"] = `vm-api-key ${apiKey}`;
|
||||
headers["Source"] = "github-actions";
|
||||
let response = undefined;
|
||||
let err = undefined;
|
||||
let retry = 0;
|
||||
while (retry < 3) {
|
||||
const policyEndpoint = `${configs_STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/policies/workflow-policy?workflow=${encodeURIComponent(workflow)}&run_id=${encodeURIComponent(runId)}&correlationId=${encodeURIComponent(correlationId)}`;
|
||||
const headers = {
|
||||
Authorization: `vm-api-key ${apiKey}`,
|
||||
Source: "github-actions",
|
||||
};
|
||||
let result;
|
||||
let err;
|
||||
for (let retry = 0; retry < 3; retry++) {
|
||||
try {
|
||||
console.log(`Attempt: ${retry + 1}`);
|
||||
response = yield httpClient.getJson(policyEndpoint, headers);
|
||||
result = yield getJsonWithTimeout(policyEndpoint, headers);
|
||||
break;
|
||||
}
|
||||
catch (e) {
|
||||
// 404 means policy not found — don't retry, return null
|
||||
if (e instanceof HttpStatusError && e.statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
err = e;
|
||||
}
|
||||
retry += 1;
|
||||
yield sleep(1000);
|
||||
}
|
||||
if (response === undefined && err !== undefined) {
|
||||
if (result === undefined) {
|
||||
const error = new Error(`[Policy Store Fetch] ${err}`);
|
||||
if (err.statusCode !== undefined) {
|
||||
if (err && typeof err === "object" && "statusCode" in err) {
|
||||
error.statusCode = err.statusCode;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (response.statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
const result = response.result;
|
||||
if (!result || (!result.egress_policy && (!result.allowed_endpoints || result.allowed_endpoints.length === 0))) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
function getJsonWithTimeout(url, headers) {
|
||||
return policy_utils_awaiter(this, void 0, void 0, function* () {
|
||||
const resp = yield fetch(url, {
|
||||
method: "GET",
|
||||
headers,
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new HttpStatusError(resp.status, `HTTP ${resp.status}`);
|
||||
}
|
||||
return (yield resp.json());
|
||||
});
|
||||
}
|
||||
function mergeConfigs(localConfig, remoteConfig) {
|
||||
if (localConfig.allowed_endpoints === "") {
|
||||
localConfig.allowed_endpoints = remoteConfig.allowed_endpoints.join(" ");
|
||||
@@ -85417,28 +85428,25 @@ var tls_inspect_awaiter = (undefined && undefined.__awaiter) || function (thisAr
|
||||
};
|
||||
|
||||
|
||||
|
||||
function isTLSEnabled(owner) {
|
||||
return tls_inspect_awaiter(this, void 0, void 0, function* () {
|
||||
let tlsStatusEndpoint = `${configs_STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
let httpClient = new lib.HttpClient();
|
||||
httpClient.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
const tlsStatusEndpoint = `${configs_STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
lib_core.info(`[!] Checking TLS_STATUS: ${owner}`);
|
||||
let isEnabled = false;
|
||||
try {
|
||||
let resp = yield httpClient.get(tlsStatusEndpoint);
|
||||
if (resp.message.statusCode === 200) {
|
||||
isEnabled = true;
|
||||
const resp = yield fetch(tlsStatusEndpoint, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
lib_core.info(`[!] TLS_ENABLED: ${owner}`);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
lib_core.info(`[!] TLS_NOT_ENABLED: ${owner}`);
|
||||
}
|
||||
lib_core.info(`[!] TLS_NOT_ENABLED: ${owner}`);
|
||||
return false;
|
||||
}
|
||||
catch (e) {
|
||||
lib_core.info(`[!] Unable to check TLS_STATUS`);
|
||||
return false;
|
||||
}
|
||||
return isEnabled;
|
||||
});
|
||||
}
|
||||
function isGithubHosted() {
|
||||
@@ -85456,17 +85464,17 @@ var external_crypto_ = __nccwpck_require__(6982);
|
||||
|
||||
const CHECKSUMS = {
|
||||
tls: {
|
||||
amd64: "d58a9c1c5245155ce4c71507a61e213a29925a7c39c0d20bfd00bef0d281bdbb",
|
||||
amd64: "d58a9c1c5245155ce4c71507a61e213a29925a7c39c0d20bfd00bef0d281bdbb", // v1.8.6
|
||||
arm64: "084fa95e74d17321dd1c37c93abeb8577e53ddf5266410e19f52aa79a02ae33e",
|
||||
},
|
||||
non_tls: {
|
||||
amd64: "e38de61e1afd98dd339bb9acce4996183875d482be1638fb198ab02b3e25bbef", // v0.16.0
|
||||
},
|
||||
bravo: {
|
||||
amd64: "495f607a891d89f12214849301f247bdca565afe67deb170fe7e5d6d361852ca",
|
||||
amd64: "495f607a891d89f12214849301f247bdca565afe67deb170fe7e5d6d361852ca", // v1.8.6
|
||||
arm64: "f96f66ab946097aae1fc887e12fe1cefcc5d510bce179221c7185374e4adf538",
|
||||
},
|
||||
darwin: "fe26a1f6af4afe9f1a854d8633832f5d18ab542827003cae445b3a64021d612c",
|
||||
darwin: "fe26a1f6af4afe9f1a854d8633832f5d18ab542827003cae445b3a64021d612c", // v0.0.5
|
||||
windows: {
|
||||
amd64: "93f1e5d87c6647e6eca7963d5f4b4bd73107029430f8e6945ffece93007a89f5", // v1.0.2
|
||||
},
|
||||
@@ -85795,7 +85803,14 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
|
||||
|
||||
|
||||
|
||||
|
||||
// Node 22+ terminates the process on unhandled promise rejections by default.
|
||||
// Third-party libraries used during Pre-step (notably @actions/cache's tar +
|
||||
// upload streams under concurrent matrix runs) can emit background rejections
|
||||
// that escape our try/catch, killing Pre-step silently and leaving the runner
|
||||
// without an agent installed. Log and continue instead.
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
lib_core.warning(`Unhandled promise rejection during Pre-step: ${reason}`);
|
||||
});
|
||||
(() => setup_awaiter(void 0, void 0, void 0, function* () {
|
||||
var _a, _b, _c, _d;
|
||||
try {
|
||||
@@ -86049,18 +86064,24 @@ var __rest = (undefined && undefined.__rest) || function (s, e) {
|
||||
console.log("Agent already installed, skipping installation");
|
||||
return;
|
||||
}
|
||||
let _http = new lib.HttpClient();
|
||||
let statusCode;
|
||||
_http.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
let addSummary = "false";
|
||||
try {
|
||||
const monitorRequestData = {
|
||||
correlation_id: correlation_id,
|
||||
job: process.env["GITHUB_JOB"],
|
||||
};
|
||||
const resp = yield _http.postJson(`${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`, monitorRequestData);
|
||||
const responseData = resp.result;
|
||||
statusCode = resp.statusCode; // adding error code to check whether agent is getting installed or not.
|
||||
const url = `${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`;
|
||||
const resp = yield fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(monitorRequestData),
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
statusCode = resp.status;
|
||||
const responseData = resp.ok
|
||||
? (yield resp.json())
|
||||
: undefined;
|
||||
external_fs_.appendFileSync(process.env.GITHUB_STATE, `monitorStatusCode=${statusCode}${external_os_.EOL}`, {
|
||||
encoding: "utf8",
|
||||
});
|
||||
@@ -86152,8 +86173,6 @@ function setup_sleep(ms) {
|
||||
}
|
||||
function callMonitorEndpoint(api_url, confg) {
|
||||
return setup_awaiter(this, void 0, void 0, function* () {
|
||||
const _http = new lib.HttpClient();
|
||||
_http.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
let statusCode;
|
||||
let addSummary = "false";
|
||||
try {
|
||||
@@ -86161,12 +86180,19 @@ function callMonitorEndpoint(api_url, confg) {
|
||||
correlation_id: confg.correlation_id,
|
||||
job: process.env["GITHUB_JOB"],
|
||||
};
|
||||
const resp = yield _http.postJson(`${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`, monitorRequestData);
|
||||
statusCode = resp.statusCode;
|
||||
if (resp.statusCode === 200 && resp.result) {
|
||||
console.log(`Runner IP Address: ${resp.result.runner_ip_address}`);
|
||||
confg.one_time_key = resp.result.one_time_key;
|
||||
addSummary = resp.result.monitoring_started ? "true" : "false";
|
||||
const url = `${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`;
|
||||
const resp = yield fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(monitorRequestData),
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
statusCode = resp.status;
|
||||
if (resp.ok) {
|
||||
const result = (yield resp.json());
|
||||
console.log(`Runner IP Address: ${result.runner_ip_address}`);
|
||||
confg.one_time_key = result.one_time_key;
|
||||
addSummary = result.monitoring_started ? "true" : "false";
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Generated
+202
-58
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.9.0",
|
||||
"@types/node": "^24.13.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
@@ -32,9 +32,9 @@
|
||||
"jest": "^29.3.1",
|
||||
"jest-junit": ">=13.0.0",
|
||||
"nock": "^13.3.0",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-jest": "^29.4.11",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.3.5"
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/cache": {
|
||||
@@ -2328,10 +2328,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.11.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz",
|
||||
"integrity": "sha512-C1pD3kgLoZ56Uuy5lhfOxie4aZlA3UMGLX9rXteq4WitEZH6Rl80mwactt9QG0w0gLFlN/kLBTFnGXtDVWvWQw==",
|
||||
"dev": true
|
||||
"version": "24.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz",
|
||||
"integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prettier": {
|
||||
"version": "2.7.1",
|
||||
@@ -4141,6 +4145,28 @@
|
||||
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.9",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
|
||||
"integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"wordwrap": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
@@ -6227,6 +6253,16 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
@@ -6256,6 +6292,13 @@
|
||||
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nock": {
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-13.3.0.tgz",
|
||||
@@ -7113,37 +7156,44 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest": {
|
||||
"version": "29.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz",
|
||||
"integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==",
|
||||
"version": "29.4.11",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz",
|
||||
"integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bs-logger": "0.x",
|
||||
"fast-json-stable-stringify": "2.x",
|
||||
"jest-util": "^29.0.0",
|
||||
"json5": "^2.2.1",
|
||||
"lodash.memoize": "4.x",
|
||||
"make-error": "1.x",
|
||||
"semver": "7.x",
|
||||
"yargs-parser": "^21.0.1"
|
||||
"bs-logger": "^0.2.6",
|
||||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"handlebars": "^4.7.9",
|
||||
"json5": "^2.2.3",
|
||||
"lodash.memoize": "^4.1.2",
|
||||
"make-error": "^1.3.6",
|
||||
"semver": "^7.8.0",
|
||||
"type-fest": "^4.41.0",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-jest": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
"node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": ">=7.0.0-beta.0 <8",
|
||||
"@jest/types": "^29.0.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
"jest": "^29.0.0",
|
||||
"typescript": ">=4.3"
|
||||
"@jest/transform": "^29.0.0 || ^30.0.0",
|
||||
"@jest/types": "^29.0.0 || ^30.0.0",
|
||||
"babel-jest": "^29.0.0 || ^30.0.0",
|
||||
"jest": "^29.0.0 || ^30.0.0",
|
||||
"jest-util": "^29.0.0 || ^30.0.0",
|
||||
"typescript": ">=4.3 <7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@jest/transform": {
|
||||
"optional": true
|
||||
},
|
||||
"@jest/types": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -7152,17 +7202,18 @@
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
},
|
||||
"jest-util": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest/node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
|
||||
"integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
@@ -7170,6 +7221,19 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest/node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||
@@ -7272,16 +7336,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
|
||||
"integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"uglifyjs": "bin/uglifyjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
@@ -7295,6 +7374,13 @@
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
@@ -7384,6 +7470,13 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
@@ -9369,10 +9462,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.11.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz",
|
||||
"integrity": "sha512-C1pD3kgLoZ56Uuy5lhfOxie4aZlA3UMGLX9rXteq4WitEZH6Rl80mwactt9QG0w0gLFlN/kLBTFnGXtDVWvWQw==",
|
||||
"dev": true
|
||||
"version": "24.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz",
|
||||
"integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"@types/prettier": {
|
||||
"version": "2.7.1",
|
||||
@@ -10646,6 +10742,19 @@
|
||||
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
|
||||
"dev": true
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.7.9",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
|
||||
"integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"uglify-js": "^3.1.4",
|
||||
"wordwrap": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
@@ -12225,6 +12334,12 @@
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
@@ -12248,6 +12363,12 @@
|
||||
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
|
||||
"dev": true
|
||||
},
|
||||
"neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"nock": {
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-13.3.0.tgz",
|
||||
@@ -12840,29 +12961,33 @@
|
||||
"requires": {}
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "29.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz",
|
||||
"integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==",
|
||||
"version": "29.4.11",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz",
|
||||
"integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bs-logger": "0.x",
|
||||
"fast-json-stable-stringify": "2.x",
|
||||
"jest-util": "^29.0.0",
|
||||
"json5": "^2.2.1",
|
||||
"lodash.memoize": "4.x",
|
||||
"make-error": "1.x",
|
||||
"semver": "7.x",
|
||||
"yargs-parser": "^21.0.1"
|
||||
"bs-logger": "^0.2.6",
|
||||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"handlebars": "^4.7.9",
|
||||
"json5": "^2.2.3",
|
||||
"lodash.memoize": "^4.1.2",
|
||||
"make-error": "^1.3.6",
|
||||
"semver": "^7.8.0",
|
||||
"type-fest": "^4.41.0",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
|
||||
"integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12927,11 +13052,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
|
||||
"integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"undici": {
|
||||
"version": "5.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||
@@ -12940,6 +13072,12 @@
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true
|
||||
},
|
||||
"universal-user-agent": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||
@@ -13011,6 +13149,12 @@
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"dev": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
||||
+3
-3
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.9.0",
|
||||
"@types/node": "^24.13.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
@@ -46,8 +46,8 @@
|
||||
"jest": "^29.3.1",
|
||||
"jest-junit": ">=13.0.0",
|
||||
"nock": "^13.3.0",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-jest": "^29.4.11",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.3.5"
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as core from "@actions/core";
|
||||
import * as fs from "fs";
|
||||
import * as common from "./common";
|
||||
import * as cp from "child_process";
|
||||
@@ -8,6 +9,11 @@ import { isGithubHosted } from "./tls-inspect";
|
||||
import { context } from "@actions/github";
|
||||
import { isPlatformSupported, isAgentInstalled, detectThirdPartyRunnerProvider } from "./utils";
|
||||
|
||||
// See setup.ts for rationale — Node 22+ kills the process on unhandled rejections.
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
core.warning(`Unhandled promise rejection during Post-step: ${reason}`);
|
||||
});
|
||||
|
||||
(async () => {
|
||||
console.log("[harden-runner] post-step");
|
||||
|
||||
@@ -242,6 +248,15 @@ async function handleWindowsCleanup() {
|
||||
const agentDir = process.env.STATE_agentDir || "C:\\agent";
|
||||
const postEventFile = path.join(agentDir, "post_event.json");
|
||||
|
||||
// If Pre-step crashed before installing the agent, agentDir doesn't exist;
|
||||
// bail out instead of throwing ENOENT on the writeFileSync below.
|
||||
if (!fs.existsSync(agentDir)) {
|
||||
console.log(
|
||||
`Windows cleanup: ${agentDir} not found; agent was not installed (Pre-step likely failed). Skipping.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isGithubHosted() && fs.existsSync(postEventFile)) {
|
||||
console.log("Windows post step already executed, skipping");
|
||||
return;
|
||||
|
||||
+3
-1
@@ -109,7 +109,9 @@ export async function addSummary() {
|
||||
const apiUrl = `${STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/runs/${run_id}/correlation/${correlation_id}/job-markdown-summary`;
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrl);
|
||||
const response = await fetch(apiUrl, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch job summary: ${response.status} ${response.statusText}`);
|
||||
return;
|
||||
|
||||
+231
-231
@@ -1,12 +1,46 @@
|
||||
import nock from "nock";
|
||||
import { fetchPolicy, mergeConfigs } from "./policy-utils";
|
||||
import { fetchPolicy, fetchPolicyFromStore, mergeConfigs } from "./policy-utils";
|
||||
import { Configuration, PolicyResponse } from "./interfaces";
|
||||
import { STEPSECURITY_API_URL } from "./configs";
|
||||
|
||||
const ORIGINAL_FETCH = globalThis.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = ORIGINAL_FETCH;
|
||||
});
|
||||
|
||||
type FetchImpl = (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit
|
||||
) => Promise<Response>;
|
||||
|
||||
function mockFetch(impl: FetchImpl) {
|
||||
globalThis.fetch = impl as typeof fetch;
|
||||
}
|
||||
|
||||
function jsonResponse(status: number, body: unknown): Response {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// Mock fetch with a sequence of responses or errors. Each call consumes one entry.
|
||||
function mockFetchSequence(entries: Array<Response | Error>) {
|
||||
let i = 0;
|
||||
mockFetch(async () => {
|
||||
const entry = entries[i++];
|
||||
if (!entry) throw new Error("fetch called more times than expected");
|
||||
if (entry instanceof Error) throw entry;
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== fetchPolicy ====================
|
||||
|
||||
test("success: fetching policy", async () => {
|
||||
let owner = "h0x0er";
|
||||
let policyName = "policy1";
|
||||
let response = {
|
||||
const owner = "h0x0er";
|
||||
const policyName = "policy1";
|
||||
const response = {
|
||||
owner: "h0x0er",
|
||||
policyName: "policy1",
|
||||
allowed_endpoints: ["github.com:443"],
|
||||
@@ -15,16 +49,196 @@ test("success: fetching policy", async () => {
|
||||
disable_sudo: false,
|
||||
disable_file_monitoring: false,
|
||||
};
|
||||
const policyScope = nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/actions/policies/${policyName}`)
|
||||
.reply(200, response);
|
||||
|
||||
let idToken = "xyz";
|
||||
let policy = await fetchPolicy(owner, policyName, idToken);
|
||||
console.log(policy);
|
||||
expect(policy).toStrictEqual(response);
|
||||
const expectedUrl = `${STEPSECURITY_API_URL}/github/${owner}/actions/policies/${policyName}`;
|
||||
mockFetch(async (url, init) => {
|
||||
expect(String(url)).toBe(expectedUrl);
|
||||
expect((init?.headers as Record<string, string>)["Authorization"]).toBe("Bearer xyz");
|
||||
expect((init?.headers as Record<string, string>)["Source"]).toBe("github-actions");
|
||||
return jsonResponse(200, response);
|
||||
});
|
||||
|
||||
const policy = await fetchPolicy(owner, policyName, "xyz");
|
||||
expect(policy).toEqual(response);
|
||||
});
|
||||
|
||||
test("fetchPolicy throws when idToken is empty", async () => {
|
||||
await expect(fetchPolicy("owner", "policy1", "")).rejects.toThrow(
|
||||
"[PolicyFetch]: id-token in empty"
|
||||
);
|
||||
});
|
||||
|
||||
test("fetchPolicy retries on failure and succeeds", async () => {
|
||||
const response = {
|
||||
allowed_endpoints: ["example.com:443"],
|
||||
egress_policy: "block",
|
||||
};
|
||||
|
||||
mockFetchSequence([
|
||||
new TypeError("fetch failed"),
|
||||
jsonResponse(200, response),
|
||||
]);
|
||||
|
||||
const policy = await fetchPolicy("test-owner", "test-policy", "token123");
|
||||
expect(policy).toEqual(response);
|
||||
});
|
||||
|
||||
test("fetchPolicy throws after all retries exhausted", async () => {
|
||||
mockFetchSequence([
|
||||
new TypeError("fetch failed"),
|
||||
new TypeError("fetch failed"),
|
||||
new TypeError("fetch failed"),
|
||||
]);
|
||||
|
||||
await expect(
|
||||
fetchPolicy("test-owner", "test-policy", "token123")
|
||||
).rejects.toThrow("[Policy Fetch]");
|
||||
});
|
||||
|
||||
test("fetchPolicy preserves statusCode from error", async () => {
|
||||
// server returns 404 on every retry; the HttpStatusError raised internally
|
||||
// carries statusCode=404 which the outer error should expose.
|
||||
mockFetchSequence([
|
||||
jsonResponse(404, { message: "not found" }),
|
||||
jsonResponse(404, { message: "not found" }),
|
||||
jsonResponse(404, { message: "not found" }),
|
||||
]);
|
||||
|
||||
try {
|
||||
await fetchPolicy("test-owner", "test-policy", "token123");
|
||||
fail("should have thrown");
|
||||
} catch (err: any) {
|
||||
expect(err.message).toContain("[Policy Fetch]");
|
||||
expect(err.statusCode).toBe(404);
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== fetchPolicyFromStore ====================
|
||||
|
||||
test("success: fetches policy from store", async () => {
|
||||
const owner = "test-owner";
|
||||
const repo = "test-repo";
|
||||
const workflow = "ci.yml";
|
||||
const runId = "12345";
|
||||
const correlationId = "abc-def";
|
||||
const response = {
|
||||
allowed_endpoints: ["registry.npmjs.org:443", "github.com:443"],
|
||||
egress_policy: "block",
|
||||
disable_sudo: true,
|
||||
disable_file_monitoring: false,
|
||||
};
|
||||
|
||||
mockFetch(async () => jsonResponse(200, response));
|
||||
|
||||
const result = await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore throws when apiKey is empty", async () => {
|
||||
await expect(
|
||||
fetchPolicyFromStore("owner", "repo", "", "ci.yml", "123", "abc")
|
||||
).rejects.toThrow("[PolicyStoreFetch]: api-key is empty");
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore returns null when policy not found (404)", async () => {
|
||||
mockFetch(async () => jsonResponse(404, { message: "not found" }));
|
||||
|
||||
const result = await fetchPolicyFromStore("test-owner", "test-repo", "my-api-key", "ci.yml", "12345", "abc-def");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore returns null when API returns empty policy", async () => {
|
||||
mockFetch(async () => jsonResponse(200, { allowed_endpoints: [], egress_policy: "", policy_name: "" }));
|
||||
|
||||
const result = await fetchPolicyFromStore("test-owner", "nonexistent-repo", "my-api-key", "ci.yml", "12345", "abc-def");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore retries on failure and succeeds", async () => {
|
||||
const response = {
|
||||
allowed_endpoints: ["example.com:443"],
|
||||
egress_policy: "audit",
|
||||
};
|
||||
|
||||
mockFetchSequence([
|
||||
new TypeError("fetch failed"),
|
||||
jsonResponse(200, response),
|
||||
]);
|
||||
|
||||
const result = await fetchPolicyFromStore("test-owner", "test-repo", "my-api-key", "ci.yml", "12345", "abc-def");
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore throws after all retries exhausted", async () => {
|
||||
mockFetchSequence([
|
||||
new TypeError("fetch failed"),
|
||||
new TypeError("fetch failed"),
|
||||
new TypeError("fetch failed"),
|
||||
]);
|
||||
|
||||
await expect(
|
||||
fetchPolicyFromStore("test-owner", "test-repo", "my-api-key", "ci.yml", "12345", "abc-def")
|
||||
).rejects.toThrow("[Policy Store Fetch]");
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore preserves statusCode from error", async () => {
|
||||
mockFetchSequence([
|
||||
jsonResponse(401, { message: "Unauthorized" }),
|
||||
jsonResponse(401, { message: "Unauthorized" }),
|
||||
jsonResponse(401, { message: "Unauthorized" }),
|
||||
]);
|
||||
|
||||
try {
|
||||
await fetchPolicyFromStore("test-owner", "test-repo", "my-api-key", "ci.yml", "12345", "abc-def");
|
||||
fail("should have thrown");
|
||||
} catch (err: any) {
|
||||
expect(err.message).toContain("[Policy Store Fetch]");
|
||||
expect(err.statusCode).toBe(401);
|
||||
}
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore sends correct authorization header", async () => {
|
||||
const apiKey = "secret-key-123";
|
||||
|
||||
mockFetch(async (_url, init) => {
|
||||
const headers = init?.headers as Record<string, string>;
|
||||
expect(headers["Authorization"]).toBe(`vm-api-key ${apiKey}`);
|
||||
expect(headers["Source"]).toBe("github-actions");
|
||||
return jsonResponse(200, { allowed_endpoints: [], egress_policy: "audit" });
|
||||
});
|
||||
|
||||
const result = await fetchPolicyFromStore("test-owner", "test-repo", apiKey, "ci.yml", "12345", "abc-def");
|
||||
expect(result).toEqual({ allowed_endpoints: [], egress_policy: "audit" });
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore returns within ~3s when server is slow (regression test for AggregateError)", async () => {
|
||||
mockFetch((_url, init) => {
|
||||
return new Promise<Response>((_resolve, reject) => {
|
||||
const signal = init?.signal;
|
||||
if (signal) {
|
||||
if (signal.aborted) {
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
return;
|
||||
}
|
||||
signal.addEventListener("abort", () =>
|
||||
reject(new DOMException("Aborted", "AbortError"))
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const start = Date.now();
|
||||
await expect(
|
||||
fetchPolicyFromStore("test-owner", "test-repo", "my-api-key", "ci.yml", "12345", "abc-def")
|
||||
).rejects.toThrow("[Policy Store Fetch]");
|
||||
const elapsed = Date.now() - start;
|
||||
// 3 retries × (3s timeout + 1s sleep), but last sleep is unnecessary.
|
||||
// Bounded by 3 * 3s + 2 * 1s = 11s. Test passes if it doesn't hang for minutes.
|
||||
expect(elapsed).toBeLessThan(13_000);
|
||||
}, 20_000);
|
||||
|
||||
// ==================== mergeConfigs ====================
|
||||
|
||||
test("merge configs", async () => {
|
||||
let localConfig: Configuration = {
|
||||
repo: "test/repo",
|
||||
@@ -47,7 +261,7 @@ test("merge configs", async () => {
|
||||
use_policy_store: false,
|
||||
deploy_on_self_hosted_vm: false,
|
||||
};
|
||||
let policyResponse: PolicyResponse = {
|
||||
const policyResponse: PolicyResponse = {
|
||||
owner: "h0x0er",
|
||||
policyName: "policy1",
|
||||
allowed_endpoints: ["github.com:443", "google.com:443"],
|
||||
@@ -56,8 +270,7 @@ test("merge configs", async () => {
|
||||
disable_sudo: false,
|
||||
disable_file_monitoring: false,
|
||||
};
|
||||
|
||||
let expectedConfiguration: Configuration = {
|
||||
const expectedConfiguration: Configuration = {
|
||||
repo: "test/repo",
|
||||
run_id: "xyx",
|
||||
correlation_id: "aaaaa",
|
||||
@@ -83,219 +296,6 @@ test("merge configs", async () => {
|
||||
expect(localConfig).toStrictEqual(expectedConfiguration);
|
||||
});
|
||||
|
||||
// ==================== additional fetchPolicy tests ====================
|
||||
|
||||
test("fetchPolicy throws when idToken is empty", async () => {
|
||||
await expect(fetchPolicy("owner", "policy1", "")).rejects.toThrow(
|
||||
"[PolicyFetch]: id-token in empty"
|
||||
);
|
||||
});
|
||||
|
||||
test("fetchPolicy retries on failure and succeeds", async () => {
|
||||
const owner = "test-owner";
|
||||
const policyName = "test-policy";
|
||||
const response = {
|
||||
allowed_endpoints: ["example.com:443"],
|
||||
egress_policy: "block",
|
||||
};
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/actions/policies/${policyName}`)
|
||||
.replyWithError("connection timeout");
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/actions/policies/${policyName}`)
|
||||
.reply(200, response);
|
||||
|
||||
const policy = await fetchPolicy(owner, policyName, "token123");
|
||||
expect(policy).toStrictEqual(response);
|
||||
});
|
||||
|
||||
test("fetchPolicy throws after all retries exhausted", async () => {
|
||||
const owner = "test-owner";
|
||||
const policyName = "test-policy";
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/actions/policies/${policyName}`)
|
||||
.times(3)
|
||||
.replyWithError("connection timeout");
|
||||
|
||||
await expect(
|
||||
fetchPolicy(owner, policyName, "token123")
|
||||
).rejects.toThrow("[Policy Fetch]");
|
||||
});
|
||||
|
||||
test("fetchPolicy preserves statusCode from error", async () => {
|
||||
const owner = "test-owner";
|
||||
const policyName = "test-policy";
|
||||
|
||||
const errorWithStatus = new Error("Not Found");
|
||||
(errorWithStatus as any).statusCode = 404;
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/actions/policies/${policyName}`)
|
||||
.times(3)
|
||||
.replyWithError(errorWithStatus);
|
||||
|
||||
try {
|
||||
await fetchPolicy(owner, policyName, "token123");
|
||||
fail("should have thrown");
|
||||
} catch (err) {
|
||||
expect(err.message).toContain("[Policy Fetch]");
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== fetchPolicyFromStore ====================
|
||||
|
||||
import { fetchPolicyFromStore } from "./policy-utils";
|
||||
|
||||
const policyStoreQueryString = (workflow: string, runId: string, correlationId: string) =>
|
||||
`workflow=${encodeURIComponent(workflow)}&run_id=${encodeURIComponent(runId)}&correlationId=${encodeURIComponent(correlationId)}`;
|
||||
|
||||
test("success: fetches policy from store", async () => {
|
||||
const owner = "test-owner";
|
||||
const repo = "test-repo";
|
||||
const workflow = "ci.yml";
|
||||
const runId = "12345";
|
||||
const correlationId = "abc-def";
|
||||
const response = {
|
||||
allowed_endpoints: ["registry.npmjs.org:443", "github.com:443"],
|
||||
egress_policy: "block",
|
||||
disable_sudo: true,
|
||||
disable_file_monitoring: false,
|
||||
};
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
|
||||
.reply(200, response);
|
||||
|
||||
const result = await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
|
||||
expect(result).toStrictEqual(response);
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore throws when apiKey is empty", async () => {
|
||||
await expect(
|
||||
fetchPolicyFromStore("owner", "repo", "", "ci.yml", "123", "abc")
|
||||
).rejects.toThrow("[PolicyStoreFetch]: api-key is empty");
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore returns null when policy not found (404)", async () => {
|
||||
const owner = "test-owner";
|
||||
const repo = "test-repo";
|
||||
const workflow = "ci.yml";
|
||||
const runId = "12345";
|
||||
const correlationId = "abc-def";
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
|
||||
.reply(404, { message: "not found" });
|
||||
|
||||
const result = await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore returns null when API returns empty policy", async () => {
|
||||
const owner = "test-owner";
|
||||
const repo = "nonexistent-repo";
|
||||
const workflow = "ci.yml";
|
||||
const runId = "12345";
|
||||
const correlationId = "abc-def";
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
|
||||
.reply(200, { allowed_endpoints: [], egress_policy: "", policy_name: "" });
|
||||
|
||||
const result = await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore retries on failure and succeeds", async () => {
|
||||
const owner = "test-owner";
|
||||
const repo = "test-repo";
|
||||
const workflow = "ci.yml";
|
||||
const runId = "12345";
|
||||
const correlationId = "abc-def";
|
||||
const response = {
|
||||
allowed_endpoints: ["example.com:443"],
|
||||
egress_policy: "audit",
|
||||
};
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
|
||||
.replyWithError("timeout");
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
|
||||
.reply(200, response);
|
||||
|
||||
const result = await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
|
||||
expect(result).toStrictEqual(response);
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore throws after all retries exhausted", async () => {
|
||||
const owner = "test-owner";
|
||||
const repo = "test-repo";
|
||||
const workflow = "ci.yml";
|
||||
const runId = "12345";
|
||||
const correlationId = "abc-def";
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
|
||||
.times(3)
|
||||
.replyWithError("connection refused");
|
||||
|
||||
await expect(
|
||||
fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId)
|
||||
).rejects.toThrow("[Policy Store Fetch]");
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore preserves statusCode from error", async () => {
|
||||
const owner = "test-owner";
|
||||
const repo = "test-repo";
|
||||
const workflow = "ci.yml";
|
||||
const runId = "12345";
|
||||
const correlationId = "abc-def";
|
||||
|
||||
const errorWithStatus = new Error("Unauthorized");
|
||||
(errorWithStatus as any).statusCode = 401;
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
|
||||
.times(3)
|
||||
.replyWithError(errorWithStatus);
|
||||
|
||||
try {
|
||||
await fetchPolicyFromStore(owner, repo, "my-api-key", workflow, runId, correlationId);
|
||||
fail("should have thrown");
|
||||
} catch (err) {
|
||||
expect(err.message).toContain("[Policy Store Fetch]");
|
||||
}
|
||||
});
|
||||
|
||||
test("fetchPolicyFromStore sends correct authorization header", async () => {
|
||||
const owner = "test-owner";
|
||||
const repo = "test-repo";
|
||||
const apiKey = "secret-key-123";
|
||||
const workflow = "ci.yml";
|
||||
const runId = "12345";
|
||||
const correlationId = "abc-def";
|
||||
|
||||
nock(`${STEPSECURITY_API_URL}`, {
|
||||
reqheaders: {
|
||||
Authorization: `vm-api-key ${apiKey}`,
|
||||
Source: "github-actions",
|
||||
},
|
||||
})
|
||||
.get(`/github/${owner}/${repo}/actions/policies/workflow-policy?${policyStoreQueryString(workflow, runId, correlationId)}`)
|
||||
.reply(200, { allowed_endpoints: [], egress_policy: "audit" });
|
||||
|
||||
const result = await fetchPolicyFromStore(owner, repo, apiKey, workflow, runId, correlationId);
|
||||
expect(result).toStrictEqual({
|
||||
allowed_endpoints: [],
|
||||
egress_policy: "audit",
|
||||
});
|
||||
});
|
||||
|
||||
// ==================== additional mergeConfigs tests ====================
|
||||
|
||||
test("mergeConfigs does not override local allowed_endpoints if not empty", () => {
|
||||
let localConfig: Configuration = {
|
||||
repo: "test/repo",
|
||||
@@ -318,7 +318,7 @@ test("mergeConfigs does not override local allowed_endpoints if not empty", () =
|
||||
use_policy_store: false,
|
||||
deploy_on_self_hosted_vm: false,
|
||||
};
|
||||
let policyResponse: PolicyResponse = {
|
||||
const policyResponse: PolicyResponse = {
|
||||
allowed_endpoints: ["remote.endpoint:443"],
|
||||
egress_policy: "block",
|
||||
};
|
||||
@@ -350,7 +350,7 @@ test("mergeConfigs overrides disable_sudo_and_containers from remote", () => {
|
||||
use_policy_store: false,
|
||||
deploy_on_self_hosted_vm: false,
|
||||
};
|
||||
let policyResponse: PolicyResponse = {
|
||||
const policyResponse: PolicyResponse = {
|
||||
allowed_endpoints: [],
|
||||
disable_sudo_and_containers: true,
|
||||
};
|
||||
@@ -381,7 +381,7 @@ test("mergeConfigs does not override fields when remote values are undefined", (
|
||||
use_policy_store: false,
|
||||
deploy_on_self_hosted_vm: false,
|
||||
};
|
||||
let policyResponse: PolicyResponse = {
|
||||
const policyResponse: PolicyResponse = {
|
||||
allowed_endpoints: [],
|
||||
};
|
||||
|
||||
|
||||
+52
-45
@@ -1,7 +1,14 @@
|
||||
import { HttpClient } from "@actions/http-client";
|
||||
import { PolicyResponse, Configuration } from "./interfaces";
|
||||
import { STEPSECURITY_API_URL } from "./configs";
|
||||
|
||||
class HttpStatusError extends Error {
|
||||
statusCode: number;
|
||||
constructor(statusCode: number, message: string) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPolicy(
|
||||
owner: string,
|
||||
policyName: string,
|
||||
@@ -11,43 +18,35 @@ export async function fetchPolicy(
|
||||
throw new Error("[PolicyFetch]: id-token in empty");
|
||||
}
|
||||
|
||||
let policyEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/actions/policies/${policyName}`;
|
||||
const policyEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/actions/policies/${policyName}`;
|
||||
|
||||
let httpClient = new HttpClient();
|
||||
const headers = {
|
||||
Authorization: `Bearer ${idToken}`,
|
||||
Source: "github-actions",
|
||||
};
|
||||
|
||||
let headers = {};
|
||||
headers["Authorization"] = `Bearer ${idToken}`;
|
||||
headers["Source"] = "github-actions";
|
||||
let result: PolicyResponse | undefined;
|
||||
let err: unknown;
|
||||
|
||||
let response = undefined;
|
||||
let err = undefined;
|
||||
|
||||
let retry = 0;
|
||||
while (retry < 3) {
|
||||
for (let retry = 0; retry < 3; retry++) {
|
||||
try {
|
||||
console.log(`Attempt: ${retry + 1}`);
|
||||
response = await httpClient.getJson<PolicyResponse>(
|
||||
policyEndpoint,
|
||||
headers
|
||||
);
|
||||
result = await getJsonWithTimeout<PolicyResponse>(policyEndpoint, headers);
|
||||
break;
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
retry += 1;
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
if (response === undefined && err !== undefined) {
|
||||
// Preserve the original error's statusCode if it exists
|
||||
if (result === undefined) {
|
||||
const error = new Error(`[Policy Fetch] ${err}`);
|
||||
if (err.statusCode !== undefined) {
|
||||
(error as any).statusCode = err.statusCode;
|
||||
if (err && typeof err === "object" && "statusCode" in err) {
|
||||
(error as any).statusCode = (err as { statusCode: unknown }).statusCode;
|
||||
}
|
||||
throw error;
|
||||
} else {
|
||||
return response.result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function fetchPolicyFromStore(
|
||||
@@ -62,46 +61,39 @@ export async function fetchPolicyFromStore(
|
||||
throw new Error("[PolicyStoreFetch]: api-key is empty");
|
||||
}
|
||||
|
||||
let policyEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/policies/workflow-policy?workflow=${encodeURIComponent(workflow)}&run_id=${encodeURIComponent(runId)}&correlationId=${encodeURIComponent(correlationId)}`;
|
||||
const policyEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/${repo}/actions/policies/workflow-policy?workflow=${encodeURIComponent(workflow)}&run_id=${encodeURIComponent(runId)}&correlationId=${encodeURIComponent(correlationId)}`;
|
||||
|
||||
let httpClient = new HttpClient();
|
||||
const headers = {
|
||||
Authorization: `vm-api-key ${apiKey}`,
|
||||
Source: "github-actions",
|
||||
};
|
||||
|
||||
let headers = {};
|
||||
headers["Authorization"] = `vm-api-key ${apiKey}`;
|
||||
headers["Source"] = "github-actions";
|
||||
let result: PolicyResponse | undefined;
|
||||
let err: unknown;
|
||||
|
||||
let response = undefined;
|
||||
let err = undefined;
|
||||
|
||||
let retry = 0;
|
||||
while (retry < 3) {
|
||||
for (let retry = 0; retry < 3; retry++) {
|
||||
try {
|
||||
console.log(`Attempt: ${retry + 1}`);
|
||||
response = await httpClient.getJson<PolicyResponse>(
|
||||
policyEndpoint,
|
||||
headers
|
||||
);
|
||||
result = await getJsonWithTimeout<PolicyResponse>(policyEndpoint, headers);
|
||||
break;
|
||||
} catch (e) {
|
||||
// 404 means policy not found — don't retry, return null
|
||||
if (e instanceof HttpStatusError && e.statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
err = e;
|
||||
}
|
||||
retry += 1;
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
if (response === undefined && err !== undefined) {
|
||||
if (result === undefined) {
|
||||
const error = new Error(`[Policy Store Fetch] ${err}`);
|
||||
if (err.statusCode !== undefined) {
|
||||
(error as any).statusCode = err.statusCode;
|
||||
if (err && typeof err === "object" && "statusCode" in err) {
|
||||
(error as any).statusCode = (err as { statusCode: unknown }).statusCode;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (response.statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = response.result;
|
||||
if (!result || (!result.egress_policy && (!result.allowed_endpoints || result.allowed_endpoints.length === 0))) {
|
||||
return null;
|
||||
}
|
||||
@@ -109,6 +101,21 @@ export async function fetchPolicyFromStore(
|
||||
return result;
|
||||
}
|
||||
|
||||
async function getJsonWithTimeout<T>(
|
||||
url: string,
|
||||
headers: Record<string, string>
|
||||
): Promise<T> {
|
||||
const resp = await fetch(url, {
|
||||
method: "GET",
|
||||
headers,
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new HttpStatusError(resp.status, `HTTP ${resp.status}`);
|
||||
}
|
||||
return (await resp.json()) as T;
|
||||
}
|
||||
|
||||
export function mergeConfigs(
|
||||
localConfig: Configuration,
|
||||
remoteConfig: PolicyResponse
|
||||
|
||||
+33
-20
@@ -1,7 +1,6 @@
|
||||
import * as core from "@actions/core";
|
||||
import * as cp from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as httpm from "@actions/http-client";
|
||||
import * as path from "path";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import * as common from "./common";
|
||||
@@ -47,6 +46,15 @@ interface MonitorResponse {
|
||||
monitoring_started?: boolean;
|
||||
}
|
||||
|
||||
// Node 22+ terminates the process on unhandled promise rejections by default.
|
||||
// Third-party libraries used during Pre-step (notably @actions/cache's tar +
|
||||
// upload streams under concurrent matrix runs) can emit background rejections
|
||||
// that escape our try/catch, killing Pre-step silently and leaving the runner
|
||||
// without an agent installed. Log and continue instead.
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
core.warning(`Unhandled promise rejection during Pre-step: ${reason}`);
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log("[harden-runner] pre-step");
|
||||
@@ -364,22 +372,25 @@ interface MonitorResponse {
|
||||
return;
|
||||
}
|
||||
|
||||
let _http = new httpm.HttpClient();
|
||||
let statusCode: number | undefined;
|
||||
_http.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
let addSummary = "false";
|
||||
try {
|
||||
const monitorRequestData = {
|
||||
correlation_id: correlation_id,
|
||||
job: process.env["GITHUB_JOB"],
|
||||
};
|
||||
const resp = await _http.postJson<MonitorResponse>(
|
||||
`${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`,
|
||||
monitorRequestData
|
||||
);
|
||||
const url = `${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`;
|
||||
const resp = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(monitorRequestData),
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
|
||||
const responseData = resp.result;
|
||||
statusCode = resp.statusCode; // adding error code to check whether agent is getting installed or not.
|
||||
statusCode = resp.status;
|
||||
const responseData = resp.ok
|
||||
? ((await resp.json()) as MonitorResponse)
|
||||
: undefined;
|
||||
fs.appendFileSync(
|
||||
process.env.GITHUB_STATE,
|
||||
`monitorStatusCode=${statusCode}${EOL}`,
|
||||
@@ -495,8 +506,6 @@ export function sleep(ms: number) {
|
||||
}
|
||||
|
||||
async function callMonitorEndpoint(api_url: string, confg: Configuration) {
|
||||
const _http = new httpm.HttpClient();
|
||||
_http.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
let statusCode: number | undefined;
|
||||
let addSummary = "false";
|
||||
try {
|
||||
@@ -504,15 +513,19 @@ async function callMonitorEndpoint(api_url: string, confg: Configuration) {
|
||||
correlation_id: confg.correlation_id,
|
||||
job: process.env["GITHUB_JOB"],
|
||||
};
|
||||
const resp = await _http.postJson<MonitorResponse>(
|
||||
`${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`,
|
||||
monitorRequestData
|
||||
);
|
||||
statusCode = resp.statusCode;
|
||||
if (resp.statusCode === 200 && resp.result) {
|
||||
console.log(`Runner IP Address: ${resp.result.runner_ip_address}`);
|
||||
confg.one_time_key = resp.result.one_time_key;
|
||||
addSummary = resp.result.monitoring_started ? "true" : "false";
|
||||
const url = `${api_url}/github/${process.env["GITHUB_REPOSITORY"]}/actions/runs/${process.env["GITHUB_RUN_ID"]}/monitor`;
|
||||
const resp = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(monitorRequestData),
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
statusCode = resp.status;
|
||||
if (resp.ok) {
|
||||
const result = (await resp.json()) as MonitorResponse;
|
||||
console.log(`Runner IP Address: ${result.runner_ip_address}`);
|
||||
confg.one_time_key = result.one_time_key;
|
||||
addSummary = result.monitoring_started ? "true" : "false";
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`error in connecting to ${api_url}: ${e}`);
|
||||
|
||||
+65
-16
@@ -1,29 +1,78 @@
|
||||
import nock from "nock";
|
||||
import { STEPSECURITY_API_URL } from "./configs";
|
||||
import { isTLSEnabled } from "./tls-inspect";
|
||||
|
||||
const ORIGINAL_FETCH = globalThis.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = ORIGINAL_FETCH;
|
||||
});
|
||||
|
||||
function mockFetch(impl: typeof fetch) {
|
||||
globalThis.fetch = impl as typeof fetch;
|
||||
}
|
||||
|
||||
test("tls-inspect enabled", async () => {
|
||||
let owner = "h0x0er";
|
||||
let expected = true;
|
||||
const owner = "h0x0er";
|
||||
const expectedUrl = `${STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
|
||||
const resp = nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/actions/tls-inspection-status`)
|
||||
.reply(200, "");
|
||||
mockFetch(async (url, _init) => {
|
||||
expect(String(url)).toBe(expectedUrl);
|
||||
return new Response("", { status: 200 });
|
||||
});
|
||||
|
||||
let got = await isTLSEnabled(owner);
|
||||
|
||||
expect(got).toEqual(expected);
|
||||
const got = await isTLSEnabled(owner);
|
||||
expect(got).toBe(true);
|
||||
});
|
||||
|
||||
test("tls-inspect not enabled", async () => {
|
||||
let owner = "step-security";
|
||||
let expected = false;
|
||||
const owner = "step-security";
|
||||
|
||||
const resp = nock(`${STEPSECURITY_API_URL}`)
|
||||
.get(`/github/${owner}/actions/tls-inspection-status`)
|
||||
.reply(401, "");
|
||||
mockFetch(async () => new Response("unauthorized", { status: 401 }));
|
||||
|
||||
let got = await isTLSEnabled(owner);
|
||||
const got = await isTLSEnabled(owner);
|
||||
expect(got).toBe(false);
|
||||
});
|
||||
|
||||
expect(got).toEqual(expected);
|
||||
test("isTLSEnabled returns false within ~3s when server is slow (regression test for AggregateError)", async () => {
|
||||
const owner = "slow-org";
|
||||
|
||||
mockFetch((_url, init) => {
|
||||
return new Promise<Response>((_resolve, reject) => {
|
||||
const signal = init?.signal;
|
||||
if (signal) {
|
||||
if (signal.aborted) {
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
return;
|
||||
}
|
||||
signal.addEventListener("abort", () => {
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const start = Date.now();
|
||||
const result = await isTLSEnabled(owner);
|
||||
const elapsed = Date.now() - start;
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(elapsed).toBeLessThan(3500);
|
||||
expect(elapsed).toBeGreaterThanOrEqual(2800);
|
||||
}, 10_000);
|
||||
|
||||
test("isTLSEnabled returns false on connection error without hanging", async () => {
|
||||
const owner = "broken-org";
|
||||
|
||||
mockFetch(async () => {
|
||||
const err = new TypeError("fetch failed");
|
||||
(err as Error & { cause?: unknown }).cause = Object.assign(new Error("ECONNREFUSED"), { code: "ECONNREFUSED" });
|
||||
throw err;
|
||||
});
|
||||
|
||||
const start = Date.now();
|
||||
const result = await isTLSEnabled(owner);
|
||||
const elapsed = Date.now() - start;
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(elapsed).toBeLessThan(3500);
|
||||
});
|
||||
|
||||
+9
-12
@@ -1,26 +1,23 @@
|
||||
import { HttpClient } from "@actions/http-client";
|
||||
import { STEPSECURITY_API_URL } from "./configs";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
export async function isTLSEnabled(owner: string): Promise<boolean> {
|
||||
let tlsStatusEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
let httpClient = new HttpClient();
|
||||
httpClient.requestOptions = { socketTimeout: 3 * 1000 };
|
||||
const tlsStatusEndpoint = `${STEPSECURITY_API_URL}/github/${owner}/actions/tls-inspection-status`;
|
||||
core.info(`[!] Checking TLS_STATUS: ${owner}`);
|
||||
let isEnabled = false;
|
||||
try {
|
||||
let resp = await httpClient.get(tlsStatusEndpoint);
|
||||
if (resp.message.statusCode === 200) {
|
||||
isEnabled = true;
|
||||
const resp = await fetch(tlsStatusEndpoint, {
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
core.info(`[!] TLS_ENABLED: ${owner}`);
|
||||
} else {
|
||||
core.info(`[!] TLS_NOT_ENABLED: ${owner}`);
|
||||
return true;
|
||||
}
|
||||
core.info(`[!] TLS_NOT_ENABLED: ${owner}`);
|
||||
return false;
|
||||
} catch (e) {
|
||||
core.info(`[!] Unable to check TLS_STATUS`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
export function isGithubHosted() {
|
||||
|
||||
Reference in New Issue
Block a user