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