IcyZip

tools/inspect.mjs

All files · Open raw file

3456 bytes · SHA-256: d7f4294dd04d87ab5fb297c973a8edc1e226483c7f4904ab03875f3485d83332

// Copyright 2026 Richard Andresik. SPDX-License-Identifier: Apache-2.0
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";

const root = fileURLToPath(new URL("../", import.meta.url));
const hash = body => createHash("sha256").update(body).digest("hex");
export function inspect() {
    const lock = JSON.parse(fs.readFileSync(path.join(root, "container-lock.json")));
    const files = lock.files.map(name => {
        const filename = path.join(root, name);
        assert.equal(fs.realpathSync(filename), filename, "Public source must not contain symlinks");
        const bytes = fs.readFileSync(filename);
        return { path: name, bytes: bytes.length, sha256: hash(bytes) };
    });
    const result = { version: lock.version, node: process.version, platform: process.platform,
        architecture: process.arch, openssl: process.versions.openssl, uid: process.getuid?.(),
        sourceId: hash(files.map(file => file.sha256 + "  " + file.path + "\n").join("")), files: files.length,
        moduleSha256: files.find(file => file.path === "src/e2ee.js").sha256 };
    if (process.env.ICYZIP_LAB_CONTAINER === "1") {
        assert.equal(result.uid, 1000, "Review container must run as uid 1000");
        const inventory = "/opt/icyzip-inventory";
        const recorded = JSON.parse(fs.readFileSync(inventory + "/sources.json"));
        assert.deepEqual(files, recorded, "Container source differs from its build inventory");
        const sbom = JSON.parse(fs.readFileSync(inventory + "/SBOM.spdx.json"));
        assert.equal(sbom.spdxVersion, "SPDX-2.3");
        result.sbom = inventory + "/SBOM.spdx.json";
        result.licenses = inventory + "/LICENSES.json";
        result.packages = sbom.packages.length;
        result.tools = {};
        for (const [name, args] of Object.entries({ git: ["--version"], openssl: ["version"], python3: ["--version"],
            jq: ["--version"], rg: ["--version"], file: ["--version"], od: ["--version"], strace: ["--version"], npm: ["--version"] })) {
            const command = spawnSync(name, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
            assert.equal(command.status, 0, "Cannot inspect " + name + ": " + (command.error || command.stderr));
            const version = (command.stdout || command.stderr || "").trim().split("\n")[0];
            assert.ok(version, "Missing tool version for " + name);
            result.tools[name] = version;
        }
        assert.equal(result.tools.npm, lock.npm.version, "npm must match the pinned archive");
        const licenses = JSON.parse(fs.readFileSync(result.licenses));
        for (const name of [...lock.packages.map(item => "deb:" + item), "node", "icyzip-e2ee-review"])
            assert.ok(licenses[name]?.length > 0, "Missing license path for " + name);
        result.licenseMetadataGaps = Object.entries(licenses).filter(([, paths]) => !paths.length).map(([name]) => name);
        for (const paths of Object.values(licenses)) {
            for (const filename of paths) assert.ok(fs.statSync(filename).isFile(), "Missing license " + filename);
        }
    }
    return result;
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
    console.log(JSON.stringify(inspect(), null, 2));
}