CVE-2024-52587
Description
StepSecurity's Harden-Runner provides network egress filtering and runtime security for GitHub-hosted and self-hosted runners. Versions of step-security/harden-runner prior to v2.10.2 contain multiple command injection weaknesses via environment variables that could potentially be exploited under specific conditions. However, due to the current execution order of pre-steps in GitHub Actions and the placement of harden-runner as the first step in a job, the likelihood of exploitation is low as the Harden-Runner action reads the environment variable during the pre-step stage. There are no known exploits at this time. Version 2.10.2 contains a patch.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
step-security/harden-runnerGitHub Actions | < 2.10.2 | 2.10.2 |
Patches
10080882f6c36Merge pull request #476 from step-security/rc-16
11 files changed · +149 −97
dist/index.js+46 −2 modified@@ -2838,7 +2838,7 @@ var lib_core = __nccwpck_require__(186); var external_fs_ = __nccwpck_require__(747); ;// CONCATENATED MODULE: ./src/configs.ts const STEPSECURITY_ENV = "agent"; // agent or int -const STEPSECURITY_API_URL = `https://${STEPSECURITY_ENV}.api.stepsecurity.io/v1`; +const configs_STEPSECURITY_API_URL = `https://${STEPSECURITY_ENV}.api.stepsecurity.io/v1`; const configs_STEPSECURITY_WEB_URL = "https://app.stepsecurity.io"; ;// CONCATENATED MODULE: ./src/common.ts @@ -3014,6 +3014,49 @@ function isDocker() { return isDockerCached; } +// EXTERNAL MODULE: ./node_modules/@actions/http-client/lib/index.js +var lib = __nccwpck_require__(255); +;// 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); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; + + + +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 }; + core.info(`[!] Checking TLS_STATUS: ${owner}`); + let isEnabled = false; + try { + let resp = yield httpClient.get(tlsStatusEndpoint); + if (resp.message.statusCode === 200) { + isEnabled = true; + core.info(`[!] TLS_ENABLED: ${owner}`); + } + else { + core.info(`[!] TLS_NOT_ENABLED: ${owner}`); + } + } + catch (e) { + core.info(`[!] Unable to check TLS_STATUS`); + } + return isEnabled; + }); +} +function isGithubHosted() { + const runnerEnvironment = process.env.RUNNER_ENVIRONMENT || ""; + return runnerEnvironment === "github-hosted"; +} + ;// CONCATENATED MODULE: ./src/index.ts var src_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } @@ -3028,13 +3071,14 @@ var src_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argu + (() => src_awaiter(void 0, void 0, void 0, function* () { console.log("[harden-runner] main-step"); if (process.platform !== "linux") { console.log(UBUNTU_MESSAGE); return; } - if (isDocker()) { + if (isGithubHosted() && isDocker()) { console.log(CONTAINER_MESSAGE); return; }
dist/index.js.map+1 −1 modifieddist/post/index.js+61 −28 modified@@ -139,7 +139,7 @@ const command_1 = __nccwpck_require__(351); const file_command_1 = __nccwpck_require__(717); const utils_1 = __nccwpck_require__(278); const os = __importStar(__nccwpck_require__(87)); -const path = __importStar(__nccwpck_require__(277)); +const path = __importStar(__nccwpck_require__(622)); const oidc_utils_1 = __nccwpck_require__(41); /** * The code to exit an action @@ -618,7 +618,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = void 0; -const path = __importStar(__nccwpck_require__(277)); +const path = __importStar(__nccwpck_require__(622)); /** * toPosixPath converts the given path to the posix form. On Windows, \\ will be * replaced with /. @@ -2752,7 +2752,7 @@ module.exports = require("os"); /***/ }), -/***/ 277: +/***/ 622: /***/ ((module) => { "use strict"; @@ -2837,10 +2837,10 @@ var external_fs_ = __nccwpck_require__(747); ;// CONCATENATED MODULE: external "child_process" const external_child_process_namespaceObject = require("child_process"); // EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js -var core = __nccwpck_require__(186); +var lib_core = __nccwpck_require__(186); ;// CONCATENATED MODULE: ./src/configs.ts const STEPSECURITY_ENV = "agent"; // agent or int -const STEPSECURITY_API_URL = `https://${STEPSECURITY_ENV}.api.stepsecurity.io/v1`; +const configs_STEPSECURITY_API_URL = `https://${STEPSECURITY_ENV}.api.stepsecurity.io/v1`; const STEPSECURITY_WEB_URL = "https://app.stepsecurity.io"; ;// CONCATENATED MODULE: ./src/common.ts @@ -2905,9 +2905,9 @@ function addSummary() { //console.error(err); } if (needsSubscription) { - yield core.summary.addSeparator() + yield lib_core.summary.addSeparator() .addRaw(`<h2>⚠️ Your GitHub Actions Runtime Security is currently disabled!</h2>`); - yield core.summary.addRaw(` + yield lib_core.summary.addRaw(` <p>It appears that you're using the <a href="https://github.com/step-security/harden-runner">Harden-Runner GitHub Action</a> by StepSecurity within a private repository. However, runtime security is not enabled as your organization hasn't signed up for a free trial or a paid subscription yet.</p> <p>To enable runtime security, start a free trial today by installing the <a href="https://github.com/apps/stepsecurity-actions-security">StepSecurity Actions Security GitHub App</a>. For more information or assistance, feel free to reach out to us through our <a href="https://www.stepsecurity.io/contact">contact form</a>.</p> `) @@ -2925,7 +2925,7 @@ function addSummary() { return; } const insightsRow = `<p><b><a href="${insights_url}">📄 View Full Report</a></b></p>`; - yield core.summary.addSeparator().addRaw(`<h2>🛡 StepSecurity Report</h2>`); + yield lib_core.summary.addSeparator().addRaw(`<h2>🛡 StepSecurity Report</h2>`); tableEntries.sort((a, b) => { if (a.status === "❌ Blocked" && b.status !== "❌ Blocked") { return -1; @@ -2938,7 +2938,7 @@ function addSummary() { } }); tableEntries = tableEntries.slice(0, 3); - yield core.summary.addRaw(` + yield lib_core.summary.addRaw(` <blockquote> <p>Preview of the outbound network calls during this workflow run.</p></blockquote> <h3>Network Calls</h3> @@ -2967,7 +2967,7 @@ function addSummary() { </table> ${insightsRow} `); - yield core.summary.addRaw(`<p><i>Markdown generated by the <a href="https://github.com/step-security/harden-runner">Harden-Runner GitHub Action</a>.</i></p>`) + yield lib_core.summary.addRaw(`<p><i>Markdown generated by the <a href="https://github.com/step-security/harden-runner">Harden-Runner GitHub Action</a>.</i></p>`) .addSeparator() .write(); }); @@ -3031,34 +3031,68 @@ function isSecondaryPod() { const workDir = "/__w"; return external_fs_.existsSync(workDir); } -function getRunnerTempDir() { - const isTest = process.env["isTest"]; - if (isTest === "1") { - return "/tmp"; - } - return process.env["RUNNER_TEMP"] || "/tmp"; -} function sendAllowedEndpoints(endpoints) { const allowedEndpoints = endpoints.split(" "); // endpoints are space separated for (const endpoint of allowedEndpoints) { if (endpoint) { - const encodedEndpoint = Buffer.from(endpoint).toString("base64"); - cp.execSync(`echo "${endpoint}" > "${getRunnerTempDir()}/step_policy_endpoint_${encodedEndpoint}"`); + let encodedEndpoint = Buffer.from(endpoint).toString("base64"); + let endpointPolicyStr = `step_policy_endpoint_${encodedEndpoint}`; + echo(endpointPolicyStr); } } if (allowedEndpoints.length > 0) { applyPolicy(allowedEndpoints.length); } } function applyPolicy(count) { - const fileName = `step_policy_apply_${count}`; - cp.execSync(`echo "${fileName}" > "${getRunnerTempDir()}/${fileName}"`); + let applyPolicyStr = `step_policy_apply_${count}`; + echo(applyPolicyStr); } -function removeStepPolicyFiles() { - external_child_process_namespaceObject.execSync(`rm ${getRunnerTempDir()}/step_policy_*`); +function echo(content) { + cp.execFileSync("echo", [content]); } -function arcCleanUp() { - external_child_process_namespaceObject.execSync(`echo "cleanup" > "${getRunnerTempDir()}/step_policy_cleanup"`); + +// EXTERNAL MODULE: ./node_modules/@actions/http-client/lib/index.js +var lib = __nccwpck_require__(255); +;// 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); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; + + + +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 }; + core.info(`[!] Checking TLS_STATUS: ${owner}`); + let isEnabled = false; + try { + let resp = yield httpClient.get(tlsStatusEndpoint); + if (resp.message.statusCode === 200) { + isEnabled = true; + core.info(`[!] TLS_ENABLED: ${owner}`); + } + else { + core.info(`[!] TLS_NOT_ENABLED: ${owner}`); + } + } + catch (e) { + core.info(`[!] Unable to check TLS_STATUS`); + } + return isEnabled; + }); +} +function isGithubHosted() { + const runnerEnvironment = process.env.RUNNER_ENVIRONMENT || ""; + return runnerEnvironment === "github-hosted"; } ;// CONCATENATED MODULE: ./src/cleanup.ts @@ -3076,20 +3110,19 @@ var cleanup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ + (() => cleanup_awaiter(void 0, void 0, void 0, function* () { console.log("[harden-runner] post-step"); if (process.platform !== "linux") { console.log(UBUNTU_MESSAGE); return; } - if (isDocker()) { + if (isGithubHosted() && isDocker()) { console.log(CONTAINER_MESSAGE); return; } if (isArcRunner()) { console.log(`[!] ${ARC_RUNNER_MESSAGE}`); - arcCleanUp(); - removeStepPolicyFiles(); return; } if (process.env.STATE_selfHosted === "true") {
dist/post/index.js.map+1 −1 modifieddist/pre/index.js+15 −19 modified@@ -71535,34 +71535,25 @@ function isSecondaryPod() { const workDir = "/__w"; return external_fs_.existsSync(workDir); } -function getRunnerTempDir() { - const isTest = process.env["isTest"]; - if (isTest === "1") { - return "/tmp"; - } - return process.env["RUNNER_TEMP"] || "/tmp"; -} function sendAllowedEndpoints(endpoints) { const allowedEndpoints = endpoints.split(" "); // endpoints are space separated for (const endpoint of allowedEndpoints) { if (endpoint) { - const encodedEndpoint = Buffer.from(endpoint).toString("base64"); - external_child_process_.execSync(`echo "${endpoint}" > "${getRunnerTempDir()}/step_policy_endpoint_${encodedEndpoint}"`); + let encodedEndpoint = Buffer.from(endpoint).toString("base64"); + let endpointPolicyStr = `step_policy_endpoint_${encodedEndpoint}`; + echo(endpointPolicyStr); } } if (allowedEndpoints.length > 0) { applyPolicy(allowedEndpoints.length); } } function applyPolicy(count) { - const fileName = `step_policy_apply_${count}`; - external_child_process_.execSync(`echo "${fileName}" > "${getRunnerTempDir()}/${fileName}"`); + let applyPolicyStr = `step_policy_apply_${count}`; + echo(applyPolicyStr); } -function removeStepPolicyFiles() { - cp.execSync(`rm ${getRunnerTempDir()}/step_policy_*`); -} -function arcCleanUp() { - cp.execSync(`echo "cleanup" > "${getRunnerTempDir()}/step_policy_cleanup"`); +function echo(content) { + external_child_process_.execFileSync("echo", [content]); } ;// CONCATENATED MODULE: ./src/tls-inspect.ts @@ -71735,7 +71726,7 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar console.log(UBUNTU_MESSAGE); return; } - if (isDocker()) { + if (isGithubHosted() && isDocker()) { console.log(CONTAINER_MESSAGE); return; } @@ -71836,7 +71827,7 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar if (confg.egress_policy === "block") { try { if (process.env.USER) { - external_child_process_.execSync(`sudo chown -R ${process.env.USER} /home/agent`); + chownForFolder(process.env.USER, "/home/agent"); } const confgStr = JSON.stringify(confg); external_fs_.writeFileSync("/home/agent/block_event.json", confgStr); @@ -71882,7 +71873,7 @@ var setup_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar } const confgStr = JSON.stringify(confg); external_child_process_.execSync("sudo mkdir -p /home/agent"); - external_child_process_.execSync("sudo chown -R $USER /home/agent"); + chownForFolder(process.env.USER, "/home/agent"); let isTLS = yield isTLSEnabled(github.context.repo.owner); const agentInstalled = yield installAgent(isTLS, confgStr); if (agentInstalled) { @@ -71923,6 +71914,11 @@ function setup_sleep(ms) { setTimeout(resolve, ms); }); } +function chownForFolder(newOwner, target) { + let cmd = "sudo"; + let args = ["chown", "-R", newOwner, target]; + external_child_process_.execFileSync(cmd, args); +} })();
dist/pre/index.js.map+1 −1 modifiedsrc/arc-runner.test.ts+1 −12 modified@@ -1,20 +1,9 @@ import { isArcRunner, sendAllowedEndpoints } from "./arc-runner"; - it("should correctly recognize arc based runner", async () => { process.env["GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT"] = "actions-runner-controller/2.0.1"; - let isArc: boolean = await isArcRunner(); + let isArc: boolean = await isArcRunner(); expect(isArc).toBe(true); - }); - - -it("should write endpoint files", ()=>{ - process.env["isTest"] = "1" - - let allowed_endpoints = ["github.com:443", "*.google.com:443", "youtube.com"].join(" "); - sendAllowedEndpoints(allowed_endpoints); - -})
src/arc-runner.ts+8 −23 modified@@ -1,6 +1,6 @@ import * as cp from "child_process"; import * as fs from "fs"; -import { sleep } from "./setup"; +import path from "path"; export function isArcRunner(): boolean { const runnerUserAgent = process.env["GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT"]; @@ -21,25 +21,14 @@ function isSecondaryPod(): boolean { return fs.existsSync(workDir); } -function getRunnerTempDir(): string { - const isTest = process.env["isTest"]; - - if (isTest === "1") { - return "/tmp"; - } - - return process.env["RUNNER_TEMP"] || "/tmp"; -} - export function sendAllowedEndpoints(endpoints: string): void { const allowedEndpoints = endpoints.split(" "); // endpoints are space separated for (const endpoint of allowedEndpoints) { if (endpoint) { - const encodedEndpoint = Buffer.from(endpoint).toString("base64"); - cp.execSync( - `echo "${endpoint}" > "${getRunnerTempDir()}/step_policy_endpoint_${encodedEndpoint}"` - ); + let encodedEndpoint = Buffer.from(endpoint).toString("base64"); + let endpointPolicyStr = `step_policy_endpoint_${encodedEndpoint}`; + echo(endpointPolicyStr); } } @@ -49,14 +38,10 @@ export function sendAllowedEndpoints(endpoints: string): void { } function applyPolicy(count: number): void { - const fileName = `step_policy_apply_${count}`; - cp.execSync(`echo "${fileName}" > "${getRunnerTempDir()}/${fileName}"`); -} - -export function removeStepPolicyFiles() { - cp.execSync(`rm ${getRunnerTempDir()}/step_policy_*`); + let applyPolicyStr = `step_policy_apply_${count}`; + echo(applyPolicyStr); } -export function arcCleanUp() { - cp.execSync(`echo "cleanup" > "${getRunnerTempDir()}/step_policy_cleanup"`); +function echo(content: string) { + cp.execFileSync("echo", [content]); }
src/cleanup.ts+3 −5 modified@@ -2,24 +2,22 @@ import * as fs from "fs"; import * as cp from "child_process"; import * as common from "./common"; import isDocker from "is-docker"; -import { arcCleanUp, isArcRunner, removeStepPolicyFiles } from "./arc-runner"; - +import { isArcRunner } from "./arc-runner"; +import { isGithubHosted } from "./tls-inspect"; (async () => { console.log("[harden-runner] post-step"); if (process.platform !== "linux") { console.log(common.UBUNTU_MESSAGE); return; } - if (isDocker()) { + if (isGithubHosted() && isDocker()) { console.log(common.CONTAINER_MESSAGE); return; } if (isArcRunner()) { console.log(`[!] ${common.ARC_RUNNER_MESSAGE}`); - arcCleanUp(); - removeStepPolicyFiles(); return; }
src/index.ts+2 −2 modified@@ -2,15 +2,15 @@ import * as common from "./common"; import * as core from "@actions/core"; import isDocker from "is-docker"; import { STEPSECURITY_WEB_URL } from "./configs"; - +import { isGithubHosted } from "./tls-inspect"; (async () => { console.log("[harden-runner] main-step"); if (process.platform !== "linux") { console.log(common.UBUNTU_MESSAGE); return; } - if (isDocker()) { + if (isGithubHosted() && isDocker()) { console.log(common.CONTAINER_MESSAGE); return; }
src/setup.ts+10 −3 modified@@ -39,7 +39,7 @@ interface MonitorResponse { console.log(common.UBUNTU_MESSAGE); return; } - if (isDocker()) { + if (isGithubHosted() && isDocker()) { console.log(common.CONTAINER_MESSAGE); return; } @@ -165,8 +165,9 @@ interface MonitorResponse { if (confg.egress_policy === "block") { try { if (process.env.USER) { - cp.execSync(`sudo chown -R ${process.env.USER} /home/agent`); + chownForFolder(process.env.USER, "/home/agent"); } + const confgStr = JSON.stringify(confg); fs.writeFileSync("/home/agent/block_event.json", confgStr); await sleep(5000); @@ -225,7 +226,7 @@ interface MonitorResponse { const confgStr = JSON.stringify(confg); cp.execSync("sudo mkdir -p /home/agent"); - cp.execSync("sudo chown -R $USER /home/agent"); + chownForFolder(process.env.USER, "/home/agent"); let isTLS = await isTLSEnabled(context.repo.owner); @@ -269,3 +270,9 @@ export function sleep(ms) { setTimeout(resolve, ms); }); } + +function chownForFolder(newOwner: string, target: string) { + let cmd = "sudo"; + let args = ["chown", "-R", newOwner, target]; + cp.execFileSync(cmd, args); +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/advisories/GHSA-g85v-wf27-67xcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-52587ghsaADVISORY
- github.com/step-security/harden-runner/blob/951b48540b429070694bc8abd82fd6901eb123ca/src/arc-runner.tsnvdWEB
- github.com/step-security/harden-runner/blob/951b48540b429070694bc8abd82fd6901eb123ca/src/arc-runner.tsnvdWEB
- github.com/step-security/harden-runner/blob/951b48540b429070694bc8abd82fd6901eb123ca/src/arc-runner.tsnvdWEB
- github.com/step-security/harden-runner/blob/951b48540b429070694bc8abd82fd6901eb123ca/src/arc-runner.tsnvdWEB
- github.com/step-security/harden-runner/blob/951b48540b429070694bc8abd82fd6901eb123ca/src/setup.tsnvdWEB
- github.com/step-security/harden-runner/blob/951b48540b429070694bc8abd82fd6901eb123ca/src/setup.tsnvdWEB
- github.com/step-security/harden-runner/commit/0080882f6c36860b6ba35c610c98ce87d4e2f26fnvdWEB
- github.com/step-security/harden-runner/security/advisories/GHSA-g85v-wf27-67xcnvdWEB
News mentions
0No linked articles in our index yet.