import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; import { existsSync, readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join, resolve } from "node:path"; import { describe, expect, it } from "vitest"; import { buildSourceManifest, collectSourceFiles, packageSourceArchive, REVIEWED_THIRD_PARTY_FILE, type SourceManifest, } from "./package-source"; const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const REQUIRED_FILES = [ "src/shared/noise-core-wasm/peach_noise_core_bg.wasm", "fixtures/golden/peach-payload-v4.json", "fixtures/golden/sync-entry-envelope-v1.json", "crypto-core/Cargo.toml", "crypto-core/Cargo.lock", "crypto-core/src/lib.rs", "crypto-core/src/jni_bridge.rs", "crypto-core/.cargo/config.toml", "scripts/build-noise-core.mjs", "scripts/check-noise-core.mjs", "manifest.json", "manifest.firefox.json", "package.json", "bun.lock", "tsconfig.json", "vite.config.ts", ]; const EXCLUDE_PATTERNS = [ /node_modules\//, /dist-[^/]+\//, /\.zip$/, /crypto-core\/target\//, /crypto-core\/generated\//, /\.DS_Store$/, /__MACOSX\//, ]; function listArchive(archivePath: string): string[] { return execFileSync("unzip", ["-Z1", archivePath], { encoding: "utf8" }) .split("\n") .filter(Boolean); } describe("PB-26-212: source package — required files", () => { it("collects all security-critical files including crypto-core", () => { const files = collectSourceFiles(); for (const required of REQUIRED_FILES) { expect(files).toContain(required); } }); it("includes crypto-core/Cargo.toml, Cargo.lock, and src/lib.rs (the core regression)", () => { const files = collectSourceFiles(); expect(files).toContain("crypto-core/Cargo.toml"); expect(files).toContain("crypto-core/Cargo.lock"); expect(files).toContain("crypto-core/src/lib.rs"); }); it("excludes forbidden directories and files", () => { const files = collectSourceFiles(); for (const file of files) { if (file === REVIEWED_THIRD_PARTY_FILE) continue; for (const pattern of EXCLUDE_PATTERNS) { expect(pattern.test(file)).toBe(false); } } }); it("collects files sorted and without duplicates", () => { const files = collectSourceFiles(); const sorted = [...files].sort(); expect(files).toEqual(sorted); expect(new Set(files).size).toBe(files.length); }); }); describe("PB-26-212: SOURCE-MANIFEST.json", () => { it("has correct structure with version, noise WASM SHA-256, toolchain, and file hashes", () => { const files = collectSourceFiles(); const manifest = buildSourceManifest(files, "0.0.0-test"); expect(manifest.peachVersion).toBe("0.0.0-test"); expect(manifest.noiseWasmSha256).toMatch(/^[0-9a-f]{64}$/); expect(manifest.toolchain).toBeDefined(); expect(manifest.toolchain.bun).toBeDefined(); expect(manifest.toolchain.node).toBeDefined(); expect(manifest.toolchain.rust).toBeDefined(); expect(manifest.files).toHaveLength(files.length); for (const entry of manifest.files) { expect(entry.path).toBeDefined(); expect(entry.sha256).toMatch(/^[0-9a-f]{64}$/); } }); it("noiseWasmSha256 matches the actual shipped WASM", () => { const wasmPath = join( ROOT, "src/shared/noise-core-wasm/peach_noise_core_bg.wasm", ); const expected = createHash("sha256") .update(readFileSync(wasmPath)) .digest("hex"); const files = collectSourceFiles(); const manifest = buildSourceManifest(files, "0.0.0-test"); expect(manifest.noiseWasmSha256).toBe(expected); }); it("file hashes match actual file contents", () => { const files = collectSourceFiles(); const manifest = buildSourceManifest(files, "0.0.0-test"); for (const entry of manifest.files) { const actual = createHash("sha256") .update(readFileSync(join(ROOT, entry.path))) .digest("hex"); expect(entry.sha256).toBe(actual); } }); }); describe("PB-26-212: source archive contents", () => { it("creates an archive containing every required file exactly once", () => { const archivePath = packageSourceArchive("0.0.0-test"); expect(existsSync(archivePath)).toBe(true); const contents = listArchive(archivePath); for (const required of REQUIRED_FILES) { const matches = contents.filter((c) => c === required); expect(matches).toHaveLength(1); } expect(contents).toContain("SOURCE-MANIFEST.json"); }); it("excludes forbidden paths from the archive", () => { const archivePath = packageSourceArchive("0.0.0-test"); const contents = listArchive(archivePath); // PB-26-913: the reviewed pdf.js worker is the single deliberate // third-party file, allowed through the node_modules exclusion. for (const file of contents) { if (file === REVIEWED_THIRD_PARTY_FILE) continue; for (const pattern of EXCLUDE_PATTERNS) { expect(pattern.test(file)).toBe(false); } } }); it("archive SOURCE-MANIFEST.json contains the noise WASM SHA-256 and version", () => { const archivePath = packageSourceArchive("0.0.0-test"); const manifestJson = execFileSync( "unzip", ["-p", archivePath, "SOURCE-MANIFEST.json"], { encoding: "utf8" }, ); const manifest: SourceManifest = JSON.parse(manifestJson); expect(manifest.peachVersion).toBe("0.0.0-test"); expect(manifest.noiseWasmSha256).toMatch(/^[0-9a-f]{64}$/); expect(Array.isArray(manifest.files)).toBe(true); expect(manifest.files.length).toBeGreaterThan(0); }); it("archive contains the same crypto-core source as the repo (rebuild provenance)", () => { const archivePath = packageSourceArchive("0.0.0-test"); const archiveLibRs = execFileSync( "unzip", ["-p", archivePath, "crypto-core/src/lib.rs"], { encoding: "utf8" }, ); const repoLibRs = readFileSync( join(ROOT, "crypto-core/src/lib.rs"), "utf8", ); expect(archiveLibRs).toBe(repoLibRs); const archiveBuildScript = execFileSync( "unzip", ["-p", archivePath, "scripts/build-noise-core.mjs"], { encoding: "utf8" }, ); const repoBuildScript = readFileSync( join(ROOT, "scripts/build-noise-core.mjs"), "utf8", ); expect(archiveBuildScript).toBe(repoBuildScript); const manifestJson = execFileSync( "unzip", ["-p", archivePath, "SOURCE-MANIFEST.json"], { encoding: "utf8" }, ); const manifest: SourceManifest = JSON.parse(manifestJson); const wasmPath = join( ROOT, "src/shared/noise-core-wasm/peach_noise_core_bg.wasm", ); const expectedWasmHash = createHash("sha256") .update(readFileSync(wasmPath)) .digest("hex"); expect(manifest.noiseWasmSha256).toBe(expectedWasmHash); }); }); describe("PB-26-212: reproducibility", () => { it("produces byte-identical archives from the same baseline", () => { const archive1 = packageSourceArchive("0.0.0-repro"); const hash1 = createHash("sha256") .update(readFileSync(archive1)) .digest("hex"); const archive2 = packageSourceArchive("0.0.0-repro"); const hash2 = createHash("sha256") .update(readFileSync(archive2)) .digest("hex"); expect(hash1).toBe(hash2); }); }); describe("PB-26-212: all entry points use the shared packager", () => { it("package.json package:source calls scripts/package-source.ts", () => { const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")); expect(pkg.scripts["package:source"]).toContain("package-source.ts"); }); it("package.json package:source does not contain a duplicate file list", () => { const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")); const script = pkg.scripts["package:source"]; expect(script).not.toContain("manifest.json manifest.firefox.json"); expect(script).not.toMatch(/^zip\s/); }); it("release.ts imports and calls packageSourceArchive", () => { const source = readFileSync(join(ROOT, "scripts/release.ts"), "utf8"); expect(source).toContain("packageSourceArchive"); }); it("release-firefox.ts imports and calls packageSourceArchive", () => { const source = readFileSync( join(ROOT, "scripts/release-firefox.ts"), "utf8", ); expect(source).toContain("packageSourceArchive"); }); it("release-lib.ts does not contain a duplicate zip file list", () => { const source = readFileSync(join(ROOT, "scripts/release-lib.ts"), "utf8"); expect(source).not.toContain("packageFirefoxSourceZip"); }); });