// Copyright 2026 Richard Andresik. SPDX-License-Identifier: Apache-2.0 import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import { spawnSync } from "node:child_process"; import test from "node:test"; import { paired } from "./endpoint.mjs"; const root = fileURLToPath(new URL("../", import.meta.url)); const read = name => readFile(new URL("../" + name, import.meta.url), "utf8"); test("container recipe fixes its inputs, copies only public files and runs without privilege", async () => { const dockerfile = await read("Dockerfile"); const lock = JSON.parse(await read("container-lock.json")); const digest = lock.base.split("@sha256:")[1]; assert.equal(digest.length, 64); assert.ok([...digest].every(char => "0123456789abcdef".includes(char))); assert.ok(dockerfile.includes("FROM " + lock.base + "\n")); assert.ok(dockerfile.includes("USER 1000:1000\n")); assert.ok(dockerfile.includes('CMD ["node", "tools/lab.mjs"]')); assert.equal(dockerfile.includes("EXPOSE"), false); assert.equal(dockerfile.includes("ADD "), false); const copies = dockerfile.split("\n").filter(line => line.startsWith("COPY ")); const copied = copies.flatMap(line => JSON.parse(line.slice(5)).slice(0, -1)); assert.deepEqual(copied.sort(), [...lock.files].sort()); assert.equal(new Set(copied).size, copied.length); for (const name of copied) { assert.equal(name.includes(".."), false); assert.equal(name.includes("*"), false); assert.equal(name.endsWith(".PFL.md"), false); assert.ok(!name.startsWith("/") && !name.endsWith("/")); } const ignore = (await read(".dockerignore")).split("\n").filter(line => line && !line.startsWith("#")); assert.equal(ignore.shift(), "**"); assert.deepEqual(ignore.filter(line => !["!src/", "!test/", "!tools/"].includes(line)).sort(), lock.files.map(name => "!" + name).sort()); assert.ok(lock.debianSnapshot.endsWith("Z")); assert.ok(dockerfile.includes(lock.debianSnapshot)); for (const name of ["git", "openssl", "python3", "jq", "ripgrep", "file", "coreutils", "strace", "tini", "ca-certificates"]) assert.ok(lock.packages.includes(name)); assert.ok(dockerfile.includes(lock.debianRelease + "-security main")); assert.equal(lock.npm.url, "https://registry.npmjs.org/npm/-/npm-" + lock.npm.version + ".tgz"); assert.equal(Buffer.from(lock.npm.integrity.slice(7), "base64").length, 64); assert.ok(lock.npm.integrity.startsWith("sha512-")); assert.ok(dockerfile.includes("lock.npm.integrity")); assert.ok(dockerfile.includes("--ignore-scripts --no-audit --no-fund")); }); test("fuzz cases repeat from a seed, reject changed messages and keep both peers progressing", async () => { const { fuzz } = await import("../tools/fuzz.mjs"); const first = await fuzz({ seed: 1387, iterations: 12 }); const second = await fuzz({ seed: 1387, iterations: 12 }); assert.deepEqual(first, second); assert.equal(first.iterations, 12); assert.equal(first.rejected, 60); assert.equal(first.roundTrips, 48); assert.equal(first.planSha256.length, 64); assert.notEqual((await fuzz({ seed: 1388, iterations: 12 })).planSha256, first.planSha256); }); test("fuzzing detects lost authentication instead of counting accepted attacks as success", async () => { const { fuzz } = await import("../tools/fuzz.mjs"); let calls = 0; const brokenPair = async () => { const peers = await paired(); const real = peers.secondary.session; peers.secondary.session = { ...real, acceptPublicMessage: async () => { calls++; return true; } }; return peers; }; await assert.rejects(fuzz({ seed: 42, iterations: 1, makePair: brokenPair }), /seed=42 iteration=0/); assert.ok(calls > 0); }); test("invalid fuzz bounds are refused before generating keys or running work", async () => { const { fuzz } = await import("../tools/fuzz.mjs"); for (const options of [{ seed: 0 }, { seed: -1 }, { seed: 1.5 }, { seed: 2 ** 32 }, { iterations: 0 }, { iterations: 100001 }, { iterations: 2.5 }]) { await assert.rejects(fuzz({ ...options, makePair: () => { assert.fail("key generation must not start"); } })); } const cli = spawnSync(process.execPath, ["tools/fuzz.mjs", "--iterations", "infinity"], { cwd: root, encoding: "utf8" }); assert.notEqual(cli.status, 0); assert.ok(cli.stderr.includes("iterations")); });