import { spawn } from "node:child_process"; import { lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { isReducedCiContractRun, loadCiGateContract, parseCiGateContract, } from "../../scripts/contracts/ci-gates.ts"; import { generateCiWorkflow } from "../../scripts/generate-ci-workflow.ts"; import { validatePackageScriptGraph } from "../../scripts/lib/package-script-graph.ts"; const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); describe("selective Task 3 contract closure", () => { it("builds a private offline aggregate-cgroup provider launch without argv secrets", async () => { const { encodeProviderBwrapInput, encodeProviderScopeFrame, formatProviderCgroupUnitName, systemctlKillProviderArguments, systemdRunProviderArguments, } = await import("../../scripts/lib/provider-cgroup.ts"); const unit = formatProviderCgroupUnitName( "vulnerability", 42, "0123456789abcdef01234567", ); const command = "node provider.mjs --token command-secret"; const credential = "credential-secret"; const launch = systemdRunProviderArguments( unit, 1_800_000, 1_200, "/trusted/node", "/workspace/scripts/lib/provider-scope-wrapper.ts", "/exact/report.json", 12, 34, ); expect(launch).toEqual(expect.arrayContaining([ "--scope", "--property=MemoryMax=1073741824", "--property=MemorySwapMax=0", "--property=TasksMax=64", "--property=CPUQuota=100%", "--property=KillMode=control-group", "/trusted/node", "/workspace/scripts/lib/provider-scope-wrapper.ts", ])); expect(launch.join("\0")).not.toContain(command); expect(launch.join("\0")).not.toContain(credential); expect( encodeProviderBwrapInput( ["--unshare-net", "--bind", "/exact/report.json", "/exact/report.json"], { PROVIDER_COMMAND: command, PROVIDER_CREDENTIAL: credential }, ), ).toEqual(expect.any(Buffer)); expect(systemctlKillProviderArguments(unit)).toEqual([ "--user", "kill", "--kill-whom=all", "--signal=SIGKILL", unit, ]); // `bwrap --args FD` stops parsing at the first non-option and never hands // the remainder back, so a command placed in the args file is dropped and // bubblewrap exits with its usage text. Refusing `--` in the option stream // is what keeps that silent no-sandbox launch from returning. expect(() => encodeProviderBwrapInput( ["--unshare-net", "--", "/usr/bin/prlimit"], { PROVIDER_COMMAND: command }, ), ).toThrow(/terminate the option stream/u); const frame = encodeProviderScopeFrame({ bwrapInput: Buffer.from("private-bwrap-vector\0"), bwrapCommand: ["/usr/bin/prlimit", "--nofile=64:64", "--", "/bin/sh", "-eu", "-c", 'exec /bin/sh -eu -c "$PROVIDER_COMMAND"'], reportPath: "/exact/report.json", reportDev: 12, reportIno: 34, }); expect(frame.readUInt32BE(0)).toBe(frame.byteLength - 4); expect(frame.subarray(4).toString("utf8")).toContain( Buffer.from("private-bwrap-vector\0").toString("base64"), ); expect(launch.join("\0")).not.toContain("private-bwrap-vector"); // The command vector rides on real argv, so it must never be able to carry // the secret that the args file exists to hide. expect(frame.subarray(4).toString("utf8")).not.toContain(credential); expect(() => encodeProviderScopeFrame({ bwrapInput: Buffer.from("x\0"), bwrapCommand: [], reportPath: "/exact/report.json", reportDev: 12, reportIno: 34, }), ).toThrow(/bwrap command is invalid/u); expect(() => encodeProviderScopeFrame({ bwrapInput: Buffer.from("x\0"), bwrapCommand: ["prlimit"], reportPath: "/exact/report.json", reportDev: 12, reportIno: 34, }), ).toThrow(/bwrap command is invalid/u); }); it("removes only the pinned raw inode during parent-loss cleanup", async () => { const { cleanupOwnedProviderReport } = await import( "../../scripts/lib/provider-raw-cleanup.ts" ); const root = await mkdtemp(path.join(tmpdir(), "provider-raw-cleanup-")); roots.push(root); const reportPath = path.join(root, "raw.json"); const originalPath = path.join(root, "original.json"); await writeFile(reportPath, "owned\n"); const identity = await lstat(reportPath); await rename(reportPath, originalPath); await writeFile(reportPath, "unrelated\n"); await expect(cleanupOwnedProviderReport({ reportPath, reportDev: identity.dev, reportIno: identity.ino, })).resolves.toBe(false); await expect(readFile(reportPath, "utf8")).resolves.toBe("unrelated\n"); await rm(reportPath); await rename(originalPath, reportPath); await expect(cleanupOwnedProviderReport({ reportPath, reportDev: identity.dev, reportIno: identity.ino, })).resolves.toBe(true); await expect(lstat(reportPath)).rejects.toMatchObject({ code: "ENOENT" }); await expect(readdir(root)).resolves.toEqual([]); }); it("uses early liveness EOF to clean the pinned raw file without waiting for a command frame", async () => { const root = await mkdtemp(path.join(tmpdir(), "provider-scope-eof-")); roots.push(root); const reportPath = path.join(root, "raw.json"); await writeFile(reportPath, "partial\n"); const identity = await lstat(reportPath); const child = spawn(process.execPath, [ path.resolve("scripts/lib/provider-scope-wrapper.ts"), "1", reportPath, String(identity.dev), String(identity.ino), ], { stdio: ["pipe", "pipe", "pipe"] }); const completion = waitForChildResult(child); await new Promise((resolve) => setTimeout(resolve, 75)); expect(child.exitCode).toBeNull(); await expect(readFile(reportPath, "utf8")).resolves.toBe("partial\n"); child.stdin.end(); const result = await within(completion, 1_000, "provider scope EOF close"); expect(result).toEqual({ code: 125, signal: null }); await expect(lstat(reportPath)).rejects.toMatchObject({ code: "ENOENT" }); await expect(readdir(root)).resolves.toEqual([]); }); it("tracks npm run-script dependencies instead of bypassing the graph", () => { expect(validatePackageScriptGraph({ root: "npm run-script missing" }, "root")) .toContain("package script missing: root -> missing"); }); // A removal fixture runs against a pruned contract on purpose, so the // canonical counts do not describe it. Asserting them there failed the // fixture for the reduction it exists to demonstrate. it.skipIf(isReducedCiContractRun())("accepts only the canonical exact-count authority and rejects orphan retention", async () => { const canonical = await loadCiGateContract(process.cwd()); // Template merge. The template added one gate and one command to the // product's 26/81; the artifact set is the product's 126 plus the // template's 2. Task 11 added TECH_LOG_STUDIO_ASSETS's manual // accessibility evidence, bringing the total to 129. // Final fix wave item 1 added `check-tech-log-contract` to FE-GATE-010. expect(canonical.gates).toHaveLength(27); expect(canonical.commands).toHaveLength(83); expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(95); expect(canonical.artifacts).toHaveLength(129); expect(canonical.stages).toHaveLength(5); expect(canonical.retention.classes).toHaveLength(5); const orphan = JSON.parse(JSON.stringify(canonical)) as Record; orphan.retention.classes.push({ id: "unused", policy: "never referenced" }); expect(() => parseCiGateContract(orphan)).toThrow(/five canonical retention|orphan retention/u); }); it.skipIf(isReducedCiContractRun())("rejects the retired validate-candidate-archive grammar", async () => { const canonical = JSON.parse( JSON.stringify(await loadCiGateContract(process.cwd())), ) as Record; canonical.jobs.find((job: Record) => job.id === "vulnerability_provider") .steps.splice(4, 0, { kind: "validate-candidate-archive", archivePath: ".release/candidate/release-candidate.tar.gz", }); expect(() => parseCiGateContract(canonical)).toThrow( /invalid discriminator|forbidden|canonical job step sequence/iu, ); }); it("publishes a generated workflow as exactly 0644 under a restrictive umask", async () => { const root = await mkdtemp(path.join(tmpdir(), "ci-workflow-mode-")); roots.push(root); await mkdir(path.join(root, ".gitea/workflows"), { recursive: true }); const previous = process.umask(0o777); try { const contract = await loadCiGateContract(process.cwd()); await generateCiWorkflow({ root, contract, check: false }); } finally { process.umask(previous); } const target = path.join(root, ".gitea/workflows/quality-gates.yml"); const metadata = await lstat(target); expect(metadata.mode & 0o777).toBe(0o644); expect((await readFile(target, "utf8")).startsWith("# GENERATED FILE")).toBe(true); }); }); async function waitForChildResult( child: ReturnType, ): Promise> { return await new Promise((resolve, reject) => { child.once("error", reject); child.once("close", (code, signal) => resolve({ code, signal })); }); } async function within(operation: Promise, timeoutMs: number, label: string): Promise { let timer: NodeJS.Timeout | undefined; try { return await Promise.race([ operation, new Promise((_resolve, reject) => { timer = setTimeout(() => reject(new Error(`${label} timed out`)), timeoutMs); }), ]); } finally { if (timer) clearTimeout(timer); } }