106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
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<boolean> {
|
|
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<void> {
|
|
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 });
|
|
}
|
|
});
|