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 { output } from "./command.ts"; 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. async function runAs(profile: RuntimeProfile, command: string, path: string): Promise { const result = await output(command, { args: [path], cwd: "/", uid: profile.uid, gid: profile.gid, }); return result.success; } 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 = await output("/bin/cat", { args: [claude.tokenFile], cwd: "/", uid: claude.uid, gid: claude.gid, }); expect(ownToken.success).toBe(true); expect(new TextDecoder().decode(ownToken.stdout)).toBe("secret-token"); expect(await runAs(claude, "/bin/cat", manifest.reviewerFile)).toBe(false); expect(await 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 = await output(process.execPath, { args: [fileURLToPath(new URL("prepare.ts", import.meta.url))], env: { ACTION_PATH: action, RUNNER_TEMP: root, BOT_AGENTS_MASTER_KEY: MASTER_KEY, GITHUB_OUTPUT: action, }, clearEnv: true, }); expect(result.success).toBe(false); expect(await readdir(root)).toStrictEqual([]); } finally { await rm(root, { recursive: true }); await rm(action, { recursive: true }); } });