IcyZip

test/container.test.mjs

All files · Open raw file

4547 bytes · SHA-256: 7ab564b72a31ec2277e2f7473e37ed0db26461f048a3f3ee2e1c112017489203

// 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"));
});