@cyclonedx/cdxgen 12.3.1 → 12.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/bin/cdxgen.js +1 -2
- package/data/rules/ai-agent-governance.yaml +43 -0
- package/data/rules/ci-permissions.yaml +132 -0
- package/data/rules/dependency-sources.yaml +65 -5
- package/data/rules/mcp-servers.yaml +36 -2
- package/data/rules/package-integrity.yaml +22 -0
- package/lib/cli/index.js +436 -56
- package/lib/cli/index.poku.js +875 -2
- package/lib/helpers/agentFormulationParser.js +10 -3
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/aiInventory.js +262 -0
- package/lib/helpers/aiInventory.poku.js +111 -0
- package/lib/helpers/analyzer.js +413 -54
- package/lib/helpers/analyzer.poku.js +117 -0
- package/lib/helpers/auditCategories.js +76 -0
- package/lib/helpers/chromextutils.js +25 -3
- package/lib/helpers/chromextutils.poku.js +68 -0
- package/lib/helpers/ciParsers/githubActions.js +79 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
- package/lib/helpers/communityAiConfigParser.js +15 -5
- package/lib/helpers/communityAiConfigParser.poku.js +71 -0
- package/lib/helpers/depsUtils.js +5 -0
- package/lib/helpers/depsUtils.poku.js +55 -0
- package/lib/helpers/display.js +50 -24
- package/lib/helpers/display.poku.js +70 -58
- package/lib/helpers/formulationParsers.js +26 -6
- package/lib/helpers/jsonLike.js +21 -20
- package/lib/helpers/jsonLike.poku.js +34 -0
- package/lib/helpers/mcpConfigParser.js +32 -16
- package/lib/helpers/mcpConfigParser.poku.js +104 -0
- package/lib/helpers/mcpDiscovery.js +13 -23
- package/lib/helpers/mcpDiscovery.poku.js +21 -0
- package/lib/helpers/propertySanitizer.js +121 -0
- package/lib/helpers/utils.js +953 -41
- package/lib/helpers/utils.poku.js +901 -1
- package/lib/managers/binary.js +16 -0
- package/lib/managers/binary.poku.js +1 -0
- package/lib/managers/docker.js +240 -16
- package/lib/managers/docker.poku.js +1142 -2
- package/lib/server/server.js +7 -4
- package/lib/server/server.poku.js +36 -1
- package/lib/stages/postgen/annotator.js +2 -1
- package/lib/stages/postgen/annotator.poku.js +15 -0
- package/lib/stages/postgen/auditBom.js +12 -6
- package/lib/stages/postgen/auditBom.poku.js +755 -6
- package/lib/stages/postgen/postgen.js +229 -6
- package/lib/stages/postgen/postgen.poku.js +180 -0
- package/package.json +2 -1
- package/types/lib/cli/index.d.ts +1 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/helpers/aiInventory.d.ts +23 -0
- package/types/lib/helpers/aiInventory.d.ts.map +1 -0
- package/types/lib/helpers/analyzer.d.ts +5 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/auditCategories.d.ts +12 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +1 -0
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/jsonLike.d.ts +4 -0
- package/types/lib/helpers/jsonLike.d.ts.map +1 -0
- package/types/lib/helpers/mcp.d.ts +29 -0
- package/types/lib/helpers/mcp.d.ts.map +1 -0
- package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
- package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/helpers/propertySanitizer.d.ts +3 -0
- package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +31 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +3 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
|
@@ -190,7 +190,7 @@ describe("evaluateRule", () => {
|
|
|
190
190
|
);
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
it("should detect npm install script from
|
|
193
|
+
it("should detect npm install script from direct manifest source (PKG-001)", async () => {
|
|
194
194
|
const rules = await loadRules(RULES_DIR);
|
|
195
195
|
const rule = rules.find((r) => r.id === "PKG-001");
|
|
196
196
|
assert.ok(rule, "PKG-001 rule should exist");
|
|
@@ -198,7 +198,11 @@ describe("evaluateRule", () => {
|
|
|
198
198
|
const bom = makeBom([
|
|
199
199
|
makeComponent("sketchy-pkg", "1.0.0", [
|
|
200
200
|
["cdx:npm:hasInstallScript", "true"],
|
|
201
|
-
["cdx:npm:
|
|
201
|
+
["cdx:npm:manifestSourceType", "git"],
|
|
202
|
+
[
|
|
203
|
+
"cdx:npm:manifestSource",
|
|
204
|
+
"git+https://github.com/acme/sketchy-pkg.git",
|
|
205
|
+
],
|
|
202
206
|
]),
|
|
203
207
|
]);
|
|
204
208
|
|
|
@@ -207,6 +211,122 @@ describe("evaluateRule", () => {
|
|
|
207
211
|
assert.strictEqual(findings[0].severity, "high");
|
|
208
212
|
});
|
|
209
213
|
|
|
214
|
+
it("should detect npm install scripts from url and path manifest sources for PKG-001", async () => {
|
|
215
|
+
const rules = await loadRules(RULES_DIR);
|
|
216
|
+
const rule = rules.find((r) => r.id === "PKG-001");
|
|
217
|
+
assert.ok(rule, "PKG-001 rule should exist");
|
|
218
|
+
|
|
219
|
+
for (const manifestSourceType of ["url", "path"]) {
|
|
220
|
+
const bom = makeBom([
|
|
221
|
+
makeComponent(`sketchy-${manifestSourceType}`, "1.0.0", [
|
|
222
|
+
["cdx:npm:hasInstallScript", "true"],
|
|
223
|
+
["cdx:npm:manifestSourceType", manifestSourceType],
|
|
224
|
+
["cdx:npm:manifestSource", `${manifestSourceType}:example`],
|
|
225
|
+
]),
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
const findings = await evaluateRule(rule, bom);
|
|
229
|
+
assert.ok(findings.length > 0);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should not detect npm install script without manifest source evidence for PKG-001", async () => {
|
|
234
|
+
const rules = await loadRules(RULES_DIR);
|
|
235
|
+
const rule = rules.find((r) => r.id === "PKG-001");
|
|
236
|
+
assert.ok(rule, "PKG-001 rule should exist");
|
|
237
|
+
|
|
238
|
+
const bom = makeBom([
|
|
239
|
+
makeComponent("registry-pkg", "1.0.0", [
|
|
240
|
+
["cdx:npm:hasInstallScript", "true"],
|
|
241
|
+
["cdx:npm:isRegistryDependency", "false"],
|
|
242
|
+
]),
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
const findings = await evaluateRule(rule, bom);
|
|
246
|
+
assert.strictEqual(findings.length, 0);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("should detect Collider packages from insecure HTTP origins (PKG-009)", async () => {
|
|
250
|
+
const rules = await loadRules(RULES_DIR);
|
|
251
|
+
const rule = rules.find((r) => r.id === "PKG-009");
|
|
252
|
+
assert.ok(rule, "PKG-009 rule should exist");
|
|
253
|
+
|
|
254
|
+
const bom = makeBom([
|
|
255
|
+
makeComponent("fmt", "11.0.2", [
|
|
256
|
+
["cdx:collider:dependencyKind", "direct"],
|
|
257
|
+
["cdx:collider:origin", "http://mirror.example.com/collider/v2/"],
|
|
258
|
+
["cdx:collider:originScheme", "http"],
|
|
259
|
+
["cdx:collider:originHost", "mirror.example.com"],
|
|
260
|
+
]),
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
const findings = await evaluateRule(rule, bom);
|
|
264
|
+
assert.ok(findings.length > 0, "Should detect insecure Collider origin");
|
|
265
|
+
assert.strictEqual(findings[0].ruleId, "PKG-009");
|
|
266
|
+
assert.strictEqual(findings[0].severity, "medium");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("should detect Collider origins that required sanitization (PKG-010)", async () => {
|
|
270
|
+
const rules = await loadRules(RULES_DIR);
|
|
271
|
+
const rule = rules.find((r) => r.id === "PKG-010");
|
|
272
|
+
assert.ok(rule, "PKG-010 rule should exist");
|
|
273
|
+
|
|
274
|
+
const bom = makeBom([
|
|
275
|
+
makeComponent("spdlog", "1.15.0", [
|
|
276
|
+
["cdx:collider:dependencyKind", "direct"],
|
|
277
|
+
["cdx:collider:origin", "https://example.com/collider/v2/"],
|
|
278
|
+
["cdx:collider:originScheme", "https"],
|
|
279
|
+
["cdx:collider:originSanitized", "true"],
|
|
280
|
+
]),
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
const findings = await evaluateRule(rule, bom);
|
|
284
|
+
assert.ok(findings.length > 0, "Should detect sanitized Collider origin");
|
|
285
|
+
assert.strictEqual(findings[0].ruleId, "PKG-010");
|
|
286
|
+
assert.strictEqual(findings[0].severity, "low");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("should detect python dependency from direct manifest source (PKG-011)", async () => {
|
|
290
|
+
const rules = await loadRules(RULES_DIR);
|
|
291
|
+
const rule = rules.find((r) => r.id === "PKG-011");
|
|
292
|
+
assert.ok(rule, "PKG-011 rule should exist");
|
|
293
|
+
|
|
294
|
+
const bom = makeBom([
|
|
295
|
+
makeComponent("suspicious-python-pkg", "1.0.0", [
|
|
296
|
+
["cdx:pypi:manifestSourceType", "url"],
|
|
297
|
+
[
|
|
298
|
+
"cdx:pypi:manifestSource",
|
|
299
|
+
"https://example.com/suspicious-python-pkg.whl",
|
|
300
|
+
],
|
|
301
|
+
]),
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
const findings = await evaluateRule(rule, bom);
|
|
305
|
+
assert.ok(findings.length > 0, "Should detect python direct source risk");
|
|
306
|
+
assert.strictEqual(findings[0].ruleId, "PKG-011");
|
|
307
|
+
assert.strictEqual(findings[0].severity, "high");
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("should detect Collider packages missing valid wrap hashes (INT-014)", async () => {
|
|
311
|
+
const rules = await loadRules(RULES_DIR);
|
|
312
|
+
const rule = rules.find((r) => r.id === "INT-014");
|
|
313
|
+
assert.ok(rule, "INT-014 rule should exist");
|
|
314
|
+
|
|
315
|
+
const bom = makeBom([
|
|
316
|
+
makeComponent("fast_float", "8.0.2", [
|
|
317
|
+
["cdx:collider:dependencyKind", "transitive"],
|
|
318
|
+
["cdx:collider:hasWrapHash", "false"],
|
|
319
|
+
["cdx:collider:wrapHash", "not-a-sha256"],
|
|
320
|
+
["cdx:collider:wrapHashInvalid", "true"],
|
|
321
|
+
]),
|
|
322
|
+
]);
|
|
323
|
+
|
|
324
|
+
const findings = await evaluateRule(rule, bom);
|
|
325
|
+
assert.ok(findings.length > 0, "Should detect missing Collider wrap hash");
|
|
326
|
+
assert.strictEqual(findings[0].ruleId, "INT-014");
|
|
327
|
+
assert.strictEqual(findings[0].severity, "high");
|
|
328
|
+
});
|
|
329
|
+
|
|
210
330
|
it("should detect OIDC token issuance to a non-official action (CI-002)", async () => {
|
|
211
331
|
const rules = await loadRules(RULES_DIR);
|
|
212
332
|
const rule = rules.find((r) => r.id === "CI-002");
|
|
@@ -524,13 +644,14 @@ describe("evaluateRule", () => {
|
|
|
524
644
|
{ name: "cdx:mcp:inventorySource", value: "config-file" },
|
|
525
645
|
{ name: "cdx:mcp:credentialExposure", value: "true" },
|
|
526
646
|
{
|
|
527
|
-
name: "cdx:mcp:
|
|
528
|
-
value: "
|
|
647
|
+
name: "cdx:mcp:credentialExposureFieldCount",
|
|
648
|
+
value: "2",
|
|
529
649
|
},
|
|
530
650
|
{
|
|
531
|
-
name: "cdx:mcp:
|
|
532
|
-
value: "
|
|
651
|
+
name: "cdx:mcp:credentialIndicatorCount",
|
|
652
|
+
value: "2",
|
|
533
653
|
},
|
|
654
|
+
{ name: "cdx:mcp:credentialReferenceCount", value: "1" },
|
|
534
655
|
],
|
|
535
656
|
},
|
|
536
657
|
],
|
|
@@ -602,6 +723,56 @@ describe("evaluateRule", () => {
|
|
|
602
723
|
assert.ok(findings.length > 0, "Should detect token passthrough risk");
|
|
603
724
|
});
|
|
604
725
|
|
|
726
|
+
it("should flag shipped AI instruction files in build/post-build BOMs (AGT-007)", async () => {
|
|
727
|
+
const rules = await loadRules(RULES_DIR);
|
|
728
|
+
const rule = rules.find((r) => r.id === "AGT-007");
|
|
729
|
+
assert.ok(rule, "AGT-007 rule should exist");
|
|
730
|
+
|
|
731
|
+
const bom = makeBom([
|
|
732
|
+
{
|
|
733
|
+
"bom-ref": "file:/repo/CLAUDE.md",
|
|
734
|
+
name: "CLAUDE.md",
|
|
735
|
+
type: "file",
|
|
736
|
+
properties: [
|
|
737
|
+
{ name: "SrcFile", value: "/repo/CLAUDE.md" },
|
|
738
|
+
{ name: "cdx:file:kind", value: "agent-instructions" },
|
|
739
|
+
{ name: "cdx:agent:inventorySource", value: "agent-file" },
|
|
740
|
+
],
|
|
741
|
+
},
|
|
742
|
+
]);
|
|
743
|
+
bom.metadata.lifecycles = [{ phase: "build" }, { phase: "post-build" }];
|
|
744
|
+
|
|
745
|
+
const findings = await evaluateRule(rule, bom);
|
|
746
|
+
assert.ok(findings.length > 0, "Should detect shipped AI instructions");
|
|
747
|
+
assert.strictEqual(findings[0].severity, "medium");
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
it("should flag shipped MCP config files in build/post-build BOMs (MCP-008)", async () => {
|
|
751
|
+
const rules = await loadRules(RULES_DIR);
|
|
752
|
+
const rule = rules.find((r) => r.id === "MCP-008");
|
|
753
|
+
assert.ok(rule, "MCP-008 rule should exist");
|
|
754
|
+
|
|
755
|
+
const bom = makeBom([
|
|
756
|
+
{
|
|
757
|
+
"bom-ref": "file:/repo/.vscode/mcp.json",
|
|
758
|
+
name: "mcp.json",
|
|
759
|
+
type: "file",
|
|
760
|
+
properties: [
|
|
761
|
+
{ name: "SrcFile", value: "/repo/.vscode/mcp.json" },
|
|
762
|
+
{ name: "cdx:file:kind", value: "mcp-config" },
|
|
763
|
+
{ name: "cdx:mcp:configFormat", value: "vscode" },
|
|
764
|
+
{ name: "cdx:mcp:configuredServiceCount", value: "1" },
|
|
765
|
+
{ name: "cdx:mcp:configuredServiceNames", value: "releaseDocs" },
|
|
766
|
+
],
|
|
767
|
+
},
|
|
768
|
+
]);
|
|
769
|
+
bom.metadata.lifecycles = [{ phase: "build" }];
|
|
770
|
+
|
|
771
|
+
const findings = await evaluateRule(rule, bom);
|
|
772
|
+
assert.ok(findings.length > 0, "Should detect shipped MCP config");
|
|
773
|
+
assert.strictEqual(findings[0].severity, "medium");
|
|
774
|
+
});
|
|
775
|
+
|
|
605
776
|
it("should detect npm name mismatch (INT-002)", async () => {
|
|
606
777
|
const rules = await loadRules(RULES_DIR);
|
|
607
778
|
const rule = rules.find((r) => r.id === "INT-002");
|
|
@@ -1383,6 +1554,528 @@ describe("evaluateRule", () => {
|
|
|
1383
1554
|
assert.match(findings[0].message, /Heuristic review/);
|
|
1384
1555
|
});
|
|
1385
1556
|
|
|
1557
|
+
it("should detect disabled npm cache when npm distributions are resolved remotely (CI-022)", async () => {
|
|
1558
|
+
const rules = await loadRules(RULES_DIR);
|
|
1559
|
+
const rule = rules.find((r) => r.id === "CI-022");
|
|
1560
|
+
assert.ok(rule, "CI-022 rule should exist");
|
|
1561
|
+
|
|
1562
|
+
const bom = makeBom(
|
|
1563
|
+
[
|
|
1564
|
+
{
|
|
1565
|
+
type: "library",
|
|
1566
|
+
name: "left-pad",
|
|
1567
|
+
version: "1.3.0",
|
|
1568
|
+
purl: "pkg:npm/left-pad@1.3.0",
|
|
1569
|
+
"bom-ref": "pkg:npm/left-pad@1.3.0",
|
|
1570
|
+
externalReferences: [
|
|
1571
|
+
{
|
|
1572
|
+
type: "distribution",
|
|
1573
|
+
url: "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
|
|
1574
|
+
},
|
|
1575
|
+
],
|
|
1576
|
+
properties: [
|
|
1577
|
+
{
|
|
1578
|
+
name: "cdx:npm:manifestSourceType",
|
|
1579
|
+
value: "url",
|
|
1580
|
+
},
|
|
1581
|
+
{
|
|
1582
|
+
name: "cdx:npm:manifestSource",
|
|
1583
|
+
value: "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
|
|
1584
|
+
},
|
|
1585
|
+
],
|
|
1586
|
+
},
|
|
1587
|
+
],
|
|
1588
|
+
[],
|
|
1589
|
+
[
|
|
1590
|
+
{
|
|
1591
|
+
type: "application",
|
|
1592
|
+
name: "setup-node",
|
|
1593
|
+
version: "v4",
|
|
1594
|
+
purl: "pkg:github/actions/setup-node@v4",
|
|
1595
|
+
"bom-ref": "pkg:github/actions/setup-node@v4",
|
|
1596
|
+
properties: [
|
|
1597
|
+
{
|
|
1598
|
+
name: "cdx:github:action:uses",
|
|
1599
|
+
value: "actions/setup-node@v4",
|
|
1600
|
+
},
|
|
1601
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
1602
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "npm" },
|
|
1603
|
+
{
|
|
1604
|
+
name: "cdx:github:action:buildCacheDisableInput",
|
|
1605
|
+
value: "package-manager-cache",
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
name: "cdx:github:action:buildCacheDisableValue",
|
|
1609
|
+
value: "false",
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
name: "cdx:github:workflow:file",
|
|
1613
|
+
value: ".github/workflows/ci.yml",
|
|
1614
|
+
},
|
|
1615
|
+
],
|
|
1616
|
+
},
|
|
1617
|
+
],
|
|
1618
|
+
);
|
|
1619
|
+
|
|
1620
|
+
const findings = await evaluateRule(rule, bom);
|
|
1621
|
+
assert.ok(findings.length > 0, "Should detect disabled npm cache");
|
|
1622
|
+
assert.strictEqual(findings[0].severity, "medium");
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
it("should detect disabled npm cache for git manifest sources (CI-022)", async () => {
|
|
1626
|
+
const rules = await loadRules(RULES_DIR);
|
|
1627
|
+
const rule = rules.find((r) => r.id === "CI-022");
|
|
1628
|
+
assert.ok(rule, "CI-022 rule should exist");
|
|
1629
|
+
|
|
1630
|
+
const bom = makeBom(
|
|
1631
|
+
[
|
|
1632
|
+
{
|
|
1633
|
+
type: "library",
|
|
1634
|
+
name: "git-dep",
|
|
1635
|
+
version: "2.0.0",
|
|
1636
|
+
purl: "pkg:npm/git-dep@2.0.0",
|
|
1637
|
+
"bom-ref": "pkg:npm/git-dep@2.0.0",
|
|
1638
|
+
externalReferences: [
|
|
1639
|
+
{
|
|
1640
|
+
type: "distribution",
|
|
1641
|
+
url: "git+https://github.com/acme/git-dep.git",
|
|
1642
|
+
},
|
|
1643
|
+
],
|
|
1644
|
+
properties: [
|
|
1645
|
+
{
|
|
1646
|
+
name: "cdx:npm:manifestSourceType",
|
|
1647
|
+
value: "git",
|
|
1648
|
+
},
|
|
1649
|
+
{
|
|
1650
|
+
name: "cdx:npm:manifestSource",
|
|
1651
|
+
value: "git+https://github.com/acme/git-dep.git",
|
|
1652
|
+
},
|
|
1653
|
+
],
|
|
1654
|
+
},
|
|
1655
|
+
],
|
|
1656
|
+
[],
|
|
1657
|
+
[
|
|
1658
|
+
{
|
|
1659
|
+
type: "application",
|
|
1660
|
+
name: "setup-node",
|
|
1661
|
+
version: "v4",
|
|
1662
|
+
purl: "pkg:github/actions/setup-node@v4",
|
|
1663
|
+
"bom-ref": "pkg:github/actions/setup-node@v4",
|
|
1664
|
+
properties: [
|
|
1665
|
+
{
|
|
1666
|
+
name: "cdx:github:action:uses",
|
|
1667
|
+
value: "actions/setup-node@v4",
|
|
1668
|
+
},
|
|
1669
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
1670
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "npm" },
|
|
1671
|
+
],
|
|
1672
|
+
},
|
|
1673
|
+
],
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
const findings = await evaluateRule(rule, bom);
|
|
1677
|
+
assert.ok(findings.length > 0, "Should detect disabled npm cache");
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
it("should not detect disabled npm cache for registry-only npm dependencies (CI-022)", async () => {
|
|
1681
|
+
const rules = await loadRules(RULES_DIR);
|
|
1682
|
+
const rule = rules.find((r) => r.id === "CI-022");
|
|
1683
|
+
assert.ok(rule, "CI-022 rule should exist");
|
|
1684
|
+
|
|
1685
|
+
const bom = makeBom(
|
|
1686
|
+
[
|
|
1687
|
+
{
|
|
1688
|
+
type: "library",
|
|
1689
|
+
name: "left-pad",
|
|
1690
|
+
version: "1.3.0",
|
|
1691
|
+
purl: "pkg:npm/left-pad@1.3.0",
|
|
1692
|
+
"bom-ref": "pkg:npm/left-pad@1.3.0",
|
|
1693
|
+
externalReferences: [
|
|
1694
|
+
{
|
|
1695
|
+
type: "distribution",
|
|
1696
|
+
url: "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
|
|
1697
|
+
},
|
|
1698
|
+
],
|
|
1699
|
+
},
|
|
1700
|
+
],
|
|
1701
|
+
[],
|
|
1702
|
+
[
|
|
1703
|
+
{
|
|
1704
|
+
type: "application",
|
|
1705
|
+
name: "setup-node",
|
|
1706
|
+
version: "v4",
|
|
1707
|
+
purl: "pkg:github/actions/setup-node@v4",
|
|
1708
|
+
"bom-ref": "pkg:github/actions/setup-node@v4",
|
|
1709
|
+
properties: [
|
|
1710
|
+
{
|
|
1711
|
+
name: "cdx:github:action:uses",
|
|
1712
|
+
value: "actions/setup-node@v4",
|
|
1713
|
+
},
|
|
1714
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
1715
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "npm" },
|
|
1716
|
+
],
|
|
1717
|
+
},
|
|
1718
|
+
],
|
|
1719
|
+
);
|
|
1720
|
+
|
|
1721
|
+
const findings = await evaluateRule(rule, bom);
|
|
1722
|
+
assert.strictEqual(findings.length, 0);
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
it("should not detect disabled npm cache for local path manifest sources (CI-022)", async () => {
|
|
1726
|
+
const rules = await loadRules(RULES_DIR);
|
|
1727
|
+
const rule = rules.find((r) => r.id === "CI-022");
|
|
1728
|
+
assert.ok(rule, "CI-022 rule should exist");
|
|
1729
|
+
|
|
1730
|
+
const bom = makeBom(
|
|
1731
|
+
[
|
|
1732
|
+
{
|
|
1733
|
+
type: "library",
|
|
1734
|
+
name: "local-dep",
|
|
1735
|
+
version: "1.0.0",
|
|
1736
|
+
purl: "pkg:npm/local-dep@1.0.0",
|
|
1737
|
+
"bom-ref": "pkg:npm/local-dep@1.0.0",
|
|
1738
|
+
externalReferences: [
|
|
1739
|
+
{
|
|
1740
|
+
type: "distribution",
|
|
1741
|
+
url: "file:../libs/local-dep",
|
|
1742
|
+
},
|
|
1743
|
+
],
|
|
1744
|
+
properties: [
|
|
1745
|
+
{
|
|
1746
|
+
name: "cdx:npm:manifestSourceType",
|
|
1747
|
+
value: "path",
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
name: "cdx:npm:manifestSource",
|
|
1751
|
+
value: "file:../libs/local-dep",
|
|
1752
|
+
},
|
|
1753
|
+
],
|
|
1754
|
+
},
|
|
1755
|
+
],
|
|
1756
|
+
[],
|
|
1757
|
+
[
|
|
1758
|
+
{
|
|
1759
|
+
type: "application",
|
|
1760
|
+
name: "setup-node",
|
|
1761
|
+
version: "v4",
|
|
1762
|
+
purl: "pkg:github/actions/setup-node@v4",
|
|
1763
|
+
"bom-ref": "pkg:github/actions/setup-node@v4",
|
|
1764
|
+
properties: [
|
|
1765
|
+
{
|
|
1766
|
+
name: "cdx:github:action:uses",
|
|
1767
|
+
value: "actions/setup-node@v4",
|
|
1768
|
+
},
|
|
1769
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
1770
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "npm" },
|
|
1771
|
+
],
|
|
1772
|
+
},
|
|
1773
|
+
],
|
|
1774
|
+
);
|
|
1775
|
+
|
|
1776
|
+
const findings = await evaluateRule(rule, bom);
|
|
1777
|
+
assert.strictEqual(findings.length, 0);
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
it("should detect disabled Python cache when pylock sources use remote artifacts (CI-023)", async () => {
|
|
1781
|
+
const rules = await loadRules(RULES_DIR);
|
|
1782
|
+
const rule = rules.find((r) => r.id === "CI-023");
|
|
1783
|
+
assert.ok(rule, "CI-023 rule should exist");
|
|
1784
|
+
|
|
1785
|
+
const bom = makeBom(
|
|
1786
|
+
[
|
|
1787
|
+
{
|
|
1788
|
+
type: "library",
|
|
1789
|
+
name: "requests",
|
|
1790
|
+
version: "2.32.0",
|
|
1791
|
+
purl: "pkg:pypi/requests@2.32.0",
|
|
1792
|
+
"bom-ref": "pkg:pypi/requests@2.32.0",
|
|
1793
|
+
properties: [
|
|
1794
|
+
{
|
|
1795
|
+
name: "cdx:pypi:manifestSourceType",
|
|
1796
|
+
value: "url",
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
name: "cdx:pypi:manifestSource",
|
|
1800
|
+
value:
|
|
1801
|
+
"https://files.pythonhosted.org/packages/requests-2.32.0.tar.gz",
|
|
1802
|
+
},
|
|
1803
|
+
],
|
|
1804
|
+
},
|
|
1805
|
+
],
|
|
1806
|
+
[],
|
|
1807
|
+
[
|
|
1808
|
+
{
|
|
1809
|
+
type: "application",
|
|
1810
|
+
name: "setup-python",
|
|
1811
|
+
version: "v5",
|
|
1812
|
+
purl: "pkg:github/actions/setup-python@v5",
|
|
1813
|
+
"bom-ref": "pkg:github/actions/setup-python@v5",
|
|
1814
|
+
properties: [
|
|
1815
|
+
{
|
|
1816
|
+
name: "cdx:github:action:uses",
|
|
1817
|
+
value: "actions/setup-python@v5",
|
|
1818
|
+
},
|
|
1819
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
1820
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "pypi" },
|
|
1821
|
+
{
|
|
1822
|
+
name: "cdx:github:action:buildCacheDisableInput",
|
|
1823
|
+
value: "cache",
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
name: "cdx:github:action:buildCacheDisableValue",
|
|
1827
|
+
value: "false",
|
|
1828
|
+
},
|
|
1829
|
+
{
|
|
1830
|
+
name: "cdx:github:workflow:file",
|
|
1831
|
+
value: ".github/workflows/ci.yml",
|
|
1832
|
+
},
|
|
1833
|
+
],
|
|
1834
|
+
},
|
|
1835
|
+
],
|
|
1836
|
+
);
|
|
1837
|
+
|
|
1838
|
+
const findings = await evaluateRule(rule, bom);
|
|
1839
|
+
assert.ok(findings.length > 0, "Should detect disabled Python cache");
|
|
1840
|
+
assert.strictEqual(findings[0].severity, "medium");
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
it("should detect disabled Python cache for git manifest sources (CI-023)", async () => {
|
|
1844
|
+
const rules = await loadRules(RULES_DIR);
|
|
1845
|
+
const rule = rules.find((r) => r.id === "CI-023");
|
|
1846
|
+
assert.ok(rule, "CI-023 rule should exist");
|
|
1847
|
+
|
|
1848
|
+
const bom = makeBom(
|
|
1849
|
+
[
|
|
1850
|
+
{
|
|
1851
|
+
type: "library",
|
|
1852
|
+
name: "private-lib",
|
|
1853
|
+
version: "1.0.0",
|
|
1854
|
+
purl: "pkg:pypi/private-lib@1.0.0",
|
|
1855
|
+
"bom-ref": "pkg:pypi/private-lib@1.0.0",
|
|
1856
|
+
properties: [
|
|
1857
|
+
{
|
|
1858
|
+
name: "cdx:pypi:manifestSourceType",
|
|
1859
|
+
value: "git",
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
name: "cdx:pypi:manifestSource",
|
|
1863
|
+
value: "git+https://github.com/acme/private-lib.git",
|
|
1864
|
+
},
|
|
1865
|
+
],
|
|
1866
|
+
},
|
|
1867
|
+
],
|
|
1868
|
+
[],
|
|
1869
|
+
[
|
|
1870
|
+
{
|
|
1871
|
+
type: "application",
|
|
1872
|
+
name: "setup-python",
|
|
1873
|
+
version: "v5",
|
|
1874
|
+
purl: "pkg:github/actions/setup-python@v5",
|
|
1875
|
+
"bom-ref": "pkg:github/actions/setup-python@v5",
|
|
1876
|
+
properties: [
|
|
1877
|
+
{
|
|
1878
|
+
name: "cdx:github:action:uses",
|
|
1879
|
+
value: "actions/setup-python@v5",
|
|
1880
|
+
},
|
|
1881
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
1882
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "pypi" },
|
|
1883
|
+
],
|
|
1884
|
+
},
|
|
1885
|
+
],
|
|
1886
|
+
);
|
|
1887
|
+
|
|
1888
|
+
const findings = await evaluateRule(rule, bom);
|
|
1889
|
+
assert.ok(findings.length > 0, "Should detect disabled Python cache");
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1892
|
+
it("should not detect disabled Python cache for registry-only lockfile sources (CI-023)", async () => {
|
|
1893
|
+
const rules = await loadRules(RULES_DIR);
|
|
1894
|
+
const rule = rules.find((r) => r.id === "CI-023");
|
|
1895
|
+
assert.ok(rule, "CI-023 rule should exist");
|
|
1896
|
+
|
|
1897
|
+
const bom = makeBom(
|
|
1898
|
+
[
|
|
1899
|
+
{
|
|
1900
|
+
type: "library",
|
|
1901
|
+
name: "requests",
|
|
1902
|
+
version: "2.32.0",
|
|
1903
|
+
purl: "pkg:pypi/requests@2.32.0",
|
|
1904
|
+
"bom-ref": "pkg:pypi/requests@2.32.0",
|
|
1905
|
+
properties: [
|
|
1906
|
+
{
|
|
1907
|
+
name: "cdx:pylock:archive",
|
|
1908
|
+
value:
|
|
1909
|
+
'{"url":"https://files.pythonhosted.org/packages/requests-2.32.0.tar.gz"}',
|
|
1910
|
+
},
|
|
1911
|
+
],
|
|
1912
|
+
},
|
|
1913
|
+
],
|
|
1914
|
+
[],
|
|
1915
|
+
[
|
|
1916
|
+
{
|
|
1917
|
+
type: "application",
|
|
1918
|
+
name: "setup-python",
|
|
1919
|
+
version: "v5",
|
|
1920
|
+
purl: "pkg:github/actions/setup-python@v5",
|
|
1921
|
+
"bom-ref": "pkg:github/actions/setup-python@v5",
|
|
1922
|
+
properties: [
|
|
1923
|
+
{
|
|
1924
|
+
name: "cdx:github:action:uses",
|
|
1925
|
+
value: "actions/setup-python@v5",
|
|
1926
|
+
},
|
|
1927
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
1928
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "pypi" },
|
|
1929
|
+
],
|
|
1930
|
+
},
|
|
1931
|
+
],
|
|
1932
|
+
);
|
|
1933
|
+
|
|
1934
|
+
const findings = await evaluateRule(rule, bom);
|
|
1935
|
+
assert.strictEqual(findings.length, 0);
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
it("should not detect disabled Python cache for local path manifest sources (CI-023)", async () => {
|
|
1939
|
+
const rules = await loadRules(RULES_DIR);
|
|
1940
|
+
const rule = rules.find((r) => r.id === "CI-023");
|
|
1941
|
+
assert.ok(rule, "CI-023 rule should exist");
|
|
1942
|
+
|
|
1943
|
+
const bom = makeBom(
|
|
1944
|
+
[
|
|
1945
|
+
{
|
|
1946
|
+
type: "library",
|
|
1947
|
+
name: "local-lib",
|
|
1948
|
+
version: "1.0.0",
|
|
1949
|
+
purl: "pkg:pypi/local-lib@1.0.0",
|
|
1950
|
+
"bom-ref": "pkg:pypi/local-lib@1.0.0",
|
|
1951
|
+
properties: [
|
|
1952
|
+
{
|
|
1953
|
+
name: "cdx:pypi:manifestSourceType",
|
|
1954
|
+
value: "path",
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
name: "cdx:pypi:manifestSource",
|
|
1958
|
+
value: "../libs/local-lib",
|
|
1959
|
+
},
|
|
1960
|
+
],
|
|
1961
|
+
},
|
|
1962
|
+
],
|
|
1963
|
+
[],
|
|
1964
|
+
[
|
|
1965
|
+
{
|
|
1966
|
+
type: "application",
|
|
1967
|
+
name: "setup-python",
|
|
1968
|
+
version: "v5",
|
|
1969
|
+
purl: "pkg:github/actions/setup-python@v5",
|
|
1970
|
+
"bom-ref": "pkg:github/actions/setup-python@v5",
|
|
1971
|
+
properties: [
|
|
1972
|
+
{
|
|
1973
|
+
name: "cdx:github:action:uses",
|
|
1974
|
+
value: "actions/setup-python@v5",
|
|
1975
|
+
},
|
|
1976
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
1977
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "pypi" },
|
|
1978
|
+
],
|
|
1979
|
+
},
|
|
1980
|
+
],
|
|
1981
|
+
);
|
|
1982
|
+
|
|
1983
|
+
const findings = await evaluateRule(rule, bom);
|
|
1984
|
+
assert.strictEqual(findings.length, 0);
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
it("should detect disabled Cargo cache for git manifest sources (CI-024)", async () => {
|
|
1988
|
+
const rules = await loadRules(RULES_DIR);
|
|
1989
|
+
const rule = rules.find((r) => r.id === "CI-024");
|
|
1990
|
+
assert.ok(rule, "CI-024 rule should exist");
|
|
1991
|
+
|
|
1992
|
+
const bom = makeBom(
|
|
1993
|
+
[
|
|
1994
|
+
{
|
|
1995
|
+
type: "library",
|
|
1996
|
+
name: "git-crate",
|
|
1997
|
+
version: "git+https://github.com/acme/git-crate.git",
|
|
1998
|
+
purl: "pkg:cargo/git-crate@git+https://github.com/acme/git-crate.git",
|
|
1999
|
+
"bom-ref":
|
|
2000
|
+
"pkg:cargo/git-crate@git+https://github.com/acme/git-crate.git",
|
|
2001
|
+
properties: [
|
|
2002
|
+
{
|
|
2003
|
+
name: "cdx:cargo:git",
|
|
2004
|
+
value: "https://github.com/acme/git-crate.git",
|
|
2005
|
+
},
|
|
2006
|
+
],
|
|
2007
|
+
},
|
|
2008
|
+
],
|
|
2009
|
+
[],
|
|
2010
|
+
[
|
|
2011
|
+
{
|
|
2012
|
+
type: "application",
|
|
2013
|
+
name: "setup-rust",
|
|
2014
|
+
version: "v1",
|
|
2015
|
+
purl: "pkg:github/moonrepo/setup-rust@v1",
|
|
2016
|
+
"bom-ref": "pkg:github/moonrepo/setup-rust@v1",
|
|
2017
|
+
properties: [
|
|
2018
|
+
{
|
|
2019
|
+
name: "cdx:github:action:uses",
|
|
2020
|
+
value: "moonrepo/setup-rust@v1",
|
|
2021
|
+
},
|
|
2022
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
2023
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "cargo" },
|
|
2024
|
+
],
|
|
2025
|
+
},
|
|
2026
|
+
],
|
|
2027
|
+
);
|
|
2028
|
+
|
|
2029
|
+
const findings = await evaluateRule(rule, bom);
|
|
2030
|
+
assert.ok(findings.length > 0, "Should detect disabled Cargo cache");
|
|
2031
|
+
assert.strictEqual(findings[0].severity, "medium");
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
it("should not detect disabled Cargo cache for local path manifest sources (CI-024)", async () => {
|
|
2035
|
+
const rules = await loadRules(RULES_DIR);
|
|
2036
|
+
const rule = rules.find((r) => r.id === "CI-024");
|
|
2037
|
+
assert.ok(rule, "CI-024 rule should exist");
|
|
2038
|
+
|
|
2039
|
+
const bom = makeBom(
|
|
2040
|
+
[
|
|
2041
|
+
{
|
|
2042
|
+
type: "library",
|
|
2043
|
+
name: "path-crate",
|
|
2044
|
+
version: "path+../path-crate",
|
|
2045
|
+
purl: "pkg:cargo/path-crate@path+../path-crate",
|
|
2046
|
+
"bom-ref": "pkg:cargo/path-crate@path+../path-crate",
|
|
2047
|
+
properties: [
|
|
2048
|
+
{
|
|
2049
|
+
name: "cdx:cargo:path",
|
|
2050
|
+
value: "../path-crate",
|
|
2051
|
+
},
|
|
2052
|
+
],
|
|
2053
|
+
},
|
|
2054
|
+
],
|
|
2055
|
+
[],
|
|
2056
|
+
[
|
|
2057
|
+
{
|
|
2058
|
+
type: "application",
|
|
2059
|
+
name: "setup-rust",
|
|
2060
|
+
version: "v1",
|
|
2061
|
+
purl: "pkg:github/moonrepo/setup-rust@v1",
|
|
2062
|
+
"bom-ref": "pkg:github/moonrepo/setup-rust@v1",
|
|
2063
|
+
properties: [
|
|
2064
|
+
{
|
|
2065
|
+
name: "cdx:github:action:uses",
|
|
2066
|
+
value: "moonrepo/setup-rust@v1",
|
|
2067
|
+
},
|
|
2068
|
+
{ name: "cdx:github:action:disablesBuildCache", value: "true" },
|
|
2069
|
+
{ name: "cdx:github:action:buildCacheEcosystem", value: "cargo" },
|
|
2070
|
+
],
|
|
2071
|
+
},
|
|
2072
|
+
],
|
|
2073
|
+
);
|
|
2074
|
+
|
|
2075
|
+
const findings = await evaluateRule(rule, bom);
|
|
2076
|
+
assert.strictEqual(findings.length, 0);
|
|
2077
|
+
});
|
|
2078
|
+
|
|
1386
2079
|
it("should detect root authorized_keys without restrictions (OBOM-LNX-003)", async () => {
|
|
1387
2080
|
const rules = await loadRules(RULES_DIR);
|
|
1388
2081
|
const rule = rules.find((r) => r.id === "OBOM-LNX-003");
|
|
@@ -2301,6 +2994,62 @@ describe("auditBom", () => {
|
|
|
2301
2994
|
}
|
|
2302
2995
|
});
|
|
2303
2996
|
|
|
2997
|
+
it("expands the ai-inventory category alias", async () => {
|
|
2998
|
+
const bom = makeBom(
|
|
2999
|
+
[],
|
|
3000
|
+
[],
|
|
3001
|
+
[
|
|
3002
|
+
{
|
|
3003
|
+
type: "application",
|
|
3004
|
+
name: "agent-guide",
|
|
3005
|
+
version: "latest",
|
|
3006
|
+
"bom-ref": "file:/repo/AGENTS.md",
|
|
3007
|
+
properties: [
|
|
3008
|
+
{ name: "SrcFile", value: "/repo/AGENTS.md" },
|
|
3009
|
+
{ name: "cdx:agent:inventorySource", value: "agent-file" },
|
|
3010
|
+
{ name: "cdx:file:kind", value: "agent-instructions" },
|
|
3011
|
+
{
|
|
3012
|
+
name: "cdx:agent:hasNonOfficialMcpReference",
|
|
3013
|
+
value: "true",
|
|
3014
|
+
},
|
|
3015
|
+
{ name: "cdx:agent:mcpPackageRefs", value: "@acme/mcp-server" },
|
|
3016
|
+
],
|
|
3017
|
+
},
|
|
3018
|
+
],
|
|
3019
|
+
[
|
|
3020
|
+
{
|
|
3021
|
+
"bom-ref": "urn:service:mcp:demo:1",
|
|
3022
|
+
group: "mcp",
|
|
3023
|
+
name: "demo-server",
|
|
3024
|
+
authenticated: false,
|
|
3025
|
+
endpoints: ["https://mcp.example.com/mcp"],
|
|
3026
|
+
properties: [
|
|
3027
|
+
{ name: "cdx:mcp:transport", value: "streamable-http" },
|
|
3028
|
+
{ name: "cdx:mcp:serviceType", value: "inferred-endpoint" },
|
|
3029
|
+
{ name: "cdx:mcp:inventorySource", value: "agent-file" },
|
|
3030
|
+
{ name: "cdx:mcp:toolCount", value: "1" },
|
|
3031
|
+
{ name: "cdx:mcp:officialSdk", value: "false" },
|
|
3032
|
+
],
|
|
3033
|
+
},
|
|
3034
|
+
],
|
|
3035
|
+
);
|
|
3036
|
+
|
|
3037
|
+
const findings = await auditBom(bom, {
|
|
3038
|
+
bomAuditCategories: "ai-inventory",
|
|
3039
|
+
});
|
|
3040
|
+
assert.ok(findings.some((finding) => finding.category === "ai-agent"));
|
|
3041
|
+
assert.ok(findings.some((finding) => finding.category === "mcp-server"));
|
|
3042
|
+
});
|
|
3043
|
+
|
|
3044
|
+
it("rejects unknown audit categories with valid choices", async () => {
|
|
3045
|
+
await assert.rejects(
|
|
3046
|
+
auditBom(makeBom([]), {
|
|
3047
|
+
bomAuditCategories: "unknown-category",
|
|
3048
|
+
}),
|
|
3049
|
+
/Unknown BOM audit category: unknown-category\. Valid categories: .*ai-inventory \(alias for ai-agent,mcp-server\).*ci-permission.*mcp-server/,
|
|
3050
|
+
);
|
|
3051
|
+
});
|
|
3052
|
+
|
|
2304
3053
|
it("should filter by minimum severity", async () => {
|
|
2305
3054
|
const bom = makeBom([
|
|
2306
3055
|
makeComponent("actions/setup-node", "v3", [
|