// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // // The software bill of materials of the built image and the THIRD-PARTY-NOTICES generated from it // (plan item AT5.1): the SBOM reads what is installed in a pnpm store (a runtime dependency and not a // link to one, once per version), and the notices list every component of it, with each licence text // printed once however many packages share it. The committed THIRD-PARTY-NOTICES is checked against // the dependencies this template declares. import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { dirname, join } from "node:path" import { afterEach, describe, expect, it } from "vitest" import { dpkgComponents, npmComponents, sbom } from "../scripts/sbom.mjs" import { notices, unlisted } from "../scripts/third-party-notices.mjs" const APP_ROOT = new URL("..", import.meta.url).pathname const made: string[] = [] afterEach(() => { for (const dir of made.splice(0)) rmSync(dir, { recursive: true, force: true }) }) const MIT = "MIT License\n\nCopyright (c) 2020 Someone\n\nPermission is hereby granted, free of charge, ..." /** A pnpm virtual store: each entry holds its own package and links to the packages it depends on. */ function image(): string { const root = mkdtempSync(join(tmpdir(), "sbom-")) made.push(root) const put = (path: string, text: string) => { mkdirSync(dirname(join(root, path)), { recursive: true }) writeFileSync(join(root, path), text) } const store = "node_modules/.pnpm" put("package.json", JSON.stringify({ name: "demo-app", version: "1.2.3" })) put( `${store}/alpha@1.0.0/node_modules/alpha/package.json`, JSON.stringify({ name: "alpha", version: "1.0.0", license: "MIT" }), ) put(`${store}/alpha@1.0.0/node_modules/alpha/LICENSE`, MIT) put( `${store}/beta@2.0.0/node_modules/beta/package.json`, JSON.stringify({ name: "beta", version: "2.0.0", license: "MIT" }), ) put(`${store}/beta@2.0.0/node_modules/beta/LICENSE.md`, MIT) // the same version under two peer sets is one component put( `${store}/beta@2.0.0_react@19/node_modules/beta/package.json`, JSON.stringify({ name: "beta", version: "2.0.0", license: "MIT" }), ) put( `${store}/@scope+gamma@3.0.0/node_modules/@scope/gamma/package.json`, JSON.stringify({ name: "@scope/gamma", version: "3.0.0", license: { type: "ISC" }, repository: { url: "git+https://example.test/gamma.git" }, }), ) put( `${store}/@conusai-kit+core@0.1.0/node_modules/@conusai-kit/core/package.json`, JSON.stringify({ name: "@conusai-kit/core", version: "0.1.0", license: "LicenseRef-ConusAI-Template-1.0" }), ) put( `${store}/delta@4.0.0/node_modules/delta/package.json`, JSON.stringify({ name: "delta", version: "4.0.0", license: "(MIT OR Apache-2.0)" }), ) // alpha depends on beta: a link inside alpha's entry, which is not a second copy of beta symlinkSync(join(root, `${store}/beta@2.0.0/node_modules/beta`), join(root, `${store}/alpha@1.0.0/node_modules/beta`)) return root } describe("the SBOM of an image", () => { it("holds each installed package once, with its licence and the text of its licence file", () => { const components = npmComponents(image()) expect(components.map((component) => component["bom-ref"])).toEqual([ "pkg:npm/%40conusai-kit/core@0.1.0", "pkg:npm/%40scope/gamma@3.0.0", "pkg:npm/alpha@1.0.0", "pkg:npm/beta@2.0.0", "pkg:npm/delta@4.0.0", ]) const alpha = components.find((component) => component.name === "alpha") expect(alpha?.licenses[0]?.license).toMatchObject({ id: "MIT", text: { content: MIT } }) expect(components.find((component) => component.name === "gamma")).toMatchObject({ group: "@scope", licenses: [{ license: { id: "ISC" } }], externalReferences: [{ type: "vcs", url: "https://example.test/gamma" }], }) expect(components.find((component) => component.name === "delta")?.licenses).toEqual([ { expression: "(MIT OR Apache-2.0)" }, ]) }) it("marks the ConusAI packages, whose licence is LICENSE-TEMPLATE, and names their licence as a reference", () => { const core = npmComponents(image()).find((component) => component.name === "core") expect(core?.properties).toContainEqual({ name: "conusai:component", value: "true" }) expect(core?.licenses[0]?.license).toEqual({ name: "LicenseRef-ConusAI-Template-1.0" }) }) it("is a CycloneDX 1.5 document with the application, the Node.js runtime and the packages", () => { const document = sbom({ root: image(), dpkg: "/nonexistent", nodeVersion: "v24.1.0" }) expect(document).toMatchObject({ bomFormat: "CycloneDX", specVersion: "1.5", metadata: { component: { type: "application", name: "app", version: "0.0.0" } }, }) expect(document.components[0]).toMatchObject({ type: "platform", name: "node", version: "24.1.0" }) expect(document.components).toHaveLength(6) }) it("reads the operating system packages that are installed, and no others", () => { const dir = mkdtempSync(join(tmpdir(), "dpkg-")) made.push(dir) const status = join(dir, "status") writeFileSync( status, [ "Package: libc6\nStatus: install ok installed\nVersion: 2.41-1\nArchitecture: amd64", "Package: gone\nStatus: deinstall ok config-files\nVersion: 1.0", "Package: zlib1g\nStatus: install ok installed\nVersion: 1:1.3.dfsg-1", "", ].join("\n\n"), ) const components = dpkgComponents(status, dir) expect(components.map((component) => `${component.name} ${component.version}`)).toEqual([ "libc6 2.41-1", "zlib1g 1:1.3.dfsg-1", ]) }) }) describe("THIRD-PARTY-NOTICES generated from an SBOM", () => { const document = () => sbom({ root: image(), dpkg: "/nonexistent", name: "demo-app", version: "1.2.3", nodeVersion: "v24.1.0" }) it("lists every component, and prints a licence text shared by several packages once", () => { const text = notices(document()) expect(unlisted(document(), text)).toEqual([]) expect(text).toContain("@conusai-kit/core@0.1.0") expect(text).toContain("node@24.1.0") expect(text.split("Permission is hereby granted").length - 1).toBe(1) expect(text).toContain("alpha@1.0.0") expect(text).toContain("beta@2.0.0") expect(text).toContain("these packages ship no licence file; they declare ISC") }) it("is the same text every time it is generated from the same SBOM", () => { expect(notices(document())).toBe(notices(document())) }) it("is checked against the SBOM: a component it does not list is named", () => { const text = notices(document()) const more = document() more.components.push({ type: "library", "bom-ref": "pkg:npm/epsilon@5.0.0", name: "epsilon", version: "5.0.0", purl: "pkg:npm/epsilon@5.0.0", licenses: [], }) expect(unlisted(more, text)).toEqual(["epsilon@5.0.0"]) // a version that moved is drift by name, not a missing component const moved = document() moved.components = moved.components.map((component) => component.name === "alpha" ? { ...component, version: "1.0.1" } : component, ) expect(unlisted(moved, text)).toEqual(["alpha@1.0.1"]) expect(unlisted(moved, text, { byName: true })).toEqual([]) }) }) describe("the committed THIRD-PARTY-NOTICES", () => { const text = readFileSync(join(APP_ROOT, "THIRD-PARTY-NOTICES"), "utf8") it("says what it is generated from, and that it is not edited by hand", () => { expect(text).toContain("software bill of materials of the built image") expect(text).toContain("do not edit it by hand") }) it("lists every runtime dependency this template declares, by name", () => { const declared = new Set() for (const path of [ "package.json", "apps/web/package.json", "apps/worker/package.json", "config/package.json", "modules/package.json", ]) { const manifest = JSON.parse(readFileSync(join(APP_ROOT, path), "utf8")) as { dependencies?: Record } for (const name of Object.keys(manifest.dependencies ?? {})) if (!name.startsWith("@app/")) declared.add(name) } expect(declared.size).toBeGreaterThan(15) const missing = [...declared].filter((name) => !text.includes(` ${name}@`)) expect(missing).toEqual([]) }) })