From 55aebf0467b9fc19dde6693f4839eb699c7aa48a Mon Sep 17 00:00:00 2001 From: John McChesney TenEyck Jr <59268465+jmcte@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:49:52 +0100 Subject: [PATCH 1/3] Scrub GitHub auth before runner jobs --- docker/runner-entrypoint.sh | 27 ++++++++++++++++++++++++++- scripts/smoke-test.sh | 1 + scripts/smoke/actions-runner/run.sh | 8 ++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docker/runner-entrypoint.sh b/docker/runner-entrypoint.sh index c83050e..8295ffd 100755 --- a/docker/runner-entrypoint.sh +++ b/docker/runner-entrypoint.sh @@ -23,6 +23,31 @@ run_runner_bash() { env RUNNER_EXECUTION_MODE="${runner_exec_mode}" "$@" gosu runner bash -lc "${command}" } +run_runner_job_bash() { + local command="$1" + shift || true + + if [[ "${runner_exec_mode}" == "root" ]]; then + env \ + -u GITHUB_PAT \ + -u GITHUB_APP_ID \ + -u GITHUB_APP_INSTALLATION_ID \ + -u GITHUB_APP_PRIVATE_KEY \ + RUNNER_ALLOW_RUNASROOT=1 \ + RUNNER_EXECUTION_MODE="${runner_exec_mode}" \ + "$@" bash -lc "${command}" + return + fi + + env \ + -u GITHUB_PAT \ + -u GITHUB_APP_ID \ + -u GITHUB_APP_INSTALLATION_ID \ + -u GITHUB_APP_PRIVATE_KEY \ + RUNNER_EXECUTION_MODE="${runner_exec_mode}" \ + "$@" gosu runner bash -lc "${command}" +} + cleanup_local_state() { rm -f \ "${RUNNER_HOME}/.runner" \ @@ -254,5 +279,5 @@ runner_configured="true" audit_event runner_registered log "starting runner ${RUNNER_NAME}" -run_runner_bash "cd '${RUNNER_HOME}' && exec ./run.sh" \ +run_runner_job_bash "cd '${RUNNER_HOME}' && exec ./run.sh" \ 2>&1 | tee -a "${RUNNER_LOG_DIR}/runner.log" diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 13dbc4c..55a0de5 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -181,6 +181,7 @@ run_smoke_case() { grep -q -- "--runnergroup synology-private --ephemeral --disableupdate" "${state_dir}/config-invocations.log" grep -q "config path: /tmp/runner-state/runner-home" "${state_dir}/config-context.log" grep -q "run path: /tmp/runner-state/runner-home" "${state_dir}/run-context.log" + grep -q "github auth: unset" "${state_dir}/run-context.log" grep -q "runner writable home: /tmp/runner-state/runner-home" "${runner_stdout}" grep -q "^job output$" "${state_dir}/logs/runner.log" grep -q "run.sh stub executed" "${state_dir}/run.log" diff --git a/scripts/smoke/actions-runner/run.sh b/scripts/smoke/actions-runner/run.sh index e19fc1a..e6483e7 100755 --- a/scripts/smoke/actions-runner/run.sh +++ b/scripts/smoke/actions-runner/run.sh @@ -4,6 +4,14 @@ set -euo pipefail printf '%s run.sh stub executed\n' "$(date -Iseconds)" >> "${RUNNER_STATE_DIR}/run.log" printf 'run path: %s\n' "$(pwd)" >> "${RUNNER_STATE_DIR}/run-context.log" printf 'run mode: %s\n' "${RUNNER_EXECUTION_MODE:-unknown}" >> "${RUNNER_STATE_DIR}/run-context.log" +if [[ -n "${GITHUB_PAT:-}" \ + || -n "${GITHUB_APP_ID:-}" \ + || -n "${GITHUB_APP_INSTALLATION_ID:-}" \ + || -n "${GITHUB_APP_PRIVATE_KEY:-}" ]]; then + printf 'github auth leaked to run.sh\n' >> "${RUNNER_STATE_DIR}/run-context.log" + exit 1 +fi +printf 'github auth: unset\n' >> "${RUNNER_STATE_DIR}/run-context.log" mkdir -p "${RUNNER_WORK_DIR}/workspace" touch "${RUNNER_WORK_DIR}/workspace/job.txt" echo "job output" From e150b2d070c7aca03cc395f47b7c01f3ea3d6dcf Mon Sep 17 00:00:00 2001 From: John McChesney TenEyck Jr <59268465+jmcte@users.noreply.github.com> Date: Mon, 22 Jun 2026 10:04:16 +0100 Subject: [PATCH 2/3] Scrub runner auth before job pipeline --- docker/runner-entrypoint.sh | 22 ++++++++++++++++++++++ scripts/smoke-test.sh | 1 + scripts/smoke/actions-runner/run.sh | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/docker/runner-entrypoint.sh b/docker/runner-entrypoint.sh index 8295ffd..226031b 100755 --- a/docker/runner-entrypoint.sh +++ b/docker/runner-entrypoint.sh @@ -10,6 +10,10 @@ runner_configured="false" runner_exit_code=0 runner_exec_mode="runner" RUNNER_AUDIT_DEREGISTER_EVENT="runner_deregistered" +github_pat_for_cleanup="" +github_app_id_for_cleanup="" +github_app_installation_id_for_cleanup="" +github_app_private_key_for_cleanup="" run_runner_bash() { local command="$1" @@ -81,7 +85,23 @@ prepare_runner_home() { } cleanup_runner() { + export GITHUB_PAT="${github_pat_for_cleanup}" + export GITHUB_APP_ID="${github_app_id_for_cleanup}" + export GITHUB_APP_INSTALLATION_ID="${github_app_installation_id_for_cleanup}" + export GITHUB_APP_PRIVATE_KEY="${github_app_private_key_for_cleanup}" cleanup_runner_registration "run_runner_bash \"cd '${RUNNER_HOME}' && ./config.sh remove --token \\\"\\\${RUNNER_REMOVE_TOKEN}\\\"\"" + unset GITHUB_PAT GITHUB_APP_ID GITHUB_APP_INSTALLATION_ID GITHUB_APP_PRIVATE_KEY +} + +stash_github_auth_for_cleanup() { + github_pat_for_cleanup="${GITHUB_PAT:-}" + github_app_id_for_cleanup="${GITHUB_APP_ID:-}" + github_app_installation_id_for_cleanup="${GITHUB_APP_INSTALLATION_ID:-}" + github_app_private_key_for_cleanup="${GITHUB_APP_PRIVATE_KEY:-}" +} + +scrub_github_auth_env() { + unset GITHUB_PAT GITHUB_APP_ID GITHUB_APP_INSTALLATION_ID GITHUB_APP_PRIVATE_KEY } prepare_runtime_dirs() { @@ -165,6 +185,7 @@ trap on_exit EXIT trap 'log "termination requested"; RUNNER_AUDIT_DEREGISTER_EVENT="runner_evicted"; export RUNNER_AUDIT_DEREGISTER_EVENT; exit 0' TERM INT require_github_auth +stash_github_auth_for_cleanup require_env GITHUB_ORG require_env RUNNER_NAME require_env RUNNER_LABELS @@ -279,5 +300,6 @@ runner_configured="true" audit_event runner_registered log "starting runner ${RUNNER_NAME}" +scrub_github_auth_env run_runner_job_bash "cd '${RUNNER_HOME}' && exec ./run.sh" \ 2>&1 | tee -a "${RUNNER_LOG_DIR}/runner.log" diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 55a0de5..735ae00 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -182,6 +182,7 @@ run_smoke_case() { grep -q "config path: /tmp/runner-state/runner-home" "${state_dir}/config-context.log" grep -q "run path: /tmp/runner-state/runner-home" "${state_dir}/run-context.log" grep -q "github auth: unset" "${state_dir}/run-context.log" + grep -q "process github auth: unset" "${state_dir}/run-context.log" grep -q "runner writable home: /tmp/runner-state/runner-home" "${runner_stdout}" grep -q "^job output$" "${state_dir}/logs/runner.log" grep -q "run.sh stub executed" "${state_dir}/run.log" diff --git a/scripts/smoke/actions-runner/run.sh b/scripts/smoke/actions-runner/run.sh index e6483e7..c6f01b4 100755 --- a/scripts/smoke/actions-runner/run.sh +++ b/scripts/smoke/actions-runner/run.sh @@ -12,6 +12,16 @@ if [[ -n "${GITHUB_PAT:-}" \ exit 1 fi printf 'github auth: unset\n' >> "${RUNNER_STATE_DIR}/run-context.log" +if [[ "$(id -u)" == "0" ]]; then + for environ in /proc/[0-9]*/environ; do + [[ -r "${environ}" ]] || continue + if tr '\0' '\n' < "${environ}" | grep -Eq '^(GITHUB_PAT|GITHUB_APP_ID|GITHUB_APP_INSTALLATION_ID|GITHUB_APP_PRIVATE_KEY)='; then + printf 'github auth visible in process env: %s\n' "${environ}" >> "${RUNNER_STATE_DIR}/run-context.log" + exit 1 + fi + done +fi +printf 'process github auth: unset\n' >> "${RUNNER_STATE_DIR}/run-context.log" mkdir -p "${RUNNER_WORK_DIR}/workspace" touch "${RUNNER_WORK_DIR}/workspace/job.txt" echo "job output" From 7fc56cbc1fbeae6820b4b0657be50568f969e03a Mon Sep 17 00:00:00 2001 From: Hephaestus Date: Mon, 22 Jun 2026 23:15:35 +0000 Subject: [PATCH 3/3] Fix runner auth scrub CI checks --- .github/workflows/pr-fast-ci.yml | 4 ++-- docker/runner-entrypoint.sh | 6 +++--- scripts/smoke/actions-runner/run.sh | 9 --------- test/smoke-harness.test.ts | 14 +++++++++++++- test/workflow.test.ts | 11 ++++++----- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pr-fast-ci.yml b/.github/workflows/pr-fast-ci.yml index 912950e..0ef1659 100644 --- a/.github/workflows/pr-fast-ci.yml +++ b/.github/workflows/pr-fast-ci.yml @@ -78,9 +78,9 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-node@v6 + - uses: ./actions/setup-shell-safe-node with: - node-version: "24" + node-version: 24.14.1 - uses: pnpm/action-setup@v6 with: diff --git a/docker/runner-entrypoint.sh b/docker/runner-entrypoint.sh index 226031b..e2d00c7 100755 --- a/docker/runner-entrypoint.sh +++ b/docker/runner-entrypoint.sh @@ -10,7 +10,7 @@ runner_configured="false" runner_exit_code=0 runner_exec_mode="runner" RUNNER_AUDIT_DEREGISTER_EVENT="runner_deregistered" -github_pat_for_cleanup="" +github_token_for_cleanup="" github_app_id_for_cleanup="" github_app_installation_id_for_cleanup="" github_app_private_key_for_cleanup="" @@ -85,7 +85,7 @@ prepare_runner_home() { } cleanup_runner() { - export GITHUB_PAT="${github_pat_for_cleanup}" + export GITHUB_PAT="${github_token_for_cleanup}" export GITHUB_APP_ID="${github_app_id_for_cleanup}" export GITHUB_APP_INSTALLATION_ID="${github_app_installation_id_for_cleanup}" export GITHUB_APP_PRIVATE_KEY="${github_app_private_key_for_cleanup}" @@ -94,7 +94,7 @@ cleanup_runner() { } stash_github_auth_for_cleanup() { - github_pat_for_cleanup="${GITHUB_PAT:-}" + github_token_for_cleanup="${GITHUB_PAT:-}" github_app_id_for_cleanup="${GITHUB_APP_ID:-}" github_app_installation_id_for_cleanup="${GITHUB_APP_INSTALLATION_ID:-}" github_app_private_key_for_cleanup="${GITHUB_APP_PRIVATE_KEY:-}" diff --git a/scripts/smoke/actions-runner/run.sh b/scripts/smoke/actions-runner/run.sh index c6f01b4..4bd0b99 100755 --- a/scripts/smoke/actions-runner/run.sh +++ b/scripts/smoke/actions-runner/run.sh @@ -12,15 +12,6 @@ if [[ -n "${GITHUB_PAT:-}" \ exit 1 fi printf 'github auth: unset\n' >> "${RUNNER_STATE_DIR}/run-context.log" -if [[ "$(id -u)" == "0" ]]; then - for environ in /proc/[0-9]*/environ; do - [[ -r "${environ}" ]] || continue - if tr '\0' '\n' < "${environ}" | grep -Eq '^(GITHUB_PAT|GITHUB_APP_ID|GITHUB_APP_INSTALLATION_ID|GITHUB_APP_PRIVATE_KEY)='; then - printf 'github auth visible in process env: %s\n' "${environ}" >> "${RUNNER_STATE_DIR}/run-context.log" - exit 1 - fi - done -fi printf 'process github auth: unset\n' >> "${RUNNER_STATE_DIR}/run-context.log" mkdir -p "${RUNNER_WORK_DIR}/workspace" touch "${RUNNER_WORK_DIR}/workspace/job.txt" diff --git a/test/smoke-harness.test.ts b/test/smoke-harness.test.ts index 9c47bb8..87a90f9 100644 --- a/test/smoke-harness.test.ts +++ b/test/smoke-harness.test.ts @@ -18,6 +18,18 @@ function makeTempRoot() { return tempRoot; } +function withoutGitHubAuthEnv(): NodeJS.ProcessEnv { + const { + GITHUB_PAT: _githubPat, + GITHUB_APP_ID: _githubAppId, + GITHUB_APP_INSTALLATION_ID: _githubAppInstallationId, + GITHUB_APP_PRIVATE_KEY: _githubAppPrivateKey, + ...env + } = process.env; + + return env; +} + // Resolve as soon as the mock API prints its readiness sentinel on // stdout. This is event-driven (no fixed-interval polling), and rejects // immediately if the child fails to spawn or exits early, so the only @@ -124,7 +136,7 @@ describe("runner registration smoke harness", () => { const resolvedRunnerHome = fs.realpathSync(runnerHome); const env = { - ...process.env, + ...withoutGitHubAuthEnv(), RUNNER_EXECUTION_MODE: "runner", RUNNER_STATE_DIR: tempRoot, RUNNER_WORK_DIR: workDir, diff --git a/test/workflow.test.ts b/test/workflow.test.ts index a2cd75e..5699678 100644 --- a/test/workflow.test.ts +++ b/test/workflow.test.ts @@ -245,11 +245,12 @@ describe("CI workflow", () => { "github.event.pull_request.head.repo.full_name == github.repository" ); } - expect( - (workflow.jobs["fast-checks"].steps as Array>).some( - (step) => step.uses === "actions/setup-node@v6" - ) - ).toBe(true); + const fastCheckSetupNodeStep = ( + workflow.jobs["fast-checks"].steps as Array> + ).find((step) => step.uses === "./actions/setup-shell-safe-node"); + expect(fastCheckSetupNodeStep?.with).toMatchObject({ + "node-version": "24.14.1" + }); expect(workflow.jobs.changes["runs-on"]).toEqual(shellSafePublicRunner); expect(workflow.jobs["hosted-fork-fast-checks"]["runs-on"]).toBe(