VYPR
High severity8.8NVD Advisory· Published Nov 18, 2024· Updated Apr 15, 2026

CVE-2024-52587

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.

PackageAffected versionsPatched versions
step-security/harden-runnerGitHub Actions
< 2.10.22.10.2

Patches

1
0080882f6c36

Merge pull request #476 from step-security/rc-16

https://github.com/step-security/harden-runnerVarun SharmaNov 18, 2024via ghsa
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 modified
  • dist/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 modified
  • dist/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 modified
  • src/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

News mentions

0

No linked articles in our index yet.