200 lines
7.1 KiB
TypeScript
200 lines
7.1 KiB
TypeScript
// @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 { CPP_LOCK, verifyCppPack } from "./cpp-engine-pack.mjs";
|
|
|
|
const temporaryDirectories: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
temporaryDirectories
|
|
.splice(0)
|
|
.map((directory) => rm(directory, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
describe("C++ engine-pack gate", () => {
|
|
it("verifies the installed exact Emscripten/libc++ pack and bridge sources", async () => {
|
|
await expect(
|
|
verifyCppPack(path.resolve("public", "engines", "cpp")),
|
|
).resolves.toEqual(
|
|
expect.objectContaining({
|
|
engineVersion: "Emscripten 6.0.4 libc++ std::wregex",
|
|
bridge: expect.objectContaining({
|
|
abiVersion: 1,
|
|
dynamicExecutionDisabled: true,
|
|
replacement: {
|
|
semantics: "libc++ match_results::format default grammar",
|
|
implementation:
|
|
"bounded streaming expansion without materializing an unbounded native format result",
|
|
paritySelfTest:
|
|
"load-time fixtures compare the bounded expander with libc++ match_results::format",
|
|
},
|
|
}),
|
|
toolchain: expect.objectContaining({
|
|
version: "6.0.4",
|
|
commitSha1: "fe5be6afdff43ad58860d821fcc8572a23f92d19",
|
|
emsdkCommitSha1: "224ec5f9f2f72f09f9ce0e26d66bae7dbd8b692f",
|
|
releaseCommitSha1: "b23272fac5d05617cd36dc451356dca0f79bf22d",
|
|
llvmVersion: "24.0.0git",
|
|
llvmCommitSha1: "c050c487e9edb97ef44f53cb29fe1d8bcddb8f76",
|
|
}),
|
|
licensing: {
|
|
sourceManifest: "SOURCE-MANIFEST.json",
|
|
linkedComponentCount: 6,
|
|
auditedExclusions: ["libunwind"],
|
|
independentlyPinnedLegalFiles: 6,
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("pins exception-enabled compilation and its exact traced native component set", async () => {
|
|
expect(CPP_LOCK.emscriptenVersion).toBe("6.0.4");
|
|
expect(CPP_LOCK.linkedArchives).toEqual([
|
|
"libc++.a",
|
|
"libc++abi.a",
|
|
"libc.a",
|
|
"libclang_rt.builtins.a",
|
|
"libembind-rtti.a",
|
|
"libemmalloc.a",
|
|
"libnoexit.a",
|
|
"libstubs.a",
|
|
]);
|
|
const metadata = JSON.parse(
|
|
await readFile(
|
|
path.resolve("public", "engines", "cpp", "engine-metadata.json"),
|
|
"utf8",
|
|
),
|
|
) as { toolchain: { command: string } };
|
|
expect(metadata.toolchain.command).toContain("-fexceptions");
|
|
expect(metadata.toolchain.command).toContain("-sMALLOC=emmalloc");
|
|
expect(metadata.toolchain.command).toContain("-Wl,--trace");
|
|
|
|
const sourceManifest = JSON.parse(
|
|
await readFile(
|
|
path.resolve("public", "engines", "cpp", "SOURCE-MANIFEST.json"),
|
|
"utf8",
|
|
),
|
|
) as {
|
|
buildSelection: { exactLinkedArchives: string[] };
|
|
sourceEvidence: { sourcePath: string; sourceSha256: string }[];
|
|
linkedComponents: {
|
|
id: string;
|
|
linked: boolean;
|
|
legalFiles: string[];
|
|
}[];
|
|
};
|
|
expect(sourceManifest.buildSelection.exactLinkedArchives).toEqual(
|
|
CPP_LOCK.linkedArchives,
|
|
);
|
|
expect(sourceManifest.sourceEvidence).toContainEqual({
|
|
sourcePath: "system/lib/emmalloc.c",
|
|
sourceSha256:
|
|
"00eb577cbef51a3359c309f45a7c99a814b8c6151ab75c9a3c78a56f0169146b",
|
|
});
|
|
expect(
|
|
sourceManifest.linkedComponents
|
|
.filter(({ linked }) => linked)
|
|
.map(({ id }) => id),
|
|
).toEqual([
|
|
"emscripten-runtime",
|
|
"musl",
|
|
"emmalloc",
|
|
"compiler-rt",
|
|
"libc++",
|
|
"libc++abi",
|
|
]);
|
|
expect(
|
|
sourceManifest.linkedComponents.find(({ id }) => id === "libunwind"),
|
|
).toEqual(
|
|
expect.objectContaining({
|
|
linked: false,
|
|
legalFiles: ["LICENSE-libunwind.txt"],
|
|
}),
|
|
);
|
|
|
|
const module = await readFile(
|
|
path.resolve("public", "engines", "cpp", "cpp-regex.mjs"),
|
|
"utf8",
|
|
);
|
|
expect(module).not.toContain("new Function");
|
|
expect(module).not.toMatch(/\beval\s*\(/u);
|
|
});
|
|
|
|
it("rejects substituted legal text even when self-reported checksums are regenerated", async () => {
|
|
const directory = await mkdtemp(path.join(tmpdir(), "regex-cpp-pack-"));
|
|
temporaryDirectories.push(directory);
|
|
const pack = path.join(directory, "cpp");
|
|
await cp(path.resolve("public", "engines", "cpp"), pack, {
|
|
recursive: true,
|
|
});
|
|
const replacement = Buffer.from("substituted legal text\n");
|
|
await writeFile(path.join(pack, "LICENSE-Emscripten.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 === "LICENSE-Emscripten.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(verifyCppPack(pack)).rejects.toThrow(
|
|
/metadata drifted from its pinned sources/u,
|
|
);
|
|
});
|
|
|
|
it("rejects a forged source inventory even when self-reported checksums are regenerated", async () => {
|
|
const directory = await mkdtemp(path.join(tmpdir(), "regex-cpp-pack-"));
|
|
temporaryDirectories.push(directory);
|
|
const pack = path.join(directory, "cpp");
|
|
await cp(path.resolve("public", "engines", "cpp"), pack, {
|
|
recursive: true,
|
|
});
|
|
const sourceManifestFile = path.join(pack, "SOURCE-MANIFEST.json");
|
|
const sourceManifest = JSON.parse(
|
|
await readFile(sourceManifestFile, "utf8"),
|
|
) as {
|
|
linkedComponents: { id: string; linked: boolean }[];
|
|
};
|
|
const libunwind = sourceManifest.linkedComponents.find(
|
|
({ id }) => id === "libunwind",
|
|
);
|
|
expect(libunwind).toBeDefined();
|
|
if (!libunwind) return;
|
|
libunwind.linked = true;
|
|
const replacement = Buffer.from(
|
|
`${JSON.stringify(sourceManifest, null, 2)}\n`,
|
|
);
|
|
await writeFile(sourceManifestFile, 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 === "SOURCE-MANIFEST.json",
|
|
);
|
|
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(verifyCppPack(pack)).rejects.toThrow(
|
|
/metadata drifted from its pinned sources/u,
|
|
);
|
|
});
|
|
});
|