@cyclonedx/cdxgen 12.3.3 → 12.4.1
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 +69 -25
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +270 -127
- package/bin/convert.js +34 -15
- package/bin/hbom.js +495 -0
- package/bin/repl.js +592 -37
- package/bin/validate.js +31 -4
- package/bin/verify.js +18 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/queries-darwin.json +12 -1
- package/data/queries-win.json +7 -1
- package/data/queries.json +39 -2
- package/data/rules/ai-agent-governance.yaml +16 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/chrome-extensions.yaml +8 -0
- package/data/rules/ci-permissions.yaml +42 -18
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +11 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +18 -3
- package/data/rules/obom-runtime.yaml +907 -22
- package/data/rules/package-integrity.yaml +14 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +9 -0
- package/lib/audit/index.js +210 -8
- package/lib/audit/index.poku.js +332 -0
- package/lib/audit/reporters.js +222 -0
- package/lib/audit/targets.js +146 -1
- package/lib/audit/targets.poku.js +186 -0
- package/lib/cli/asar.poku.js +328 -0
- package/lib/cli/index.js +527 -99
- package/lib/cli/index.poku.js +1469 -212
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/analyzer.js +1406 -29
- package/lib/helpers/analyzer.poku.js +342 -0
- package/lib/helpers/analyzerScope.js +712 -0
- package/lib/helpers/asarutils.js +1556 -0
- package/lib/helpers/asarutils.poku.js +443 -0
- package/lib/helpers/auditCategories.js +12 -0
- package/lib/helpers/auditCategories.poku.js +32 -0
- package/lib/helpers/bomUtils.js +155 -1
- package/lib/helpers/bomUtils.poku.js +79 -1
- package/lib/helpers/cbomutils.js +271 -1
- package/lib/helpers/cbomutils.poku.js +248 -5
- package/lib/helpers/display.js +291 -1
- package/lib/helpers/display.poku.js +149 -0
- package/lib/helpers/evidenceUtils.js +58 -0
- package/lib/helpers/evidenceUtils.poku.js +54 -0
- package/lib/helpers/exportUtils.js +9 -0
- package/lib/helpers/gtfobins.js +142 -8
- package/lib/helpers/gtfobins.poku.js +24 -1
- package/lib/helpers/hbom.js +710 -0
- package/lib/helpers/hbom.poku.js +496 -0
- package/lib/helpers/hbomAnalysis.js +268 -0
- package/lib/helpers/hbomAnalysis.poku.js +249 -0
- package/lib/helpers/hbomLoader.js +35 -0
- package/lib/helpers/hostTopology.js +803 -0
- package/lib/helpers/hostTopology.poku.js +363 -0
- package/lib/helpers/inventoryStats.js +69 -0
- package/lib/helpers/inventoryStats.poku.js +86 -0
- package/lib/helpers/lolbas.js +19 -1
- package/lib/helpers/lolbas.poku.js +23 -0
- package/lib/helpers/osqueryTransform.js +47 -0
- package/lib/helpers/osqueryTransform.poku.js +47 -0
- package/lib/helpers/plugins.js +350 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/protobom.js +209 -45
- package/lib/helpers/protobom.poku.js +183 -5
- package/lib/helpers/protobomLoader.js +43 -0
- package/lib/helpers/protobomLoader.poku.js +31 -0
- package/lib/helpers/remote/dependency-track.js +36 -3
- package/lib/helpers/remote/dependency-track.poku.js +44 -0
- package/lib/helpers/source.js +24 -0
- package/lib/helpers/source.poku.js +32 -0
- package/lib/helpers/utils.js +1438 -93
- package/lib/helpers/utils.poku.js +846 -4
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2293 -353
- package/lib/managers/binary.poku.js +1699 -1
- package/lib/managers/docker.js +201 -79
- package/lib/managers/docker.poku.js +337 -12
- package/lib/server/server.js +4 -28
- package/lib/stages/postgen/annotator.js +38 -0
- package/lib/stages/postgen/annotator.poku.js +107 -1
- package/lib/stages/postgen/auditBom.js +121 -18
- package/lib/stages/postgen/auditBom.poku.js +1366 -31
- package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
- package/lib/stages/postgen/postgen.js +406 -8
- package/lib/stages/postgen/postgen.poku.js +484 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/lib/validator/bomValidator.js +90 -38
- package/lib/validator/bomValidator.poku.js +90 -0
- package/lib/validator/complianceRules.js +4 -2
- package/lib/validator/index.poku.js +14 -0
- package/package.json +23 -21
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts +1 -1
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +44 -0
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts +16 -0
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +16 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +4 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +33 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/analyzerScope.d.ts +11 -0
- package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
- package/types/lib/helpers/asarutils.d.ts +34 -0
- package/types/lib/helpers/asarutils.d.ts.map +1 -0
- package/types/lib/helpers/auditCategories.d.ts +5 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -1
- package/types/lib/helpers/bomUtils.d.ts +10 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +3 -2
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/evidenceUtils.d.ts +8 -0
- package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +8 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -1
- package/types/lib/helpers/hbom.d.ts +49 -0
- package/types/lib/helpers/hbom.d.ts.map +1 -0
- package/types/lib/helpers/hbomAnalysis.d.ts +76 -0
- package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/helpers/hbomLoader.d.ts +7 -0
- package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
- package/types/lib/helpers/hostTopology.d.ts +12 -0
- package/types/lib/helpers/hostTopology.d.ts.map +1 -0
- package/types/lib/helpers/inventoryStats.d.ts +11 -0
- package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -1
- package/types/lib/helpers/osqueryTransform.d.ts +3 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
- package/types/lib/helpers/plugins.d.ts +58 -0
- package/types/lib/helpers/plugins.d.ts.map +1 -0
- package/types/lib/helpers/protobom.d.ts +5 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/protobomLoader.d.ts +17 -0
- package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +45 -8
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +5 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +2 -1
- 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 +26 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +2 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/node.d.ts +23 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/data/spdx-model-v3.0.1.jsonld +0 -15999
|
@@ -241,6 +241,443 @@ it("postProcess passes formulationList from bomNSData into the formulation secti
|
|
|
241
241
|
);
|
|
242
242
|
});
|
|
243
243
|
|
|
244
|
+
it("postProcess finalizes CycloneDX 2.0-dev root fields and strips legacy fields", () => {
|
|
245
|
+
const bomNSData = {
|
|
246
|
+
bomJson: {
|
|
247
|
+
bomFormat: "CycloneDX",
|
|
248
|
+
specVersion: "1.7",
|
|
249
|
+
metadata: {
|
|
250
|
+
manufacture: { name: "Legacy Factory" },
|
|
251
|
+
component: "not-a-component-object",
|
|
252
|
+
tools: [
|
|
253
|
+
{
|
|
254
|
+
author: "OWASP Foundation",
|
|
255
|
+
name: "cdxgen",
|
|
256
|
+
vendor: "OWASP Foundation",
|
|
257
|
+
version: "12.4.0",
|
|
258
|
+
components: [
|
|
259
|
+
{
|
|
260
|
+
author: "Nested Tool Author",
|
|
261
|
+
modified: true,
|
|
262
|
+
name: "cdxgen-plugin",
|
|
263
|
+
type: "library",
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
components: [
|
|
270
|
+
{
|
|
271
|
+
author: "Jane Doe",
|
|
272
|
+
modified: false,
|
|
273
|
+
name: "demo-lib",
|
|
274
|
+
type: "library",
|
|
275
|
+
version: "1.0.0",
|
|
276
|
+
components: [
|
|
277
|
+
{
|
|
278
|
+
author: "Nested Author",
|
|
279
|
+
modified: true,
|
|
280
|
+
name: "nested-lib",
|
|
281
|
+
type: "library",
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const result = postProcess(bomNSData, { specVersion: 2.0 });
|
|
290
|
+
const [toolComponent] = result.bomJson.metadata.tools.components;
|
|
291
|
+
const [component] = result.bomJson.components;
|
|
292
|
+
|
|
293
|
+
assert.strictEqual(result.bomJson.specFormat, "CycloneDX");
|
|
294
|
+
assert.strictEqual(result.bomJson.bomFormat, undefined);
|
|
295
|
+
assert.strictEqual(result.bomJson.specVersion, "2.0");
|
|
296
|
+
assert.strictEqual(result.bomJson.metadata.manufacture, undefined);
|
|
297
|
+
assert.deepStrictEqual(result.bomJson.metadata.manufacturer, {
|
|
298
|
+
name: "Legacy Factory",
|
|
299
|
+
});
|
|
300
|
+
assert.strictEqual(
|
|
301
|
+
result.bomJson.metadata.component,
|
|
302
|
+
"not-a-component-object",
|
|
303
|
+
);
|
|
304
|
+
assert.strictEqual(toolComponent.publisher, "OWASP Foundation");
|
|
305
|
+
assert.deepStrictEqual(toolComponent.authors, [{ name: "OWASP Foundation" }]);
|
|
306
|
+
assert.strictEqual(toolComponent.author, undefined);
|
|
307
|
+
assert.deepStrictEqual(toolComponent.components[0].authors, [
|
|
308
|
+
{ name: "Nested Tool Author" },
|
|
309
|
+
]);
|
|
310
|
+
assert.strictEqual(toolComponent.components[0].author, undefined);
|
|
311
|
+
assert.strictEqual(toolComponent.components[0].modified, undefined);
|
|
312
|
+
assert.deepStrictEqual(component.authors, [{ name: "Jane Doe" }]);
|
|
313
|
+
assert.strictEqual(component.author, undefined);
|
|
314
|
+
assert.strictEqual(component.modified, undefined);
|
|
315
|
+
assert.deepStrictEqual(component.components[0].authors, [
|
|
316
|
+
{ name: "Nested Author" },
|
|
317
|
+
]);
|
|
318
|
+
assert.strictEqual(component.components[0].author, undefined);
|
|
319
|
+
assert.strictEqual(component.components[0].modified, undefined);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("postProcess preserves malformed explicit specVersion values instead of coercing them to 1.7", () => {
|
|
323
|
+
const malformedBomResult = postProcess(
|
|
324
|
+
{
|
|
325
|
+
bomJson: {
|
|
326
|
+
bomFormat: "CycloneDX",
|
|
327
|
+
specVersion: "2.0.1",
|
|
328
|
+
components: [],
|
|
329
|
+
dependencies: [],
|
|
330
|
+
metadata: { properties: [] },
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
{},
|
|
334
|
+
);
|
|
335
|
+
assert.strictEqual(malformedBomResult.bomJson.specVersion, "2.0.1");
|
|
336
|
+
assert.strictEqual(malformedBomResult.bomJson.bomFormat, "CycloneDX");
|
|
337
|
+
assert.strictEqual(malformedBomResult.bomJson.specFormat, undefined);
|
|
338
|
+
|
|
339
|
+
const malformedOptionResult = postProcess(
|
|
340
|
+
{
|
|
341
|
+
bomJson: {
|
|
342
|
+
bomFormat: "CycloneDX",
|
|
343
|
+
specVersion: "1.7",
|
|
344
|
+
components: [],
|
|
345
|
+
dependencies: [],
|
|
346
|
+
metadata: { properties: [] },
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
{ specVersion: "2.0.1" },
|
|
350
|
+
);
|
|
351
|
+
assert.strictEqual(malformedOptionResult.bomJson.specVersion, "1.7");
|
|
352
|
+
assert.strictEqual(malformedOptionResult.bomJson.bomFormat, "CycloneDX");
|
|
353
|
+
assert.strictEqual(malformedOptionResult.bomJson.specFormat, undefined);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("postProcess migrates CycloneDX 2.0 metadata manufacture and tool services without broad recursion", () => {
|
|
357
|
+
const bomNSData = {
|
|
358
|
+
bomJson: {
|
|
359
|
+
bomFormat: "CycloneDX",
|
|
360
|
+
specVersion: "1.7",
|
|
361
|
+
metadata: {
|
|
362
|
+
manufacture: { name: "Component Factory" },
|
|
363
|
+
component: {
|
|
364
|
+
name: "demo-app",
|
|
365
|
+
type: "application",
|
|
366
|
+
},
|
|
367
|
+
tools: {
|
|
368
|
+
components: [],
|
|
369
|
+
services: [
|
|
370
|
+
{
|
|
371
|
+
author: "Legacy Author",
|
|
372
|
+
name: "scanner-service",
|
|
373
|
+
vendor: "Scanner Vendor",
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
components: [],
|
|
379
|
+
dependencies: [{ ref: "pkg:generic/demo@1.0.0", dependsOn: [] }],
|
|
380
|
+
unrelatedInventory: {
|
|
381
|
+
components: [
|
|
382
|
+
{
|
|
383
|
+
author: "Should Not Be Traversed",
|
|
384
|
+
name: "not-a-component-list",
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const result = postProcess(bomNSData, { specVersion: 2.0 });
|
|
392
|
+
const [toolService] = result.bomJson.metadata.tools.services;
|
|
393
|
+
|
|
394
|
+
assert.strictEqual(result.bomJson.metadata.manufacture, undefined);
|
|
395
|
+
assert.deepStrictEqual(result.bomJson.metadata.component.manufacturer, {
|
|
396
|
+
name: "Component Factory",
|
|
397
|
+
});
|
|
398
|
+
assert.deepStrictEqual(toolService.provider, { name: "Scanner Vendor" });
|
|
399
|
+
assert.strictEqual(toolService.vendor, undefined);
|
|
400
|
+
assert.strictEqual(toolService.author, undefined);
|
|
401
|
+
assert.strictEqual(
|
|
402
|
+
result.bomJson.unrelatedInventory.components[0].author,
|
|
403
|
+
"Should Not Be Traversed",
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("postProcess downgrades certificate crypto properties for spec version 1.6", () => {
|
|
408
|
+
const bomNSData = {
|
|
409
|
+
bomJson: {
|
|
410
|
+
bomFormat: "CycloneDX",
|
|
411
|
+
specVersion: "1.6",
|
|
412
|
+
components: [
|
|
413
|
+
{
|
|
414
|
+
type: "cryptographic-asset",
|
|
415
|
+
name: "demo-cert",
|
|
416
|
+
cryptoProperties: {
|
|
417
|
+
assetType: "certificate",
|
|
418
|
+
certificateProperties: {
|
|
419
|
+
serialNumber: "1234",
|
|
420
|
+
subjectName: "CN=demo",
|
|
421
|
+
issuerName: "CN=demo",
|
|
422
|
+
notValidBefore: "2024-01-01T00:00:00.000Z",
|
|
423
|
+
notValidAfter: "2034-01-01T00:00:00.000Z",
|
|
424
|
+
certificateFormat: "X.509",
|
|
425
|
+
certificateFileExtension: "crt",
|
|
426
|
+
fingerprint: { alg: "SHA-1", content: "a".repeat(40) },
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
dependencies: [],
|
|
432
|
+
formulation: [
|
|
433
|
+
{
|
|
434
|
+
components: [
|
|
435
|
+
{
|
|
436
|
+
type: "cryptographic-asset",
|
|
437
|
+
name: "formulation-cert",
|
|
438
|
+
cryptoProperties: {
|
|
439
|
+
assetType: "certificate",
|
|
440
|
+
certificateProperties: {
|
|
441
|
+
serialNumber: "5678",
|
|
442
|
+
subjectName: "CN=formulation",
|
|
443
|
+
certificateFileExtension: "pem",
|
|
444
|
+
fingerprint: { alg: "SHA-1", content: "b".repeat(40) },
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
],
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
metadata: {
|
|
452
|
+
properties: [],
|
|
453
|
+
tools: {
|
|
454
|
+
components: [{ name: "cdxgen" }],
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
const result = postProcess(bomNSData, { specVersion: 1.6 });
|
|
460
|
+
const componentCert =
|
|
461
|
+
result.bomJson.components[0].cryptoProperties.certificateProperties;
|
|
462
|
+
const formulationCert =
|
|
463
|
+
result.bomJson.formulation[0].components[0].cryptoProperties
|
|
464
|
+
.certificateProperties;
|
|
465
|
+
|
|
466
|
+
assert.deepStrictEqual(componentCert, {
|
|
467
|
+
subjectName: "CN=demo",
|
|
468
|
+
issuerName: "CN=demo",
|
|
469
|
+
notValidBefore: "2024-01-01T00:00:00.000Z",
|
|
470
|
+
notValidAfter: "2034-01-01T00:00:00.000Z",
|
|
471
|
+
certificateFormat: "X.509",
|
|
472
|
+
certificateExtension: "crt",
|
|
473
|
+
});
|
|
474
|
+
assert.deepStrictEqual(formulationCert, {
|
|
475
|
+
subjectName: "CN=formulation",
|
|
476
|
+
certificateExtension: "pem",
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("postProcess removes remaining 1.7-only fields from metadata, components, and formulation inventories for spec version 1.6", () => {
|
|
481
|
+
const bomNSData = {
|
|
482
|
+
bomJson: {
|
|
483
|
+
bomFormat: "CycloneDX",
|
|
484
|
+
specVersion: "1.6",
|
|
485
|
+
components: [
|
|
486
|
+
{
|
|
487
|
+
type: "library",
|
|
488
|
+
name: "demo-lib",
|
|
489
|
+
version: "1.0.0",
|
|
490
|
+
isExternal: true,
|
|
491
|
+
patentAssertions: [{ patentNumber: "US-123" }],
|
|
492
|
+
versionRange: "vers:npm/>=1.0.0|<2.0.0",
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
dependencies: [],
|
|
496
|
+
formulation: [
|
|
497
|
+
{
|
|
498
|
+
components: [
|
|
499
|
+
{
|
|
500
|
+
type: "library",
|
|
501
|
+
name: "formulation-lib",
|
|
502
|
+
version: "2.0.0",
|
|
503
|
+
isExternal: true,
|
|
504
|
+
versionRange: "vers:npm/>=2.0.0|<3.0.0",
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
services: [
|
|
508
|
+
{
|
|
509
|
+
name: "formulation-service",
|
|
510
|
+
patentAssertions: [{ patentNumber: "US-456" }],
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
metadata: {
|
|
516
|
+
distributionConstraints: { tlp: "GREEN" },
|
|
517
|
+
component: {
|
|
518
|
+
type: "application",
|
|
519
|
+
name: "demo-app",
|
|
520
|
+
version: "1.0.0",
|
|
521
|
+
isExternal: true,
|
|
522
|
+
versionRange: "vers:npm/>=1.0.0|<2.0.0",
|
|
523
|
+
},
|
|
524
|
+
properties: [],
|
|
525
|
+
tools: {
|
|
526
|
+
components: [{ name: "cdxgen" }],
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
services: [
|
|
530
|
+
{
|
|
531
|
+
name: "demo-service",
|
|
532
|
+
patentAssertions: [{ patentNumber: "US-789" }],
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const result = postProcess(bomNSData, { specVersion: 1.6 });
|
|
539
|
+
const rootComponent = result.bomJson.components[0];
|
|
540
|
+
const formulationComponent = result.bomJson.formulation[0].components[0];
|
|
541
|
+
const rootService = result.bomJson.services[0];
|
|
542
|
+
const formulationService = result.bomJson.formulation[0].services[0];
|
|
543
|
+
const metadataComponent = result.bomJson.metadata.component;
|
|
544
|
+
|
|
545
|
+
assert.strictEqual(
|
|
546
|
+
result.bomJson.metadata.distributionConstraints,
|
|
547
|
+
undefined,
|
|
548
|
+
);
|
|
549
|
+
assert.strictEqual(rootComponent.isExternal, undefined);
|
|
550
|
+
assert.strictEqual(rootComponent.patentAssertions, undefined);
|
|
551
|
+
assert.strictEqual(rootComponent.versionRange, undefined);
|
|
552
|
+
assert.strictEqual(formulationComponent.isExternal, undefined);
|
|
553
|
+
assert.strictEqual(formulationComponent.versionRange, undefined);
|
|
554
|
+
assert.strictEqual(rootService.patentAssertions, undefined);
|
|
555
|
+
assert.strictEqual(formulationService.patentAssertions, undefined);
|
|
556
|
+
assert.strictEqual(metadataComponent.isExternal, undefined);
|
|
557
|
+
assert.strictEqual(metadataComponent.versionRange, undefined);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("postProcess removes remaining 1.6-only fields from metadata, components, and formulation inventories for spec version 1.5", () => {
|
|
561
|
+
const bomNSData = {
|
|
562
|
+
bomJson: {
|
|
563
|
+
bomFormat: "CycloneDX",
|
|
564
|
+
specVersion: "1.5",
|
|
565
|
+
components: [
|
|
566
|
+
{
|
|
567
|
+
type: "library",
|
|
568
|
+
name: "demo-lib",
|
|
569
|
+
version: "1.0.0",
|
|
570
|
+
authors: [{ name: "Alice" }],
|
|
571
|
+
manufacturer: { name: "Acme" },
|
|
572
|
+
omniborId: ["gitoid:blob:sha1:abc"],
|
|
573
|
+
swhid: ["swh:1:rev:def"],
|
|
574
|
+
tags: ["demo"],
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
dependencies: [],
|
|
578
|
+
formulation: [
|
|
579
|
+
{
|
|
580
|
+
components: [
|
|
581
|
+
{
|
|
582
|
+
type: "library",
|
|
583
|
+
name: "formulation-lib",
|
|
584
|
+
version: "2.0.0",
|
|
585
|
+
authors: [{ name: "Bob" }],
|
|
586
|
+
manufacturer: { name: "Builder" },
|
|
587
|
+
omniborId: ["gitoid:blob:sha1:ghi"],
|
|
588
|
+
swhid: ["swh:1:dir:jkl"],
|
|
589
|
+
tags: ["workflow"],
|
|
590
|
+
},
|
|
591
|
+
],
|
|
592
|
+
services: [
|
|
593
|
+
{
|
|
594
|
+
name: "formulation-service",
|
|
595
|
+
tags: ["ci"],
|
|
596
|
+
},
|
|
597
|
+
],
|
|
598
|
+
},
|
|
599
|
+
],
|
|
600
|
+
metadata: {
|
|
601
|
+
manufacturer: { name: "BOM Factory" },
|
|
602
|
+
component: {
|
|
603
|
+
type: "application",
|
|
604
|
+
name: "demo-app",
|
|
605
|
+
version: "1.0.0",
|
|
606
|
+
authors: [{ name: "Carol" }],
|
|
607
|
+
manufacturer: { name: "Acme" },
|
|
608
|
+
tags: ["root"],
|
|
609
|
+
},
|
|
610
|
+
properties: [],
|
|
611
|
+
},
|
|
612
|
+
services: [
|
|
613
|
+
{
|
|
614
|
+
name: "demo-service",
|
|
615
|
+
tags: ["runtime"],
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const result = postProcess(bomNSData, { specVersion: 1.5 });
|
|
622
|
+
const rootComponent = result.bomJson.components[0];
|
|
623
|
+
const formulationComponent = result.bomJson.formulation[0].components[0];
|
|
624
|
+
const rootService = result.bomJson.services[0];
|
|
625
|
+
const formulationService = result.bomJson.formulation[0].services[0];
|
|
626
|
+
const metadataComponent = result.bomJson.metadata.component;
|
|
627
|
+
|
|
628
|
+
assert.strictEqual(result.bomJson.metadata.manufacturer, undefined);
|
|
629
|
+
assert.strictEqual(rootComponent.authors, undefined);
|
|
630
|
+
assert.strictEqual(rootComponent.manufacturer, undefined);
|
|
631
|
+
assert.strictEqual(rootComponent.omniborId, undefined);
|
|
632
|
+
assert.strictEqual(rootComponent.swhid, undefined);
|
|
633
|
+
assert.strictEqual(rootComponent.tags, undefined);
|
|
634
|
+
assert.strictEqual(formulationComponent.authors, undefined);
|
|
635
|
+
assert.strictEqual(formulationComponent.manufacturer, undefined);
|
|
636
|
+
assert.strictEqual(formulationComponent.omniborId, undefined);
|
|
637
|
+
assert.strictEqual(formulationComponent.swhid, undefined);
|
|
638
|
+
assert.strictEqual(formulationComponent.tags, undefined);
|
|
639
|
+
assert.strictEqual(rootService.tags, undefined);
|
|
640
|
+
assert.strictEqual(formulationService.tags, undefined);
|
|
641
|
+
assert.strictEqual(metadataComponent.authors, undefined);
|
|
642
|
+
assert.strictEqual(metadataComponent.manufacturer, undefined);
|
|
643
|
+
assert.strictEqual(metadataComponent.tags, undefined);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it("postProcess removes unsupported evidence occurrence details for spec version 1.5", () => {
|
|
647
|
+
const bomNSData = {
|
|
648
|
+
bomJson: {
|
|
649
|
+
bomFormat: "CycloneDX",
|
|
650
|
+
specVersion: "1.5",
|
|
651
|
+
components: [
|
|
652
|
+
{
|
|
653
|
+
type: "file",
|
|
654
|
+
name: "deviceTypeManager.js",
|
|
655
|
+
evidence: {
|
|
656
|
+
occurrences: [
|
|
657
|
+
{
|
|
658
|
+
location: "source/microservices/lib/deviceTypeManager.js",
|
|
659
|
+
line: 11,
|
|
660
|
+
offset: 2,
|
|
661
|
+
symbol: "deviceTypeManager",
|
|
662
|
+
additionalContext: "source-import",
|
|
663
|
+
},
|
|
664
|
+
],
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
dependencies: [],
|
|
669
|
+
metadata: { properties: [] },
|
|
670
|
+
},
|
|
671
|
+
};
|
|
672
|
+
const result = postProcess(bomNSData, { specVersion: 1.5 });
|
|
673
|
+
|
|
674
|
+
assert.deepStrictEqual(result.bomJson.components[0].evidence.occurrences, [
|
|
675
|
+
{
|
|
676
|
+
location: "source/microservices/lib/deviceTypeManager.js",
|
|
677
|
+
},
|
|
678
|
+
]);
|
|
679
|
+
});
|
|
680
|
+
|
|
244
681
|
it("postProcess merges formulation-discovered MCP config services into bomJson.services", () => {
|
|
245
682
|
const tmpDir = join(tmpdir(), `cdxgen-postgen-${Date.now()}`);
|
|
246
683
|
mkdirSync(join(tmpDir, ".vscode"), { recursive: true });
|
|
@@ -372,6 +809,53 @@ it("postProcess attaches releaseNotes to cdxgen metadata tool component", () =>
|
|
|
372
809
|
}
|
|
373
810
|
});
|
|
374
811
|
|
|
812
|
+
it("postProcess refreshes unpackaged native file inventory counts from the final BOM", () => {
|
|
813
|
+
const bomNSData = {
|
|
814
|
+
bomJson: {
|
|
815
|
+
bomFormat: "CycloneDX",
|
|
816
|
+
specVersion: "1.7",
|
|
817
|
+
components: [
|
|
818
|
+
{
|
|
819
|
+
name: "demo",
|
|
820
|
+
type: "file",
|
|
821
|
+
properties: [{ name: "internal:is_executable", value: "true" }],
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
name: "libdemo.so",
|
|
825
|
+
type: "file",
|
|
826
|
+
properties: [{ name: "internal:is_shared_library", value: "true" }],
|
|
827
|
+
},
|
|
828
|
+
],
|
|
829
|
+
dependencies: [],
|
|
830
|
+
metadata: {
|
|
831
|
+
properties: [
|
|
832
|
+
{ name: "cdx:container:unpackagedExecutableCount", value: "0" },
|
|
833
|
+
{
|
|
834
|
+
name: "cdx:container:unpackagedSharedLibraryCount",
|
|
835
|
+
value: "0",
|
|
836
|
+
},
|
|
837
|
+
],
|
|
838
|
+
tools: {
|
|
839
|
+
components: [
|
|
840
|
+
{ group: "@cyclonedx", name: "cdxgen", version: "test" },
|
|
841
|
+
],
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
const result = postProcess(bomNSData, { specVersion: 1.7 });
|
|
848
|
+
assert.deepStrictEqual(
|
|
849
|
+
result.bomJson.metadata.properties.filter((property) =>
|
|
850
|
+
property.name.startsWith("cdx:container:unpackaged"),
|
|
851
|
+
),
|
|
852
|
+
[
|
|
853
|
+
{ name: "cdx:container:unpackagedExecutableCount", value: "1" },
|
|
854
|
+
{ name: "cdx:container:unpackagedSharedLibraryCount", value: "1" },
|
|
855
|
+
],
|
|
856
|
+
);
|
|
857
|
+
});
|
|
858
|
+
|
|
375
859
|
it("postProcess fails for weak TLP when sensitive property values are present", () => {
|
|
376
860
|
const bomNSData = {
|
|
377
861
|
bomJson: {
|
|
@@ -136,6 +136,24 @@ function normalizeStandardsMetadata(rule) {
|
|
|
136
136
|
return Object.keys(normalized).length ? normalized : undefined;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
function normalizeDryRunSupport(rule) {
|
|
140
|
+
const rawValue =
|
|
141
|
+
typeof rule?.["dry-run-support"] === "string"
|
|
142
|
+
? rule["dry-run-support"]
|
|
143
|
+
: typeof rule?.dryRunSupport === "string"
|
|
144
|
+
? rule.dryRunSupport
|
|
145
|
+
: undefined;
|
|
146
|
+
if (rawValue !== undefined && !["no", "partial", "full"].includes(rawValue)) {
|
|
147
|
+
if (DEBUG_MODE) {
|
|
148
|
+
console.warn(
|
|
149
|
+
`Rule ${rule?.id || "unknown"} has invalid dry-run-support '${rawValue}'; defaulting to 'partial'`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return "partial";
|
|
153
|
+
}
|
|
154
|
+
return ["no", "partial", "full"].includes(rawValue) ? rawValue : "partial";
|
|
155
|
+
}
|
|
156
|
+
|
|
139
157
|
/**
|
|
140
158
|
* Helper: Check if property exists and equals expected value
|
|
141
159
|
* Usage: $hasProp(component, 'cdx:foo', 'bar')
|
|
@@ -247,6 +265,103 @@ function registerCdxHelpers(expression) {
|
|
|
247
265
|
expression.registerFunction("safeStr", (val) => {
|
|
248
266
|
return val === null || val === undefined ? "" : String(val).trim();
|
|
249
267
|
});
|
|
268
|
+
expression.registerFunction("parseSizeBytes", (val) => {
|
|
269
|
+
if (val === null || val === undefined || val === "") {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (typeof val === "number") {
|
|
273
|
+
return Number.isFinite(val) ? val : null;
|
|
274
|
+
}
|
|
275
|
+
const normalizedValue = String(val).trim();
|
|
276
|
+
if (!normalizedValue) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
const matchedValue = normalizedValue.match(
|
|
280
|
+
/^(\d+(?:\.\d+)?)\s*([kmgtpe]?i?b)?$/iu,
|
|
281
|
+
);
|
|
282
|
+
if (!matchedValue) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const numericValue = Number.parseFloat(matchedValue[1]);
|
|
286
|
+
if (!Number.isFinite(numericValue)) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const unit = (matchedValue[2] || "").toLowerCase();
|
|
290
|
+
const unitMap = {
|
|
291
|
+
b: 1,
|
|
292
|
+
kb: 1000,
|
|
293
|
+
kib: 1024,
|
|
294
|
+
mb: 1000 ** 2,
|
|
295
|
+
mib: 1024 ** 2,
|
|
296
|
+
gb: 1000 ** 3,
|
|
297
|
+
gib: 1024 ** 3,
|
|
298
|
+
tb: 1000 ** 4,
|
|
299
|
+
tib: 1024 ** 4,
|
|
300
|
+
pb: 1000 ** 5,
|
|
301
|
+
pib: 1024 ** 5,
|
|
302
|
+
eb: 1000 ** 6,
|
|
303
|
+
eib: 1024 ** 6,
|
|
304
|
+
};
|
|
305
|
+
const multiplier = unitMap[unit || "b"];
|
|
306
|
+
if (!multiplier) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
return numericValue * multiplier;
|
|
310
|
+
});
|
|
311
|
+
expression.registerFunction("firstNonEmpty", (...values) => {
|
|
312
|
+
for (const value of values) {
|
|
313
|
+
if (value === null || value === undefined) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (Array.isArray(value)) {
|
|
317
|
+
const candidate = value
|
|
318
|
+
.map((entry) =>
|
|
319
|
+
entry === null || entry === undefined ? "" : String(entry).trim(),
|
|
320
|
+
)
|
|
321
|
+
.filter(Boolean)
|
|
322
|
+
.join(", ");
|
|
323
|
+
if (candidate) {
|
|
324
|
+
return candidate;
|
|
325
|
+
}
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const candidate = String(value).trim();
|
|
329
|
+
if (candidate) {
|
|
330
|
+
return candidate;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return "";
|
|
334
|
+
});
|
|
335
|
+
expression.registerFunction("isDarwinSystemPath", (value) => {
|
|
336
|
+
if (typeof value !== "string") {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
const normalized = value.trim();
|
|
340
|
+
return (
|
|
341
|
+
normalized.startsWith("/bin/") ||
|
|
342
|
+
normalized.startsWith("/sbin/") ||
|
|
343
|
+
normalized.startsWith("/System/") ||
|
|
344
|
+
normalized.startsWith("/usr/bin/") ||
|
|
345
|
+
normalized.startsWith("/usr/libexec/") ||
|
|
346
|
+
normalized.startsWith("/usr/sbin/") ||
|
|
347
|
+
normalized.startsWith("/Library/Apple/System/") ||
|
|
348
|
+
normalized.startsWith("/System/Volumes/Preboot/Cryptexes/")
|
|
349
|
+
);
|
|
350
|
+
});
|
|
351
|
+
expression.registerFunction("isWindowsUserControlledPath", (value) => {
|
|
352
|
+
if (typeof value !== "string") {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
const normalized = value.trim().replaceAll("/", "\\").toLowerCase();
|
|
356
|
+
return (
|
|
357
|
+
normalized.includes("\\users\\") ||
|
|
358
|
+
normalized.includes("\\programdata\\") ||
|
|
359
|
+
normalized.includes("\\appdata\\") ||
|
|
360
|
+
normalized.includes("\\downloads\\") ||
|
|
361
|
+
normalized.includes("\\desktop\\") ||
|
|
362
|
+
normalized.includes("\\temp\\")
|
|
363
|
+
);
|
|
364
|
+
});
|
|
250
365
|
expression.registerFunction("auditComponents", (bomJson) =>
|
|
251
366
|
getAuditComponents(bomJson),
|
|
252
367
|
);
|
|
@@ -331,6 +446,7 @@ export async function loadRules(rulesDir) {
|
|
|
331
446
|
}
|
|
332
447
|
rule.severity = rule.severity || "medium";
|
|
333
448
|
rule.category = rule.category || "unknown";
|
|
449
|
+
rule.dryRunSupport = normalizeDryRunSupport(rule);
|
|
334
450
|
const attack = normalizeAttackMetadata(rule);
|
|
335
451
|
if (attack.tactics.length || attack.techniques.length) {
|
|
336
452
|
rule.attack = attack;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
|
|
3
|
+
import { recordEnvironmentRead } from "../../helpers/utils.js";
|
|
4
|
+
|
|
3
5
|
const PERMISSION_FLAGS = [
|
|
4
6
|
"--permission",
|
|
5
7
|
"--allow-fs-read",
|
|
@@ -57,8 +59,13 @@ const PERMISSION_FLAG_PATTERNS = PERMISSION_FLAGS.map(
|
|
|
57
59
|
|
|
58
60
|
export function auditEnvironment(env = process.env) {
|
|
59
61
|
const findings = [];
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
+
const envSource = env === process.env ? "process.env" : "env";
|
|
63
|
+
const readEnv = (varName) => {
|
|
64
|
+
recordEnvironmentRead(varName, { source: envSource });
|
|
65
|
+
return env[varName];
|
|
66
|
+
};
|
|
67
|
+
const nodeOptions = readEnv("NODE_OPTIONS") || "";
|
|
68
|
+
const cdxgenNodeOptions = readEnv("CDXGEN_NODE_OPTIONS") || "";
|
|
62
69
|
const hasPermission = PERMISSION_FLAG_PATTERNS.some((re) =>
|
|
63
70
|
re.test(nodeOptions),
|
|
64
71
|
);
|
|
@@ -93,7 +100,7 @@ export function auditEnvironment(env = process.env) {
|
|
|
93
100
|
});
|
|
94
101
|
}
|
|
95
102
|
// NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS verification; any other value is benign.
|
|
96
|
-
if (
|
|
103
|
+
if (readEnv("NODE_TLS_REJECT_UNAUTHORIZED") === "0") {
|
|
97
104
|
findings.push({
|
|
98
105
|
type: "environment-variable",
|
|
99
106
|
variable: "NODE_TLS_REJECT_UNAUTHORIZED",
|
|
@@ -107,6 +114,7 @@ export function auditEnvironment(env = process.env) {
|
|
|
107
114
|
|
|
108
115
|
for (const varName of RISKY_PRESENCE_VARS) {
|
|
109
116
|
if (env[varName] != null && env[varName] !== "") {
|
|
117
|
+
recordEnvironmentRead(varName, { source: envSource });
|
|
110
118
|
const messages = {
|
|
111
119
|
NODE_PATH:
|
|
112
120
|
"NODE_PATH is set and may cause unexpected modules to be loaded, enabling module-resolution poisoning.",
|
|
@@ -166,6 +174,7 @@ export function auditEnvironment(env = process.env) {
|
|
|
166
174
|
"JDK_JAVA_OPTIONS",
|
|
167
175
|
]) {
|
|
168
176
|
const jvmOptions = env[jvmVar] || "";
|
|
177
|
+
recordEnvironmentRead(jvmVar, { source: envSource });
|
|
169
178
|
if (jvmOptions) {
|
|
170
179
|
for (const pattern of JVM_CODE_EXECUTION_PATTERNS) {
|
|
171
180
|
if (pattern.test(jvmOptions)) {
|
|
@@ -184,6 +193,7 @@ export function auditEnvironment(env = process.env) {
|
|
|
184
193
|
// Proxy interception — informational
|
|
185
194
|
const activeProxy = PROXY_VARS.find((v) => env[v] != null && env[v] !== "");
|
|
186
195
|
if (activeProxy) {
|
|
196
|
+
recordEnvironmentRead(activeProxy, { source: envSource });
|
|
187
197
|
findings.push({
|
|
188
198
|
type: "network-interception",
|
|
189
199
|
variable: activeProxy,
|
|
@@ -197,6 +207,7 @@ export function auditEnvironment(env = process.env) {
|
|
|
197
207
|
// Credential exposure — detect any env var whose name follows a credential-naming convention.
|
|
198
208
|
for (const [varName, varValue] of Object.entries(env)) {
|
|
199
209
|
if (varValue && CREDENTIAL_VAR_PATTERN.test(varName)) {
|
|
210
|
+
recordEnvironmentRead(varName, { source: envSource });
|
|
200
211
|
findings.push({
|
|
201
212
|
type: "credential-exposure",
|
|
202
213
|
variable: varName,
|