import { createHash } from "node:crypto"; import { spawnSync } from "node:child_process"; import { chmod, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { describe, expect, it } from "vitest"; import { PROMOTED_FILE_NAMES } from "../../scripts/contracts/promotion-artifacts.ts"; import { publishPrivatePromotionStaging } from "../../scripts/lib/promotion-stager.ts"; import { verifyExactPromotionBundle } from "../../scripts/lib/exact-promotion-bundle.ts"; import { PROCESS_HEAVY_TIMEOUT_MS, privatePromotionFiles, syntheticSignedPromotionBundle, } from "./security-followup-fixture.ts"; describe("security private promotion staging contracts", () => { it("forces exact private staging modes in an isolated child with umask 077", async () => { const root = await mkdtemp(path.join(tmpdir(), "promotion-umask-")); try { const stagerUrl = pathToFileURL( path.join(process.cwd(), "scripts/lib/promotion-stager.ts"), ).href; const contractsUrl = pathToFileURL( path.join(process.cwd(), "scripts/contracts/promotion-artifacts.ts"), ).href; const childPath = path.join(root, "umask-child.mjs"); const resultPath = path.join(root, "result.json"); await writeFile(resultPath, "{}\n", { mode: 0o600 }); await writeFile( childPath, [ `import { lstat, writeFile } from "node:fs/promises";`, `import path from "node:path";`, `import { createHash } from "node:crypto";`, `import { cleanupFinalizedPromotion, publishPrivatePromotionStaging } from ${JSON.stringify(stagerUrl)};`, `import { PROMOTED_FILE_NAMES } from ${JSON.stringify(contractsUrl)};`, `process.umask(Number.parseInt(process.argv[2], 8));`, `const runnerTempRoot = process.argv[3];`, `const files = PROMOTED_FILE_NAMES.map((name) => { const bytes = Buffer.from(name); return { name, bytes, sha256: createHash("sha256").update(bytes).digest("hex") }; });`, `const finalized = await publishPrivatePromotionStaging(runnerTempRoot, { id: "umask", attempt: 1 }, files, () => Buffer.alloc(16, 1));`, `const directoryMode = (await lstat(finalized.stagingRoot)).mode & 0o777;`, `const fileModes = await Promise.all(PROMOTED_FILE_NAMES.map(async (name) => (await lstat(path.join(finalized.stagingRoot, name))).mode & 0o777));`, `await cleanupFinalizedPromotion({ runnerTempRoot, stagingRoot: finalized.stagingRoot, cleanupToken: finalized.cleanupToken, runnerTempIdentity: finalized.runnerTempIdentity, stagingIdentity: finalized.stagingIdentity });`, `await writeFile(process.argv[4], JSON.stringify({ directoryMode, fileModes }));`, ].join("\n"), ); const child = spawnSync(process.execPath, [childPath, "077", root, resultPath], { cwd: root, encoding: "utf8", timeout: 30_000, }); expect(child.status, `${child.stdout}\n${child.stderr}`).toBe(0); expect(JSON.parse(await readFile(resultPath, "utf8"))).toEqual({ directoryMode: 0o700, fileModes: [0o400, 0o400, 0o400, 0o400, 0o400], }); } finally { await rm(root, { recursive: true, force: true }); } }); it("rejects a staged file unlinked and recreated after its original write", async () => { const root = await mkdtemp(path.join(tmpdir(), "promotion-seal-recreate-")); const files = privatePromotionFiles(); const token = `promotion-seal-1-${"11".repeat(16)}`; try { await expect( publishPrivatePromotionStaging( root, { id: "seal", attempt: 1 }, files, () => Buffer.alloc(16, 0x11), undefined, async (name) => { if (name !== PROMOTED_FILE_NAMES.at(-1)) return; const first = path.join(root, token, PROMOTED_FILE_NAMES[0]); await rm(first); await writeFile(first, "replacement bytes\n", { mode: 0o400 }); }, ), ).rejects.toThrow(/staged.*digest|inode|seal/u); } finally { await rm(root, { recursive: true, force: true }); } }); it("rejects staged mode drift before returning the upload root", async () => { const root = await mkdtemp(path.join(tmpdir(), "promotion-seal-mode-")); const files = privatePromotionFiles(); const token = `promotion-seal-1-${"12".repeat(16)}`; try { await expect( publishPrivatePromotionStaging( root, { id: "seal", attempt: 1 }, files, () => Buffer.alloc(16, 0x12), undefined, async (name) => { if (name === PROMOTED_FILE_NAMES.at(-1)) { await chmod(path.join(root, token, PROMOTED_FILE_NAMES[0]), 0o600); } }, ), ).rejects.toThrow(/mode.*0400|staged.*mode|seal/u); } finally { await rm(root, { recursive: true, force: true }); } }); it("rejects staging leaf replacement between mkdir and descriptor open", async () => { const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-replace-")); const files = privatePromotionFiles(); const token = `promotion-preopen-1-${"13".repeat(16)}`; const displaced = path.join(root, `${token}-displaced`); const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]); try { await expect( publishPrivatePromotionStaging( root, { id: "preopen", attempt: 1 }, files, () => Buffer.alloc(16, 0x13), undefined, undefined, undefined, async (stagingRoot) => { await rename(stagingRoot, displaced); await mkdir(stagingRoot, { mode: 0o700 }); await writeFile(replacementCanary, "external replacement canary\n"); }, ), ).rejects.toThrow(/staging leaf.*changed|mkdir.*open|identity/u); await expect(readFile(replacementCanary, "utf8")).resolves.toBe( "external replacement canary\n", ); await expect(readdir(displaced)).resolves.toEqual([]); } finally { await rm(root, { recursive: true, force: true }); } }); it("does not scan a crowded parent to recover an unverified pre-open leaf", async () => { const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-bounded-")); const files = privatePromotionFiles(); const token = `promotion-preopen-bound-1-${"14".repeat(16)}`; const displaced = path.join(root, `${token}-displaced`); const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]); try { for (let offset = 0; offset < 4_097; offset += 128) { await Promise.all( Array.from( { length: Math.min(128, 4_097 - offset) }, (_, index) => mkdir( path.join( root, `noise-${String(offset + index).padStart(4, "0")}`, ), ), ), ); } let failure: unknown; try { await publishPrivatePromotionStaging( root, { id: "preopen-bound", attempt: 1 }, files, () => Buffer.alloc(16, 0x14), undefined, undefined, undefined, async (stagingRoot) => { await rename(stagingRoot, displaced); await mkdir(stagingRoot, { mode: 0o700 }); await writeFile(replacementCanary, "external replacement canary\n"); }, ); } catch (error) { failure = error; } expect(failure).toBeInstanceOf(Error); expect(failure).not.toBeInstanceOf(AggregateError); expect((failure as Error).message).toMatch(/staging leaf.*identity/i); await expect(readdir(displaced)).resolves.toEqual([]); await expect(readFile(replacementCanary, "utf8")).resolves.toBe( "external replacement canary\n", ); } finally { await rm(root, { recursive: true, force: true }); } }, 20_000); it("leaves a non-empty moved original untouched after pre-open mismatch", async () => { const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-nonempty-")); const files = privatePromotionFiles(); const token = `promotion-preopen-nonempty-1-${"15".repeat(16)}`; const displaced = path.join(root, `${token}-displaced`); const ownedResidual = path.join(displaced, "owned-residual"); const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]); try { let failure: unknown; try { await publishPrivatePromotionStaging( root, { id: "preopen-nonempty", attempt: 1 }, files, () => Buffer.alloc(16, 0x15), undefined, undefined, undefined, async (stagingRoot) => { await rename(stagingRoot, displaced); await writeFile(ownedResidual, "owned residual\n"); await mkdir(stagingRoot, { mode: 0o700 }); await writeFile(replacementCanary, "external replacement canary\n"); }, ); } catch (error) { failure = error; } expect(failure).toBeInstanceOf(Error); expect(failure).not.toBeInstanceOf(AggregateError); expect((failure as Error).message).toMatch(/staging leaf.*identity/i); await expect(readFile(ownedResidual, "utf8")).resolves.toBe( "owned residual\n", ); await expect(readFile(replacementCanary, "utf8")).resolves.toBe( "external replacement canary\n", ); } finally { await rm(root, { recursive: true, force: true }); } }); it("does not search outside the parent for a moved unverified original", async () => { const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-missing-")); const outside = await mkdtemp(path.join(tmpdir(), "promotion-preopen-moved-")); const files = privatePromotionFiles(); const token = `promotion-preopen-missing-1-${"16".repeat(16)}`; const displaced = path.join(outside, token); const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]); try { let failure: unknown; try { await publishPrivatePromotionStaging( root, { id: "preopen-missing", attempt: 1 }, files, () => Buffer.alloc(16, 0x16), undefined, undefined, undefined, async (stagingRoot) => { await rename(stagingRoot, displaced); await mkdir(stagingRoot, { mode: 0o700 }); await writeFile(replacementCanary, "external replacement canary\n"); }, ); } catch (error) { failure = error; } expect(failure).toBeInstanceOf(Error); expect(failure).not.toBeInstanceOf(AggregateError); expect((failure as Error).message).toMatch(/staging leaf.*identity/i); await expect(lstat(displaced)).resolves.toEqual( expect.objectContaining({ dev: expect.any(Number), ino: expect.any(Number) }), ); await expect(readFile(replacementCanary, "utf8")).resolves.toBe( "external replacement canary\n", ); } finally { await rm(root, { recursive: true, force: true }); await rm(outside, { recursive: true, force: true }); } }); it("rejects a fresh signed exact-five bundle replayed under a different expected run", async () => { const fixture = syntheticSignedPromotionBundle(); await expect( verifyExactPromotionBundle(fixture.files, { ...fixture.verification, expected: { ...fixture.verification.expected, run: { id: "different-run", attempt: 1 }, }, }), ).rejects.toThrow(/external expected run.*mismatch|expected promotion run/u); }); it("requires every external expected identity variable at the exact promotion CLI", async () => { const fixture = syntheticSignedPromotionBundle(); const root = await mkdtemp(path.join(tmpdir(), "promotion-replay-cli-")); const bundleRoot = path.join(root, "bundle"); try { await mkdir(bundleRoot); for (const [name, bytes] of Object.entries(fixture.files)) { await writeFile(path.join(bundleRoot, name), bytes); } await writeFile(path.join(root, "vulnerability.pem"), fixture.vulnerabilityPem); await writeFile(path.join(root, "provenance.pem"), fixture.provenancePem); const cliPath = path.join(process.cwd(), "scripts/verify-exact-promotion-bundle.ts"); const baseEnvironment: NodeJS.ProcessEnv = { ...process.env, PROMOTION_BUNDLE_ROOT: bundleRoot, VULNERABILITY_PUBLIC_KEY_PATH: "vulnerability.pem", VULNERABILITY_KEY_ID: "synthetic-vulnerability", PROVENANCE_PUBLIC_KEY_PATH: "provenance.pem", PROVENANCE_KEY_ID: "synthetic-provenance", EXPECTED_PROMOTION_RUN_ID: fixture.verification.expected.run.id, EXPECTED_PROMOTION_RUN_ATTEMPT: String( fixture.verification.expected.run.attempt, ), EXPECTED_PROMOTION_SOURCE_REVISION: fixture.verification.expected.sourceRevision, EXPECTED_PROMOTION_ARCHIVE_SHA256: fixture.verification.expected.archiveSha256, }; const requiredExpected = [ "EXPECTED_PROMOTION_RUN_ID", "EXPECTED_PROMOTION_RUN_ATTEMPT", "EXPECTED_PROMOTION_SOURCE_REVISION", "EXPECTED_PROMOTION_ARCHIVE_SHA256", ] as const; for (const missing of requiredExpected) { const environment = { ...baseEnvironment }; delete environment[missing]; const result = spawnSync(process.execPath, [cliPath], { cwd: root, encoding: "utf8", env: environment, }); expect(result.status, missing).not.toBe(0); expect(result.stderr, missing).toContain( `exact promotion verification environment is missing ${missing}`, ); } for (const [name, value, diagnostic] of [ ["EXPECTED_PROMOTION_RUN_ID", "different-run", /external expected run.*mismatch/u], ["EXPECTED_PROMOTION_RUN_ATTEMPT", "2", /external expected run.*mismatch/u], ["EXPECTED_PROMOTION_SOURCE_REVISION", "f".repeat(40), /external expected source revision.*mismatch/u], ["EXPECTED_PROMOTION_ARCHIVE_SHA256", "0".repeat(64), /external expected archive digest.*mismatch/u], ] as const) { const result = spawnSync(process.execPath, [cliPath], { cwd: root, encoding: "utf8", env: { ...baseEnvironment, [name]: value }, }); expect(result.status, name).not.toBe(0); expect(result.stderr, name).toMatch(diagnostic); } } finally { await rm(root, { recursive: true, force: true }); } }, PROCESS_HEAVY_TIMEOUT_MS); });