tools/inspect.mjs
Alle Dateien · Rohdatei öffnen
// 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));
}