@cyclonedx/cdxgen
Version:
Creates CycloneDX Software Bill of Materials (SBOM) from source or container image
931 lines (857 loc) • 27.7 kB
JavaScript
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import esmock from "esmock";
import { assert, describe, it } from "poku";
import {
collectAuditTargets,
extractPurlTargetsFromBom,
isRequiredComponentScope,
normalizePackageName,
} from "./targets.js";
function makeBom(components, extra = {}) {
return {
bomFormat: "CycloneDX",
components,
specVersion: "1.6",
...extra,
};
}
function makeAllowlistInputBom(source) {
return {
bomJson: makeBom([
{
"bom-ref": "pkg:npm/%40acme/core@1.0.0",
name: "core",
purl: "pkg:npm/%40acme/core@1.0.0",
},
{
"bom-ref": "pkg:pypi/internal-tool@1.0.0",
name: "internal-tool",
purl: "pkg:pypi/internal-tool@1.0.0",
},
{
"bom-ref": "pkg:npm/left-pad@1.3.0",
name: "left-pad",
purl: "pkg:npm/left-pad@1.3.0",
},
]),
source,
};
}
function withTemporaryAllowlistFile(fileName, content, callback) {
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "cdx-audit-allowlist-"));
const allowlistFile = path.join(tmpDir, fileName);
writeFileSync(allowlistFile, content);
try {
// Let test failures propagate after cleanup so the original assertion error
// is preserved for the runner.
callback(allowlistFile);
} finally {
rmSync(tmpDir, { force: true, recursive: true });
}
}
describe("normalizePackageName()", () => {
it("normalizes Python-style package separators", () => {
assert.strictEqual(
normalizePackageName("My_Package.Name"),
"my-package-name",
);
});
});
describe("extractPurlTargetsFromBom()", () => {
it("extracts supported npm, pypi, and cargo purls", () => {
const bom = makeBom([
{
"bom-ref": "pkg:npm/left-pad@1.3.0",
name: "left-pad",
properties: [
{ name: "cdx:npm:trustedPublishing", value: "true" },
{ name: "cdx:npm:provenanceKeyId", value: "sigstore-key" },
],
purl: "pkg:npm/left-pad@1.3.0",
},
{
"bom-ref": "pkg:pypi/requests@2.32.3",
name: "requests",
purl: "pkg:pypi/requests@2.32.3",
},
{
"bom-ref": "pkg:cargo/serde@1.0.217",
name: "serde",
properties: [{ name: "cdx:cargo:dependencyKind", value: "dev" }],
purl: "pkg:cargo/serde@1.0.217",
},
{
"bom-ref": "pkg:gem/rails@8.0.0",
name: "rails",
purl: "pkg:gem/rails@8.0.0",
},
]);
const extracted = extractPurlTargetsFromBom(bom, "bom.json");
assert.strictEqual(extracted.targets.length, 3);
assert.strictEqual(extracted.skipped.length, 1);
assert.strictEqual(extracted.targets[0].type, "npm");
assert.strictEqual(
extracted.targets[0].properties[0].name,
"cdx:npm:trustedPublishing",
);
assert.strictEqual(
extracted.targets[0].properties[1].name,
"cdx:npm:provenanceKeyId",
);
assert.strictEqual(extracted.targets[1].type, "pypi");
assert.strictEqual(extracted.targets[2].type, "cargo");
assert.strictEqual(extracted.targets[2].developmentOnly, true);
assert.strictEqual(extracted.skipped[0].reason, "unsupported-ecosystem");
});
it("records invalid purls as skipped entries", () => {
const bom = makeBom([
{
"bom-ref": "bad-ref",
name: "broken",
purl: "not-a-purl",
},
]);
const extracted = extractPurlTargetsFromBom(bom, "broken.json");
assert.strictEqual(extracted.targets.length, 0);
assert.strictEqual(extracted.skipped.length, 1);
assert.strictEqual(extracted.skipped[0].reason, "invalid-purl");
});
});
describe("isRequiredComponentScope()", () => {
it("treats missing scope as required and excludes optional/excluded scopes", () => {
assert.strictEqual(isRequiredComponentScope(undefined), true);
assert.strictEqual(isRequiredComponentScope("required"), true);
assert.strictEqual(isRequiredComponentScope("optional"), false);
assert.strictEqual(isRequiredComponentScope("excluded"), false);
});
});
describe("collectAuditTargets()", () => {
it("deduplicates targets across multiple BOMs while preserving sources", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/left-pad@1.3.0",
name: "left-pad",
properties: [{ name: "cdx:npm:trustedPublishing", value: "true" }],
purl: "pkg:npm/left-pad@1.3.0",
},
]),
source: "one.json",
},
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/left-pad@1.3.0",
name: "left-pad",
properties: [{ name: "cdx:npm:publisher", value: "octo" }],
purl: "pkg:npm/left-pad@1.3.0",
},
{
"bom-ref": "pkg:pypi/requests@2.32.3",
name: "requests",
purl: "pkg:pypi/requests@2.32.3",
},
]),
source: "two.json",
},
];
const collected = collectAuditTargets(inputBoms, { trusted: "include" });
assert.strictEqual(collected.targets.length, 2);
const npmTarget = collected.targets.find((target) => target.type === "npm");
assert.deepStrictEqual(npmTarget.sources, ["one.json", "two.json"]);
assert.strictEqual(npmTarget.bomRefs.length, 1);
assert.strictEqual(npmTarget.properties.length, 2);
});
it("recognizes Cargo trusted-publishing and target-specific metadata", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:cargo/ring@0.17.8",
name: "ring",
properties: [
{ name: "cdx:cargo:target", value: 'cfg(target_os = "linux")' },
{ name: "cdx:cargo:trustedPublishing", value: "true" },
],
purl: "pkg:cargo/ring@0.17.8",
},
{
"bom-ref": "pkg:cargo/serde@1.0.217",
name: "serde",
purl: "pkg:cargo/serde@1.0.217",
},
]),
source: "cargo-trusted.json",
},
];
const collected = collectAuditTargets(inputBoms, { trusted: "include" });
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:cargo/serde@1.0.217", "pkg:cargo/ring@0.17.8"],
);
assert.strictEqual(collected.stats.trustedTargets, 1);
assert.strictEqual(collected.stats.platformSpecificTargets, 1);
});
it("prioritizes runtime-facing Cargo crates ahead of build-only workspace crates", () => {
const inputBoms = [
{
bomJson: makeBom(
[
{
"bom-ref": "pkg:cargo/root@1.0.0",
name: "root",
purl: "pkg:cargo/root@1.0.0",
},
{
"bom-ref": "pkg:cargo/runtime-helper@1.0.0",
name: "runtime-helper",
properties: [
{ name: "cdx:cargo:dependencyKind", value: "runtime" },
{
name: "cdx:cargo:workspaceDependencyResolved",
value: "true",
},
],
purl: "pkg:cargo/runtime-helper@1.0.0",
},
{
"bom-ref": "pkg:cargo/build-helper@1.0.0",
name: "build-helper",
properties: [
{ name: "cdx:cargo:dependencyKind", value: "build" },
{
name: "cdx:cargo:workspaceDependencyResolved",
value: "true",
},
],
purl: "pkg:cargo/build-helper@1.0.0",
},
],
{
dependencies: [
{
ref: "pkg:cargo/root@1.0.0",
dependsOn: [
"pkg:cargo/runtime-helper@1.0.0",
"pkg:cargo/build-helper@1.0.0",
],
},
],
metadata: {
component: {
"bom-ref": "pkg:cargo/root@1.0.0",
},
},
},
),
source: "cargo-priority.json",
},
];
const collected = collectAuditTargets(inputBoms, {
maxTargets: 2,
trusted: "include",
});
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:cargo/runtime-helper@1.0.0", "pkg:cargo/build-helper@1.0.0"],
);
assert.strictEqual(collected.targets[0].runtimeFacingCargo, true);
assert.strictEqual(collected.targets[1].buildOnlyWorkspace, true);
assert.strictEqual(collected.stats.buildOnlyWorkspaceTargets, 1);
assert.strictEqual(collected.stats.cargoRuntimeFacingTargets, 1);
});
it("respects maxTargets when supplied", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/a@1.0.0",
name: "a",
purl: "pkg:npm/a@1.0.0",
},
{
"bom-ref": "pkg:npm/b@1.0.0",
name: "b",
purl: "pkg:npm/b@1.0.0",
},
]),
source: "limit.json",
},
];
const collected = collectAuditTargets(inputBoms, 1);
assert.strictEqual(collected.targets.length, 1);
});
it("filters predictive audit targets to required scope when requested", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/core@1.0.0",
name: "core",
purl: "pkg:npm/core@1.0.0",
scope: "required",
},
{
"bom-ref": "pkg:npm/transitive@1.0.0",
name: "transitive",
purl: "pkg:npm/transitive@1.0.0",
},
{
"bom-ref": "pkg:npm/optional-addon@1.0.0",
name: "optional-addon",
purl: "pkg:npm/optional-addon@1.0.0",
scope: "optional",
},
{
"bom-ref": "pkg:pypi/unused@1.0.0",
name: "unused",
purl: "pkg:pypi/unused@1.0.0",
scope: "excluded",
},
]),
source: "required.json",
},
];
const collected = collectAuditTargets(inputBoms, { scope: "required" });
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/core@1.0.0", "pkg:npm/transitive@1.0.0"],
);
assert.strictEqual(collected.stats.requiredTargets, 2);
assert.strictEqual(collected.stats.nonRequiredTargets, 0);
});
it("skips built-in well-known npm allowlist prefixes by default", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/%40babel/parser@7.29.3",
name: "parser",
purl: "pkg:npm/%40babel/parser@7.29.3",
},
{
"bom-ref": "pkg:npm/npm@10.9.0",
name: "npm",
purl: "pkg:npm/npm@10.9.0",
},
{
"bom-ref": "pkg:npm/%40types/node@24.0.0",
name: "node",
purl: "pkg:npm/%40types/node@24.0.0",
},
{
"bom-ref": "pkg:npm/left-pad@1.3.0",
name: "left-pad",
purl: "pkg:npm/left-pad@1.3.0",
},
]),
source: "allowlist-default.json",
},
];
const collected = collectAuditTargets(inputBoms, { trusted: "include" });
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/left-pad@1.3.0"],
);
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 3);
assert.strictEqual(
collected.skipped.filter(
(entry) => entry.reason === "allowlisted-purl-prefix",
).length,
3,
);
});
it("supports additive custom predictive audit allowlists", () => {
withTemporaryAllowlistFile(
"custom-allowlist.json",
`${JSON.stringify(["pkg:npm/%40acme", "pkg:pypi/internal-tool"])}\n`,
(allowlistFile) => {
const inputBoms = [makeAllowlistInputBom("allowlist-custom.json")];
const collected = collectAuditTargets(inputBoms, {
allowlistFile,
trusted: "include",
});
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/left-pad@1.3.0"],
);
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 2);
},
);
});
it("supports newline-delimited custom allowlists with comments", () => {
withTemporaryAllowlistFile(
"custom-allowlist.txt",
[
"pkg:npm/%40acme # internal namespace",
"",
"pkg:pypi/internal-tool",
].join("\n"),
(allowlistFile) => {
const inputBoms = [makeAllowlistInputBom("allowlist-custom-text.json")];
const collected = collectAuditTargets(inputBoms, {
allowlistFile,
trusted: "include",
});
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/left-pad@1.3.0"],
);
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 2);
},
);
});
it("supports custom allowlists provided as a prefixes object", () => {
withTemporaryAllowlistFile(
"custom-allowlist.json",
`${JSON.stringify({ prefixes: ["pkg:npm/%40acme", "pkg:pypi/internal-tool"] })}\n`,
(allowlistFile) => {
const inputBoms = [
makeAllowlistInputBom("allowlist-custom-prefixes.json"),
];
const collected = collectAuditTargets(inputBoms, {
allowlistFile,
trusted: "include",
});
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/left-pad@1.3.0"],
);
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 2);
},
);
});
it("requires a purl boundary after an allowlisted prefix", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/npm@10.9.0",
name: "npm",
purl: "pkg:npm/npm@10.9.0",
},
{
"bom-ref": "pkg:npm/npm-run-all@4.1.5",
name: "npm-run-all",
purl: "pkg:npm/npm-run-all@4.1.5",
},
{
"bom-ref": "pkg:npm/npm-check-updates@17.1.0",
name: "npm-check-updates",
purl: "pkg:npm/npm-check-updates@17.1.0",
},
]),
source: "allowlist-boundary.json",
},
];
const collected = collectAuditTargets(inputBoms, { trusted: "include" });
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/npm-check-updates@17.1.0", "pkg:npm/npm-run-all@4.1.5"],
);
assert.strictEqual(collected.stats.allowlistedTargetsExcluded, 1);
});
it("prioritizes required targets before optional ones when maxTargets is set", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/a-optional@1.0.0",
name: "a-optional",
purl: "pkg:npm/a-optional@1.0.0",
scope: "optional",
},
{
"bom-ref": "pkg:npm/z-required@1.0.0",
name: "z-required",
purl: "pkg:npm/z-required@1.0.0",
scope: "required",
},
]),
source: "priority.json",
},
];
const collected = collectAuditTargets(inputBoms, { maxTargets: 1 });
assert.strictEqual(collected.targets.length, 1);
assert.strictEqual(collected.targets[0].purl, "pkg:npm/z-required@1.0.0");
assert.strictEqual(collected.stats.truncatedTargets, 1);
});
it("can prioritize direct runtime dependencies ahead of transitive platform-specific packages", () => {
const inputBoms = [
{
bomJson: makeBom(
[
{
"bom-ref": "pkg:npm/direct-runtime@1.0.0",
name: "direct-runtime",
purl: "pkg:npm/direct-runtime@1.0.0",
scope: "required",
},
{
"bom-ref": "pkg:npm/transitive-platform@1.0.0",
name: "transitive-platform",
properties: [{ name: "cdx:npm:os", value: "darwin" }],
purl: "pkg:npm/transitive-platform@1.0.0",
},
],
{
dependencies: [
{
dependsOn: ["pkg:npm/direct-runtime@1.0.0"],
ref: "pkg:application/root-app@1.0.0",
},
],
metadata: {
component: {
"bom-ref": "pkg:application/root-app@1.0.0",
name: "root-app",
type: "application",
},
},
},
),
source: "priority-runtime.json",
},
];
const collected = collectAuditTargets(inputBoms, {
maxTargets: 1,
prioritizeDirectRuntime: true,
trusted: "include",
});
assert.strictEqual(collected.targets.length, 1);
assert.strictEqual(
collected.targets[0].purl,
"pkg:npm/direct-runtime@1.0.0",
);
assert.strictEqual(collected.targets[0].directDependency, true);
assert.strictEqual(collected.stats.directRuntimeTargets, 1);
assert.strictEqual(collected.stats.platformSpecificTargets, 1);
});
it("prioritizes direct runtime dependencies by default", () => {
const inputBoms = [
{
bomJson: makeBom(
[
{
"bom-ref": "pkg:npm/direct-runtime@1.0.0",
name: "direct-runtime",
purl: "pkg:npm/direct-runtime@1.0.0",
scope: "required",
},
{
"bom-ref": "pkg:npm/transitive-platform@1.0.0",
name: "transitive-platform",
properties: [{ name: "cdx:npm:os", value: "darwin" }],
purl: "pkg:npm/transitive-platform@1.0.0",
},
],
{
dependencies: [
{
dependsOn: ["pkg:npm/direct-runtime@1.0.0"],
ref: "pkg:application/root-app@1.0.0",
},
],
metadata: {
component: {
"bom-ref": "pkg:application/root-app@1.0.0",
name: "root-app",
type: "application",
},
},
},
),
source: "priority-runtime-default.json",
},
];
const collected = collectAuditTargets(inputBoms, {
maxTargets: 1,
trusted: "include",
});
assert.strictEqual(collected.targets.length, 1);
assert.strictEqual(
collected.targets[0].purl,
"pkg:npm/direct-runtime@1.0.0",
);
});
it("uses explicit required scope and evidence occurrences to improve prioritization", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/implicit-required@1.0.0",
name: "implicit-required",
purl: "pkg:npm/implicit-required@1.0.0",
},
{
"bom-ref": "pkg:npm/evidence-backed@1.0.0",
evidence: {
occurrences: [
{ location: "src/a.js#1" },
{ location: "src/b.js#1" },
{ location: "src/c.js#1" },
],
},
name: "evidence-backed",
purl: "pkg:npm/evidence-backed@1.0.0",
},
{
"bom-ref": "pkg:npm/explicit-required@1.0.0",
evidence: {
occurrences: [{ location: "src/main.js#1" }],
},
name: "explicit-required",
purl: "pkg:npm/explicit-required@1.0.0",
scope: "required",
},
]),
source: "priority-evidence.json",
},
];
const collected = collectAuditTargets(inputBoms, {
maxTargets: 3,
trusted: "include",
});
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
[
"pkg:npm/explicit-required@1.0.0",
"pkg:npm/evidence-backed@1.0.0",
"pkg:npm/implicit-required@1.0.0",
],
);
assert.strictEqual(collected.targets[0].explicitRequiredScope, true);
assert.strictEqual(collected.targets[1].occurrenceCount, 3);
});
it("excludes trusted-publishing-backed targets by default", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/trusted@1.0.0",
name: "trusted",
properties: [
{
name: "cdx:npm:trustedPublishing",
value: "true",
},
],
purl: "pkg:npm/trusted@1.0.0",
scope: "required",
},
{
"bom-ref": "pkg:npm/plain@1.0.0",
name: "plain",
purl: "pkg:npm/plain@1.0.0",
scope: "required",
},
]),
source: "trusted.json",
},
];
const collected = collectAuditTargets(inputBoms);
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/plain@1.0.0"],
);
assert.strictEqual(collected.stats.trustedTargets, 1);
assert.strictEqual(collected.stats.trustedTargetsExcluded, 1);
});
it("includes trusted-publishing-backed targets when explicitly requested", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/trusted@1.0.0",
name: "trusted",
properties: [
{
name: "cdx:npm:trustedPublishing",
value: "true",
},
],
purl: "pkg:npm/trusted@1.0.0",
},
{
"bom-ref": "pkg:pypi/plain@1.0.0",
name: "plain",
purl: "pkg:pypi/plain@1.0.0",
},
]),
source: "include-trusted.json",
},
];
const collected = collectAuditTargets(inputBoms, { trusted: "include" });
assert.strictEqual(collected.targets.length, 2);
assert.strictEqual(collected.stats.trustedTargetsExcluded, 0);
});
it("can restrict predictive audit targets to only trusted-publishing-backed packages", () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/trusted@1.0.0",
name: "trusted",
properties: [
{
name: "cdx:npm:trustedPublishing",
value: "true",
},
],
purl: "pkg:npm/trusted@1.0.0",
},
{
"bom-ref": "pkg:npm/plain@1.0.0",
name: "plain",
purl: "pkg:npm/plain@1.0.0",
},
]),
source: "only-trusted.json",
},
];
const collected = collectAuditTargets(inputBoms, { trusted: "only" });
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/trusted@1.0.0"],
);
assert.strictEqual(collected.stats.availableTargets, 1);
assert.strictEqual(collected.stats.trustedTargets, 1);
});
});
describe("enrichInputBomsWithRegistryMetadata()", () => {
it("adds registry trusted-publishing properties for npm targets so default selection can exclude them", async () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:npm/@sec-ant/readable-stream@0.4.1",
name: "readable-stream",
purl: "pkg:npm/%40sec-ant/readable-stream@0.4.1",
},
{
"bom-ref": "pkg:npm/plain@1.0.0",
name: "plain",
purl: "pkg:npm/plain@1.0.0",
},
]),
source: "registry-enrichment.json",
},
];
const { enrichInputBomsWithRegistryMetadata: enrichWithMock } =
await esmock("./targets.js", {
"../helpers/utils.js": {
getCratesMetadata: async (pkgList) => pkgList,
getNpmMetadata: async (pkgList) =>
pkgList.map((pkg) =>
pkg.name === "readable-stream"
? {
...pkg,
properties: [
...(pkg.properties || []),
{ name: "cdx:npm:trustedPublishing", value: "true" },
{
name: "cdx:npm:provenanceUrl",
value:
"https://registry.npmjs.org/-/npm/v1/attestations/readable-stream",
},
],
}
: pkg,
),
getPyMetadata: async (pkgList) => pkgList,
},
});
await enrichWithMock(inputBoms);
const enrichedProperties = inputBoms[0].bomJson.components[0].properties;
assert.ok(
enrichedProperties.some(
(property) => property.name === "cdx:npm:trustedPublishing",
),
);
const collected = collectAuditTargets(inputBoms);
assert.deepStrictEqual(
collected.targets.map((target) => target.purl),
["pkg:npm/plain@1.0.0"],
);
});
it("adds registry trusted-publishing properties for pypi targets", async () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:pypi/example@1.0.0",
name: "example",
purl: "pkg:pypi/example@1.0.0",
},
]),
source: "pypi-enrichment.json",
},
];
const { enrichInputBomsWithRegistryMetadata: enrichWithMock } =
await esmock("./targets.js", {
"../helpers/utils.js": {
getCratesMetadata: async (pkgList) => pkgList,
getNpmMetadata: async (pkgList) => pkgList,
getPyMetadata: async (pkgList) =>
pkgList.map((pkg) => ({
...pkg,
properties: [
...(pkg.properties || []),
{ name: "cdx:pypi:trustedPublishing", value: "true" },
{ name: "cdx:pypi:uploaderVerified", value: "true" },
],
})),
},
});
await enrichWithMock(inputBoms);
assert.ok(
inputBoms[0].bomJson.components[0].properties.some(
(property) => property.name === "cdx:pypi:trustedPublishing",
),
);
});
it("adds registry metadata for cargo targets", async () => {
const inputBoms = [
{
bomJson: makeBom([
{
"bom-ref": "pkg:cargo/serde@1.0.217",
name: "serde",
purl: "pkg:cargo/serde@1.0.217",
},
]),
source: "cargo-enrichment.json",
},
];
const { enrichInputBomsWithRegistryMetadata: enrichWithMock } =
await esmock("./targets.js", {
"../helpers/utils.js": {
getCratesMetadata: async (pkgList) =>
pkgList.map((pkg) => ({
...pkg,
properties: [
...(pkg.properties || []),
{ name: "cdx:cargo:trustedPublishing", value: "true" },
{ name: "cdx:cargo:yanked", value: "true" },
],
})),
getNpmMetadata: async (pkgList) => pkgList,
getPyMetadata: async (pkgList) => pkgList,
},
});
await enrichWithMock(inputBoms);
assert.ok(
inputBoms[0].bomJson.components[0].properties.some(
(property) => property.name === "cdx:cargo:trustedPublishing",
),
);
assert.ok(
inputBoms[0].bomJson.components[0].properties.some(
(property) => property.name === "cdx:cargo:yanked",
),
);
});
});