Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pr-fast-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
34 changes: 32 additions & 2 deletions docker/runner-entrypoint.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,36 @@ function Request-RunnerToken {
return $response.token
}


function Invoke-ActionsRunner {
$credentialNames = @(
"GITHUB_PAT",
"GITHUB_TOKEN",
"GH_TOKEN",
"GITHUB_APP_ID",
"GITHUB_APP_INSTALLATION_ID",
"GITHUB_APP_PRIVATE_KEY"
)
$savedCredentials = @{}

foreach ($name in $credentialNames) {
$savedCredentials[$name] = [Environment]::GetEnvironmentVariable($name, "Process")
[Environment]::SetEnvironmentVariable($name, $null, "Process")
}

try {
& .\run.cmd 2>&1 |
Tee-Object -FilePath (Join-Path $env:RUNNER_LOG_DIR "runner.log") -Append |
Out-Null
$runnerExitCode = $LASTEXITCODE
return $runnerExitCode
} finally {
foreach ($name in $credentialNames) {
[Environment]::SetEnvironmentVariable($name, $savedCredentials[$name], "Process")
}
}
}

function Clear-RunnerState {
Remove-Item -Force -ErrorAction SilentlyContinue `
(Join-Path $env:RUNNER_HOME ".runner"), `
Expand Down Expand Up @@ -178,8 +208,8 @@ try {
& .\config.cmd @configArgs
$script:RunnerConfigured = $true
Write-RunnerLog "starting runner $env:RUNNER_NAME"
& .\run.cmd 2>&1 | Tee-Object -FilePath (Join-Path $env:RUNNER_LOG_DIR "runner.log") -Append
exit $LASTEXITCODE
$runnerExitCode = Invoke-ActionsRunner
exit $runnerExitCode
} finally {
Pop-Location
}
Expand Down
17 changes: 13 additions & 4 deletions docker/runner-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,21 @@ run_runner_bash() {
shift || true

if [[ "${runner_exec_mode}" == "root" ]]; then
env RUNNER_ALLOW_RUNASROOT=1 RUNNER_EXECUTION_MODE="${runner_exec_mode}" "$@" bash -lc "${command}"
env "$@" RUNNER_ALLOW_RUNASROOT=1 RUNNER_EXECUTION_MODE="${runner_exec_mode}" bash -lc "${command}"
return
fi

env RUNNER_EXECUTION_MODE="${runner_exec_mode}" "$@" gosu runner bash -lc "${command}"
env "$@" RUNNER_EXECUTION_MODE="${runner_exec_mode}" gosu runner bash -lc "${command}"
}

run_actions_runner() {
run_runner_bash "cd '${RUNNER_HOME}' && exec ./run.sh" \
-u GITHUB_PAT \
-u GITHUB_TOKEN \
-u GH_TOKEN \
-u GITHUB_APP_ID \
-u GITHUB_APP_INSTALLATION_ID \
-u GITHUB_APP_PRIVATE_KEY
}

cleanup_local_state() {
Expand Down Expand Up @@ -254,5 +264,4 @@ runner_configured="true"
audit_event runner_registered

log "starting runner ${RUNNER_NAME}"
run_runner_bash "cd '${RUNNER_HOME}' && exec ./run.sh" \
2>&1 | tee -a "${RUNNER_LOG_DIR}/runner.log"
run_actions_runner 2>&1 | tee -a "${RUNNER_LOG_DIR}/runner.log"
5 changes: 4 additions & 1 deletion scripts/ci/run-fast-checks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"

corepack enable
corepack_bin_dir="${COREPACK_BIN_DIR:-${RUNNER_TEMP:-${TMPDIR:-/tmp}}/corepack-bin}"
mkdir -p "${corepack_bin_dir}"
corepack enable --install-directory "${corepack_bin_dir}"
export PATH="${corepack_bin_dir}:${PATH}"
corepack prepare pnpm@10.32.1 --activate

pnpm install --frozen-lockfile
Expand Down
1 change: 1 addition & 0 deletions scripts/guest/macos-runner-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ RUNNER_CONFIGURED="true"

log "starting runner ${RUNNER_NAME}"
(
unset GITHUB_PAT GITHUB_TOKEN GH_TOKEN GITHUB_APP_ID GITHUB_APP_INSTALLATION_ID GITHUB_APP_PRIVATE_KEY
cd "${RUNNER_ROOT}"
./run.sh
)
6 changes: 6 additions & 0 deletions scripts/smoke/actions-runner/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ 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"
for credential_name in GITHUB_PAT GITHUB_APP_ID GITHUB_APP_INSTALLATION_ID GITHUB_APP_PRIVATE_KEY; do
if [[ -n "${!credential_name:-}" ]]; then
printf 'leaked credential: %s\n' "${credential_name}" >> "${RUNNER_STATE_DIR}/run-context.log"
exit 1
fi
done
mkdir -p "${RUNNER_WORK_DIR}/workspace"
touch "${RUNNER_WORK_DIR}/workspace/job.txt"
echo "job output"
46 changes: 45 additions & 1 deletion test/smoke-harness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import path from "node:path";
import { afterEach, describe, expect, test } from "vitest";

const tempRoots: string[] = [];
const runnerCredentialNames = [
"GITHUB_PAT",
"GITHUB_TOKEN",
"GH_TOKEN",
"GITHUB_APP_ID",
"GITHUB_APP_INSTALLATION_ID",
"GITHUB_APP_PRIVATE_KEY",
];

afterEach(() => {
for (const tempRoot of tempRoots.splice(0)) {
Expand All @@ -18,6 +26,14 @@ function makeTempRoot() {
return tempRoot;
}

function withoutRunnerCredentials(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const scrubbedEnv = { ...env };
for (const name of runnerCredentialNames) {
delete scrubbedEnv[name];
}
return scrubbedEnv;
}

// 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
Expand Down Expand Up @@ -124,7 +140,7 @@ describe("runner registration smoke harness", () => {
const resolvedRunnerHome = fs.realpathSync(runnerHome);

const env = {
...process.env,
...withoutRunnerCredentials(process.env),
RUNNER_EXECUTION_MODE: "runner",
RUNNER_STATE_DIR: tempRoot,
RUNNER_WORK_DIR: workDir,
Expand Down Expand Up @@ -180,6 +196,34 @@ describe("runner registration smoke harness", () => {
expect(fs.existsSync(path.join(workDir, "workspace", "job.txt"))).toBe(true);
});

test("scrubs GitHub credentials before launching runner jobs", () => {
const linuxEntrypoint = fs.readFileSync(
path.resolve("docker/runner-entrypoint.sh"),
"utf8"
);
const windowsEntrypoint = fs.readFileSync(
path.resolve("docker/runner-entrypoint.ps1"),
"utf8"
);
const macosBootstrap = fs.readFileSync(
path.resolve("scripts/guest/macos-runner-bootstrap.sh"),
"utf8"
);

for (const name of runnerCredentialNames) {
expect(linuxEntrypoint).toContain(`-u ${name}`);
expect(windowsEntrypoint).toContain(`\"${name}\"`);
}
expect(linuxEntrypoint).toContain("run_actions_runner 2>&1");
expect(windowsEntrypoint).toContain("Out-Null");
expect(windowsEntrypoint).toContain("$runnerExitCode = Invoke-ActionsRunner");
expect(windowsEntrypoint).toContain("exit $runnerExitCode");
expect(windowsEntrypoint).not.toContain("exit (Invoke-ActionsRunner)");
expect(macosBootstrap).toContain(
"unset GITHUB_PAT GITHUB_TOKEN GH_TOKEN GITHUB_APP_ID GITHUB_APP_INSTALLATION_ID GITHUB_APP_PRIVATE_KEY"
);
});

test("wires the smoke script to run both execution modes and verify cleanup", () => {
const script = fs.readFileSync(
path.resolve("scripts/smoke-test.sh"),
Expand Down
2 changes: 1 addition & 1 deletion test/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ describe("CI workflow", () => {
}
expect(
(workflow.jobs["fast-checks"].steps as Array<Record<string, unknown>>).some(
(step) => step.uses === "actions/setup-node@v6"
(step) => step.uses === "./actions/setup-shell-safe-node"
)
).toBe(true);

Expand Down
Loading