import { spawnSync } from "node:child_process"; import { chmod, readdir, readFile, rm, stat, writeFile } from "node:fs/promises"; import process from "node:process"; import { fileURLToPath } from "node:url"; import { assert, expect, test } from "vite-plus/test"; import { prepareRuntime } from "./runtime.ts"; import type { RuntimeProfile } from "./runtime.ts"; import { IS_ROOT, makeTempDir } from "#testing"; const MASTER_KEY = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8"; const ENCRYPTED = "v1:ICEiIyQlJicoKSor:oV_FAgnsN3p1FyegJxwGVdyjfyPio6qDeSUwbA"; // Runs `command` as the profile's user, from the root directory. function runAs(profile: RuntimeProfile, command: string, path: string): boolean { const result = spawnSync(command, [path], { cwd: "/", uid: profile.uid, gid: profile.gid }); return result.status === 0; } async function expectPrivate(path: string, uid: number, mode: number): Promise { const info = await stat(path); expect(info.uid).toStrictEqual(uid); expect(info.mode & 0o777).toBe(mode); } test.skipIf(!IS_ROOT)("prepares root-only reviewer and isolated profile credentials", async () => { const root = await makeTempDir(); await chmod(root, 0o711); const action = await makeTempDir(); try { await writeFile( `${action}/profiles.json`, JSON.stringify({ "reviewer-token": ENCRYPTED, profiles: [{ type: "claude", token: ENCRYPTED }, { type: "codex" }], }), ); const manifest = await prepareRuntime(action, root, MASTER_KEY); const serialized = await readFile(manifest.manifestPath, "utf8"); assert(!serialized.includes("secret-token")); expect(await readFile(manifest.reviewerFile, "utf8")).toBe("secret-token"); await expectPrivate(manifest.reviewerFile, 0, 0o600); const [claude, codex] = manifest.profiles; assert(claude !== undefined && codex !== undefined); expect(claude.uid).not.toStrictEqual(codex.uid); await expectPrivate(claude.home, claude.uid, 0o700); assert(claude.tokenFile !== null); expect(await readFile(claude.tokenFile, "utf8")).toBe("secret-token"); await expectPrivate(claude.tokenFile, claude.uid, 0o600); expect((await stat(codex.home)).uid).toStrictEqual(codex.uid); expect(codex.tokenFile).toBe(null); const ownToken = spawnSync("/bin/cat", [claude.tokenFile], { cwd: "/", uid: claude.uid, gid: claude.gid, encoding: "utf8", }); expect(ownToken.status).toBe(0); expect(ownToken.stdout).toBe("secret-token"); expect(runAs(claude, "/bin/cat", manifest.reviewerFile)).toBe(false); expect(runAs(claude, "/bin/ls", codex.home)).toBe(false); } finally { await rm(root, { recursive: true }); await rm(action, { recursive: true }); } }); test.skipIf(!IS_ROOT)("removes prepared credentials when output publication fails", async () => { const root = await makeTempDir(); const action = await makeTempDir(); try { await writeFile( `${action}/profiles.json`, JSON.stringify({ "reviewer-token": ENCRYPTED, profiles: [{ type: "claude", token: ENCRYPTED }], }), ); const result = spawnSync( process.execPath, [fileURLToPath(new URL("prepare.ts", import.meta.url))], { env: { ACTION_PATH: action, RUNNER_TEMP: root, BOT_AGENTS_MASTER_KEY: MASTER_KEY, GITHUB_OUTPUT: action, }, }, ); expect(result.status).not.toBe(0); expect(await readdir(root)).toStrictEqual([]); } finally { await rm(root, { recursive: true }); await rm(action, { recursive: true }); } });