// @vitest-environment node import { createHash } from "node:crypto"; import { cp, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { writeEngineChecksums } from "./checksummed-engine-pack.mjs"; import { DOTNET_LOCK, verifyDotNetPack } from "./dotnet-engine-pack.mjs"; const temporaryDirectories: string[] = []; afterEach(async () => { await Promise.all( temporaryDirectories .splice(0) .map((directory) => rm(directory, { recursive: true, force: true })), ); }); describe(".NET engine-pack gate", () => { it("verifies the installed exact runtime, bridge sources and closed checksums", async () => { await expect( verifyDotNetPack(path.resolve("public", "engines", "dotnet")), ).resolves.toEqual( expect.objectContaining({ engineVersion: ".NET 10.0.10 System.Text.RegularExpressions", source: expect.objectContaining({ runtimeVersion: "10.0.10", runtimeCommit: "f7d90799ce", }), toolchain: expect.objectContaining({ sdkVersion: "10.0.302", sdkCommit: "35b593bebf", workload: "wasm-tools", }), }), ); }); it("pins the compiler/runtime identity used by the checked-in pack", () => { expect(DOTNET_LOCK).toEqual( expect.objectContaining({ sdkVersion: "10.0.302", runtimeVersion: "10.0.10", bridgeAbi: 1, }), ); }); it("rejects modified framework assets", async () => { const directory = await mkdtemp(path.join(tmpdir(), "regex-dotnet-pack-")); temporaryDirectories.push(directory); const pack = path.join(directory, "dotnet"); await cp(path.resolve("public", "engines", "dotnet"), pack, { recursive: true, }); const metadata = JSON.parse( await readFile(path.join(pack, "engine-metadata.json"), "utf8"), ) as { files: readonly { path: string }[]; }; const runtime = metadata.files.find(({ path: name }) => /^_framework\/System\.Text\.RegularExpressions\..+\.wasm$/u.test(name), ); expect(runtime).toBeDefined(); await writeFile(path.join(pack, runtime?.path ?? ""), "modified"); await expect(verifyDotNetPack(pack)).rejects.toThrow(/SHA-256 mismatch/u); }); it("rejects substituted notices even when self-reported checksums are regenerated", async () => { const directory = await mkdtemp(path.join(tmpdir(), "regex-dotnet-pack-")); temporaryDirectories.push(directory); const pack = path.join(directory, "dotnet"); await cp(path.resolve("public", "engines", "dotnet"), pack, { recursive: true, }); const replacement = Buffer.from("substituted notices\n"); await writeFile(path.join(pack, "THIRD-PARTY-NOTICES.txt"), replacement); const metadataFile = path.join(pack, "engine-metadata.json"); const metadata = JSON.parse(await readFile(metadataFile, "utf8")) as { files: { path: string; sha256: string; bytes: number }[]; }; const record = metadata.files.find( ({ path: name }) => name === "THIRD-PARTY-NOTICES.txt", ); expect(record).toBeDefined(); if (!record) return; record.sha256 = createHash("sha256").update(replacement).digest("hex"); record.bytes = replacement.byteLength; await writeFile(metadataFile, `${JSON.stringify(metadata, null, 2)}\n`); await writeEngineChecksums(pack); await expect(verifyDotNetPack(pack)).rejects.toThrow( /pinned runtime contract/u, ); }); it("rejects a build-host path hidden in WebAssembly after checksums are regenerated", async () => { const directory = await mkdtemp(path.join(tmpdir(), "regex-dotnet-pack-")); temporaryDirectories.push(directory); const pack = path.join(directory, "dotnet"); await cp(path.resolve("public", "engines", "dotnet"), pack, { recursive: true, }); const metadataFile = path.join(pack, "engine-metadata.json"); const metadata = JSON.parse(await readFile(metadataFile, "utf8")) as { files: { path: string; sha256: string; bytes: number }[]; }; const record = metadata.files.find(({ path: name }) => /^_framework\/RegexTools\.DotNet\..+\.wasm$/u.test(name), ); expect(record).toBeDefined(); if (!record) return; const assetFile = path.join(pack, record.path); const replacement = Buffer.concat([ await readFile(assetFile), Buffer.from("/mnt/private/regex-tools/obj/linked/RegexTools.DotNet.pdb"), ]); await writeFile(assetFile, replacement); record.sha256 = createHash("sha256").update(replacement).digest("hex"); record.bytes = replacement.byteLength; await writeFile(metadataFile, `${JSON.stringify(metadata, null, 2)}\n`); await writeEngineChecksums(pack); await expect(verifyDotNetPack(pack)).rejects.toThrow( /Local absolute path/u, ); }); });