Harden drop-sudo regression coverage

This commit is contained in:
Winston Howes
2026-08-13 15:19:58 -04:00
committed by William Woodruff
parent ebb705f8cf
commit f7d5898e7e
7 changed files with 101 additions and 30 deletions
+3
View File
@@ -33,6 +33,9 @@ jobs:
- name: Test
run: pnpm test
- name: Test privileged Linux drop-sudo
run: pnpm run test:privileged
- name: Build bundles
run: pnpm run build
+21 -12
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -6,7 +6,7 @@
"scripts": {
"build": "esbuild src/main.ts --sourcemap=inline --format=cjs --bundle --platform=node --target=node20 --outdir=dist",
"check": "tsc --noEmit",
"test": "pnpm run build && node --test test/*.test.mjs && node --test --test-concurrency=1 test/privileged/*.test.mjs",
"test": "pnpm run build && node --test test/*.test.mjs",
"test:privileged": "pnpm run build && node --test --test-concurrency=1 test/privileged/*.test.mjs"
},
"dependencies": {
+1 -1
View File
@@ -172,7 +172,7 @@ async function dropSudoWithPrivileges(options: DropSudoOptions): Promise<void> {
);
}
async function ensurePasswordlessSudo(): Promise<void> {
export async function ensurePasswordlessSudo(): Promise<void> {
try {
await execCommand(SUDO_PATH, ["-n", "true"], { capture: true });
} catch (error) {
+31 -16
View File
@@ -4,7 +4,11 @@ import path from "path";
import os from "os";
import { setOutput } from "@actions/core";
import { checkOutput } from "./checkOutput";
import { dropSudo, verifySudoUnavailable } from "./dropSudo";
import {
dropSudo,
ensurePasswordlessSudo,
verifySudoUnavailable,
} from "./dropSudo";
import {
buildGatedCodexCommand,
endChildInput,
@@ -93,6 +97,31 @@ export async function runCodexExec({
break;
}
const isLinuxDropSudo =
safetyStrategy === "drop-sudo" && process.platform === "linux";
if (isLinuxDropSudo) {
if (
typeof process.getuid !== "function" ||
typeof process.getgid !== "function"
) {
throw new Error("Linux drop-sudo requires POSIX user and group APIs.");
}
if (process.getuid() === 0) {
throw new Error(
"Linux drop-sudo cannot run Codex from a runner whose default user is root."
);
}
try {
await ensurePasswordlessSudo();
} catch {
throw new Error(
"Linux drop-sudo requires passwordless sudo before Codex starts. If an earlier drop-sudo invocation already removed sudo, run Codex in that invocation or use a fresh job."
);
}
}
const runAsUser: string | null =
safetyStrategy === "unprivileged-user" ? codexUser : null;
@@ -119,21 +148,7 @@ export async function runCodexExec({
let linuxGate: GatedCodexCommand | null = null;
let pathToCodex = "codex";
if (safetyStrategy === "drop-sudo" && process.platform === "linux") {
if (
typeof process.getuid !== "function" ||
typeof process.getgid !== "function"
) {
throw new Error("Linux drop-sudo requires POSIX user and group APIs.");
}
const uid = process.getuid();
if (uid === 0) {
throw new Error(
"Linux drop-sudo cannot run Codex from a runner whose default user is root."
);
}
if (isLinuxDropSudo) {
pathToCodex = (await checkOutput(["which", "codex"])).trim();
if (!pathToCodex) {
throw new Error("could not find 'codex' in PATH");
+27
View File
@@ -0,0 +1,27 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
const actionPath = fileURLToPath(new URL("../action.yml", import.meta.url));
const action = readFileSync(actionPath, "utf8");
function stepCondition(name) {
const marker = ` - name: ${name}\n`;
const start = action.indexOf(marker);
assert.notEqual(start, -1, `missing action step: ${name}`);
const next = action.indexOf("\n - name: ", start + marker.length);
const block = action.slice(start, next < 0 ? undefined : next);
const condition = block
.split("\n")
.find((line) => line.startsWith(" if: "));
assert.ok(condition, `missing condition for action step: ${name}`);
return condition.slice(" if: ".length);
}
test("keeps Linux prompt execution out of the pre-drop steps", () => {
const expected = `\${{ inputs['safety-strategy'] == 'drop-sudo' && inputs['openai-api-key'] != '' && (runner.os != 'Linux' || (inputs.prompt == '' && inputs['prompt-file'] == '')) }}`;
assert.equal(stepCondition("Drop sudo privilege, if appropriate"), expected);
assert.equal(stepCondition("Verify sudo privilege removed"), expected);
});
+17
View File
@@ -272,6 +272,23 @@ test(
run("/usr/bin/id", ["-nG", user]).stdout.split(/\s+/).includes("sudo"),
false
);
const repeatedResult = spawnCodexAs({
uid,
gid,
user,
home,
binDir,
actionPath,
workspace,
outputPath,
prompt: "second invocation must remain gated",
});
assert.notEqual(repeatedResult.status, 0);
assert.match(
repeatedResult.stderr,
/If an earlier drop-sudo invocation already removed sudo/
);
}
);