@cyclonedx/cdxgen 12.4.0 → 12.4.2
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 -4
- package/bin/cdxgen.js +32 -11
- package/bin/convert.js +12 -8
- package/bin/evinse.js +15 -0
- package/bin/hbom.js +13 -8
- package/bin/repl.js +14 -10
- package/bin/validate.js +10 -13
- package/bin/verify.js +7 -29
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
- package/lib/audit/index.js +2 -1
- package/lib/cli/index.js +77 -16
- package/lib/cli/index.poku.js +197 -0
- package/lib/evinser/evinser.js +118 -3
- package/lib/helpers/bomUtils.js +155 -1
- package/lib/helpers/bomUtils.poku.js +79 -1
- package/lib/helpers/cbomutils.js +162 -2
- package/lib/helpers/cbomutils.poku.js +100 -0
- package/lib/helpers/ciParsers/githubActions.js +15 -3
- package/lib/helpers/ciParsers/githubActions.poku.js +52 -0
- package/lib/helpers/dosai.js +433 -0
- package/lib/helpers/dosai.poku.js +302 -0
- package/lib/helpers/dosaiParsers.js +103 -0
- package/lib/helpers/plugins.js +17 -16
- package/lib/helpers/protobom.js +53 -0
- package/lib/helpers/protobom.poku.js +44 -1
- package/lib/helpers/protobomLoader.js +43 -0
- package/lib/helpers/protobomLoader.poku.js +31 -0
- package/lib/helpers/utils.js +130 -1
- package/lib/helpers/utils.poku.js +295 -0
- package/lib/server/server.js +2 -1
- package/lib/stages/postgen/annotator.js +2 -1
- package/lib/stages/postgen/annotator.poku.js +28 -0
- package/lib/stages/postgen/postgen.js +219 -12
- package/lib/stages/postgen/postgen.poku.js +163 -0
- 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 +12 -12
- package/types/bin/repl.d.ts +1 -1
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +15 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomUtils.d.ts +8 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +1 -0
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/dosai.d.ts +24 -0
- package/types/lib/helpers/dosai.d.ts.map +1 -0
- package/types/lib/helpers/dosaiParsers.d.ts +8 -0
- package/types/lib/helpers/dosaiParsers.d.ts.map +1 -0
- package/types/lib/helpers/hbomAnalysis.d.ts +14 -0
- package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/helpers/hostTopology.d.ts.map +1 -1
- package/types/lib/helpers/plugins.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -0
- 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/utils.d.ts.map +1 -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/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.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/lib/audit/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import process from "node:process";
|
|
|
6
6
|
import { createBom } from "../cli/index.js";
|
|
7
7
|
import { DEFAULT_HBOM_AUDIT_CATEGORIES } from "../helpers/auditCategories.js";
|
|
8
8
|
import {
|
|
9
|
+
getCycloneDxFormat,
|
|
9
10
|
getNonCycloneDxErrorMessage,
|
|
10
11
|
isCycloneDxBom,
|
|
11
12
|
} from "../helpers/bomUtils.js";
|
|
@@ -230,7 +231,7 @@ export async function runDirectBomAuditFromBoms(inputBoms, options = {}) {
|
|
|
230
231
|
const findings = await auditBom(inputBom.bomJson, directAuditOptions);
|
|
231
232
|
results.push({
|
|
232
233
|
auditOptions: directAuditOptions,
|
|
233
|
-
bomFormat: inputBom.bomJson
|
|
234
|
+
bomFormat: getCycloneDxFormat(inputBom.bomJson),
|
|
234
235
|
findings,
|
|
235
236
|
serialNumber: inputBom.bomJson?.serialNumber,
|
|
236
237
|
source: inputBom.source,
|
package/lib/cli/index.js
CHANGED
|
@@ -41,8 +41,15 @@ import {
|
|
|
41
41
|
rewriteExtractedArchivePaths,
|
|
42
42
|
} from "../helpers/asarutils.js";
|
|
43
43
|
import { expandBomAuditCategories } from "../helpers/auditCategories.js";
|
|
44
|
+
import {
|
|
45
|
+
setCycloneDxFormat,
|
|
46
|
+
toCycloneDxSpecVersionString,
|
|
47
|
+
} from "../helpers/bomUtils.js";
|
|
44
48
|
import { parseCaxaMetadata } from "../helpers/caxa.js";
|
|
45
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
collectDosaiCryptoComponents,
|
|
51
|
+
collectSourceCryptoComponents,
|
|
52
|
+
} from "../helpers/cbomutils.js";
|
|
46
53
|
import {
|
|
47
54
|
CHROME_EXTENSION_PURL_TYPE,
|
|
48
55
|
collectChromeExtensionsFromPath,
|
|
@@ -54,6 +61,13 @@ import {
|
|
|
54
61
|
mergeServices,
|
|
55
62
|
trimComponents,
|
|
56
63
|
} from "../helpers/depsUtils.js";
|
|
64
|
+
import {
|
|
65
|
+
collectDosaiServicesFromMethods,
|
|
66
|
+
createDosaiMethodsSlice,
|
|
67
|
+
isDosaiDotnetLanguage,
|
|
68
|
+
normalizeDosaiServiceMap,
|
|
69
|
+
readDosaiJsonFile,
|
|
70
|
+
} from "../helpers/dosai.js";
|
|
57
71
|
import { GIT_COMMAND } from "../helpers/envcontext.js";
|
|
58
72
|
import {
|
|
59
73
|
createHbomDocument,
|
|
@@ -242,7 +256,6 @@ import {
|
|
|
242
256
|
enrichOSComponentsWithTrustData,
|
|
243
257
|
executeOsQuery,
|
|
244
258
|
getBinaryBom,
|
|
245
|
-
getDotnetSlices,
|
|
246
259
|
getOSPackages,
|
|
247
260
|
getPluginToolComponents,
|
|
248
261
|
} from "../managers/binary.js";
|
|
@@ -396,6 +409,27 @@ const hasExplicitProjectTypeSelection = (options, baseProjectType) => {
|
|
|
396
409
|
);
|
|
397
410
|
};
|
|
398
411
|
|
|
412
|
+
const hasDotnetProjectIndicators = (src, options = {}) => {
|
|
413
|
+
return Boolean(
|
|
414
|
+
getAllFiles(src, "**/*.{csproj,fsproj,vbproj,sln}", options)?.length,
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const shouldCollectDosaiCrypto = (src, options = {}) => {
|
|
419
|
+
const projectTypes = Array.isArray(options.projectType)
|
|
420
|
+
? options.projectType
|
|
421
|
+
: options.projectType
|
|
422
|
+
? [options.projectType]
|
|
423
|
+
: [];
|
|
424
|
+
if (projectTypes.some((projectType) => isDosaiDotnetLanguage(projectType))) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
if (!projectTypes.length || projectTypes.includes("universal")) {
|
|
428
|
+
return hasDotnetProjectIndicators(src, options);
|
|
429
|
+
}
|
|
430
|
+
return false;
|
|
431
|
+
};
|
|
432
|
+
|
|
399
433
|
const determineParentComponent = (options) => {
|
|
400
434
|
let parentComponent;
|
|
401
435
|
if (options.parentComponent && Object.keys(options.parentComponent).length) {
|
|
@@ -1393,13 +1427,16 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
|
|
|
1393
1427
|
// CycloneDX Json Template
|
|
1394
1428
|
const jsonTpl = {
|
|
1395
1429
|
bomFormat: "CycloneDX",
|
|
1396
|
-
specVersion:
|
|
1430
|
+
specVersion: toCycloneDxSpecVersionString(options.specVersion || "1.7"),
|
|
1397
1431
|
serialNumber: serialNum,
|
|
1398
1432
|
version: 1,
|
|
1399
1433
|
metadata: metadata,
|
|
1400
1434
|
components,
|
|
1401
1435
|
dependencies,
|
|
1402
1436
|
};
|
|
1437
|
+
setCycloneDxFormat(jsonTpl, jsonTpl.specVersion, {
|
|
1438
|
+
preserveLegacyBomFormat: true,
|
|
1439
|
+
});
|
|
1403
1440
|
if (services.length) {
|
|
1404
1441
|
jsonTpl.services = mergeServices([], services);
|
|
1405
1442
|
}
|
|
@@ -7735,6 +7772,7 @@ export async function createCsharpBom(path, options) {
|
|
|
7735
7772
|
}
|
|
7736
7773
|
}
|
|
7737
7774
|
const pkgNameVersions = {};
|
|
7775
|
+
let services = [];
|
|
7738
7776
|
if (csProjFiles.length) {
|
|
7739
7777
|
manifestFiles = manifestFiles.concat(csProjFiles);
|
|
7740
7778
|
// Parsing csproj is quite error-prone. Some project files may not have versions specified
|
|
@@ -7796,21 +7834,30 @@ export async function createCsharpBom(path, options) {
|
|
|
7796
7834
|
// Perform deep analysis using dosai
|
|
7797
7835
|
if (options.deep) {
|
|
7798
7836
|
const slicesFile = resolve(
|
|
7799
|
-
|
|
7837
|
+
options.depsSlicesFile
|
|
7838
|
+
? join(path, options.depsSlicesFile)
|
|
7839
|
+
: join(getTmpDir(), "dosai.json"),
|
|
7800
7840
|
);
|
|
7801
7841
|
// Create the slices file if it doesn't exist
|
|
7802
7842
|
if (!safeExistsSync(slicesFile)) {
|
|
7803
7843
|
thoughtLog(
|
|
7804
7844
|
"Alright, the next step is to invoke the dosai command to identify evidence of occurrences for various components.",
|
|
7805
7845
|
);
|
|
7806
|
-
const sliceResult =
|
|
7846
|
+
const sliceResult = createDosaiMethodsSlice(
|
|
7847
|
+
resolve(path),
|
|
7848
|
+
resolve(slicesFile),
|
|
7849
|
+
options,
|
|
7850
|
+
);
|
|
7807
7851
|
if (!sliceResult && DEBUG_MODE) {
|
|
7808
7852
|
console.log(
|
|
7809
7853
|
"Slicing with dosai was unsuccessful. Check the errors reported in the logs above.",
|
|
7810
7854
|
);
|
|
7811
7855
|
}
|
|
7812
7856
|
}
|
|
7813
|
-
pkgList = addEvidenceForDotnet(pkgList, slicesFile
|
|
7857
|
+
pkgList = addEvidenceForDotnet(pkgList, slicesFile);
|
|
7858
|
+
const methodsSlice = readDosaiJsonFile(slicesFile);
|
|
7859
|
+
const servicesMap = collectDosaiServicesFromMethods(methodsSlice, {});
|
|
7860
|
+
services = normalizeDosaiServiceMap(servicesMap);
|
|
7814
7861
|
}
|
|
7815
7862
|
}
|
|
7816
7863
|
// Parent dependency tree
|
|
@@ -7836,6 +7883,8 @@ export async function createCsharpBom(path, options) {
|
|
|
7836
7883
|
filename: manifestFiles.join(", "),
|
|
7837
7884
|
dependencies,
|
|
7838
7885
|
parentComponent,
|
|
7886
|
+
services,
|
|
7887
|
+
tools: options.deep ? getPluginToolComponents(["dosai"]) : [],
|
|
7839
7888
|
});
|
|
7840
7889
|
}
|
|
7841
7890
|
|
|
@@ -8347,6 +8396,15 @@ export async function createCryptoCertsBom(path, options) {
|
|
|
8347
8396
|
if (sourceCryptoComponents.length) {
|
|
8348
8397
|
pkgList.push(...sourceCryptoComponents);
|
|
8349
8398
|
}
|
|
8399
|
+
if (shouldCollectDosaiCrypto(path, options)) {
|
|
8400
|
+
const dosaiCryptoComponents = await collectDosaiCryptoComponents(
|
|
8401
|
+
path,
|
|
8402
|
+
options,
|
|
8403
|
+
);
|
|
8404
|
+
if (dosaiCryptoComponents.length) {
|
|
8405
|
+
pkgList.push(...dosaiCryptoComponents);
|
|
8406
|
+
}
|
|
8407
|
+
}
|
|
8350
8408
|
return {
|
|
8351
8409
|
bomJson: {
|
|
8352
8410
|
components: pkgList,
|
|
@@ -8392,16 +8450,19 @@ export function dedupeBom(options, components, parentComponent, dependencies) {
|
|
|
8392
8450
|
options,
|
|
8393
8451
|
parentComponent,
|
|
8394
8452
|
components,
|
|
8395
|
-
bomJson:
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8453
|
+
bomJson: setCycloneDxFormat(
|
|
8454
|
+
{
|
|
8455
|
+
specVersion: toCycloneDxSpecVersionString(options.specVersion || 1.7),
|
|
8456
|
+
serialNumber: serialNum,
|
|
8457
|
+
version: 1,
|
|
8458
|
+
metadata: addMetadata(parentComponent, options, {}),
|
|
8459
|
+
components,
|
|
8460
|
+
services: options.services || [],
|
|
8461
|
+
dependencies,
|
|
8462
|
+
},
|
|
8463
|
+
options.specVersion || 1.7,
|
|
8464
|
+
{ preserveLegacyBomFormat: true },
|
|
8465
|
+
),
|
|
8405
8466
|
};
|
|
8406
8467
|
}
|
|
8407
8468
|
|
package/lib/cli/index.poku.js
CHANGED
|
@@ -286,6 +286,86 @@ describe("CLI tests", () => {
|
|
|
286
286
|
);
|
|
287
287
|
assert.strictEqual(components[0].type, "data");
|
|
288
288
|
});
|
|
289
|
+
|
|
290
|
+
it("does not invoke dosai crypto analysis for non-.NET CBOM scans", async () => {
|
|
291
|
+
const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-cbom-non-dotnet-"));
|
|
292
|
+
const collectDosaiCryptoComponents = sinon.stub().resolves([]);
|
|
293
|
+
try {
|
|
294
|
+
const { createCryptoCertsBom } = await esmock("./index.js", {
|
|
295
|
+
"../helpers/cbomutils.js": {
|
|
296
|
+
collectDosaiCryptoComponents,
|
|
297
|
+
collectSourceCryptoComponents: sinon.stub().resolves([]),
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await createCryptoCertsBom(tempDir, {
|
|
302
|
+
projectType: ["js"],
|
|
303
|
+
specVersion: 1.7,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
sinon.assert.notCalled(collectDosaiCryptoComponents);
|
|
307
|
+
} finally {
|
|
308
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("invokes dosai crypto analysis for explicit .NET CBOM scans", async () => {
|
|
313
|
+
const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-cbom-dotnet-"));
|
|
314
|
+
const collectDosaiCryptoComponents = sinon.stub().resolves([
|
|
315
|
+
{
|
|
316
|
+
name: "sha-256",
|
|
317
|
+
type: "cryptographic-asset",
|
|
318
|
+
"bom-ref": "crypto/algorithm/sha-256@2.16.840.1.101.3.4.2.1",
|
|
319
|
+
cryptoProperties: {
|
|
320
|
+
assetType: "algorithm",
|
|
321
|
+
oid: "2.16.840.1.101.3.4.2.1",
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
]);
|
|
325
|
+
try {
|
|
326
|
+
const { createCryptoCertsBom } = await esmock("./index.js", {
|
|
327
|
+
"../helpers/cbomutils.js": {
|
|
328
|
+
collectDosaiCryptoComponents,
|
|
329
|
+
collectSourceCryptoComponents: sinon.stub().resolves([]),
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const bomData = await createCryptoCertsBom(tempDir, {
|
|
334
|
+
projectType: ["dotnet"],
|
|
335
|
+
specVersion: 1.7,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
sinon.assert.calledOnce(collectDosaiCryptoComponents);
|
|
339
|
+
assert.strictEqual(bomData.bomJson.components[0].name, "sha-256");
|
|
340
|
+
} finally {
|
|
341
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("invokes dosai crypto analysis when universal scans contain .NET project files", async () => {
|
|
346
|
+
const tempDir = mkdtempSync(
|
|
347
|
+
join(tmpdir(), "cdxgen-cbom-dotnet-indicator-"),
|
|
348
|
+
);
|
|
349
|
+
const collectDosaiCryptoComponents = sinon.stub().resolves([]);
|
|
350
|
+
try {
|
|
351
|
+
writeFileSync(join(tempDir, "app.csproj"), "<Project />");
|
|
352
|
+
const { createCryptoCertsBom } = await esmock("./index.js", {
|
|
353
|
+
"../helpers/cbomutils.js": {
|
|
354
|
+
collectDosaiCryptoComponents,
|
|
355
|
+
collectSourceCryptoComponents: sinon.stub().resolves([]),
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
await createCryptoCertsBom(tempDir, {
|
|
360
|
+
projectType: ["universal"],
|
|
361
|
+
specVersion: 1.7,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
sinon.assert.calledOnce(collectDosaiCryptoComponents);
|
|
365
|
+
} finally {
|
|
366
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
289
369
|
});
|
|
290
370
|
|
|
291
371
|
describe("distribution filters", () => {
|
|
@@ -623,6 +703,7 @@ describe("CLI tests", () => {
|
|
|
623
703
|
false,
|
|
624
704
|
);
|
|
625
705
|
},
|
|
706
|
+
isolateDepsSlicesFile: true,
|
|
626
707
|
expectedSpecVersion: (specVersion) => specVersion,
|
|
627
708
|
name: "cbom",
|
|
628
709
|
},
|
|
@@ -651,6 +732,13 @@ describe("CLI tests", () => {
|
|
|
651
732
|
fixtureRoot,
|
|
652
733
|
`${scenario.name}-${specVersion}.spdx.json`,
|
|
653
734
|
);
|
|
735
|
+
const depsSlicesPath = join(
|
|
736
|
+
fixtureRoot,
|
|
737
|
+
`${scenario.name}-${specVersion}.deps.slices.json`,
|
|
738
|
+
);
|
|
739
|
+
const depsSlicesArgs = scenario.isolateDepsSlicesFile
|
|
740
|
+
? ["--deps-slices-file", depsSlicesPath]
|
|
741
|
+
: [];
|
|
654
742
|
const generateResult = spawnSync(
|
|
655
743
|
process.execPath,
|
|
656
744
|
[
|
|
@@ -660,6 +748,7 @@ describe("CLI tests", () => {
|
|
|
660
748
|
jsonPath,
|
|
661
749
|
"--spec-version",
|
|
662
750
|
specVersion,
|
|
751
|
+
...depsSlicesArgs,
|
|
663
752
|
"--export-proto",
|
|
664
753
|
"--proto-bin-file",
|
|
665
754
|
protoPath,
|
|
@@ -738,6 +827,114 @@ describe("CLI tests", () => {
|
|
|
738
827
|
scenario.assertRoundTrip(roundTrippedBom);
|
|
739
828
|
}
|
|
740
829
|
}
|
|
830
|
+
assert.strictEqual(
|
|
831
|
+
existsSync(join(repoDir, "deps.slices.json")),
|
|
832
|
+
false,
|
|
833
|
+
"protobuf round-trip tests must not leave deps.slices.json in the repository root",
|
|
834
|
+
);
|
|
835
|
+
} finally {
|
|
836
|
+
rmSync(join(repoDir, "deps.slices.json"), { force: true });
|
|
837
|
+
rmSync(fixtureRoot, { force: true, recursive: true });
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
describe("CycloneDX 2.0 JSON output", () => {
|
|
843
|
+
it("generates valid experimental 2.0-dev JSON with specFormat", () => {
|
|
844
|
+
const fixtureRoot = mkdtempSync(join(tmpdir(), "cdxgen-json20-"));
|
|
845
|
+
try {
|
|
846
|
+
const jsonPath = join(fixtureRoot, "bom-2.0.json");
|
|
847
|
+
const generateResult = spawnSync(
|
|
848
|
+
process.execPath,
|
|
849
|
+
[
|
|
850
|
+
join(repoDir, "bin", "cdxgen.js"),
|
|
851
|
+
"-t",
|
|
852
|
+
"js",
|
|
853
|
+
mcpFixtureDir,
|
|
854
|
+
"-o",
|
|
855
|
+
jsonPath,
|
|
856
|
+
"--spec-version",
|
|
857
|
+
"2.0",
|
|
858
|
+
"--no-banner",
|
|
859
|
+
],
|
|
860
|
+
{
|
|
861
|
+
cwd: repoDir,
|
|
862
|
+
encoding: "utf8",
|
|
863
|
+
env: buildMinimalCliEnv(),
|
|
864
|
+
},
|
|
865
|
+
);
|
|
866
|
+
assert.strictEqual(
|
|
867
|
+
generateResult.status,
|
|
868
|
+
0,
|
|
869
|
+
`${generateResult.stdout}${generateResult.stderr}`,
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
const generatedBom = JSON.parse(readFileSync(jsonPath, "utf8"));
|
|
873
|
+
assert.strictEqual(generatedBom.specFormat, "CycloneDX");
|
|
874
|
+
assert.strictEqual(generatedBom.bomFormat, undefined);
|
|
875
|
+
assert.strictEqual(generatedBom.specVersion, "2.0");
|
|
876
|
+
assert.ok(Array.isArray(generatedBom.metadata?.tools?.components));
|
|
877
|
+
|
|
878
|
+
const validateResult = spawnSync(
|
|
879
|
+
process.execPath,
|
|
880
|
+
[
|
|
881
|
+
join(repoDir, "bin", "validate.js"),
|
|
882
|
+
"-i",
|
|
883
|
+
jsonPath,
|
|
884
|
+
"--fail-severity",
|
|
885
|
+
"critical",
|
|
886
|
+
"--no-deep",
|
|
887
|
+
"--report",
|
|
888
|
+
"json",
|
|
889
|
+
],
|
|
890
|
+
{
|
|
891
|
+
cwd: repoDir,
|
|
892
|
+
encoding: "utf8",
|
|
893
|
+
env: buildMinimalCliEnv(),
|
|
894
|
+
},
|
|
895
|
+
);
|
|
896
|
+
assert.strictEqual(
|
|
897
|
+
validateResult.status,
|
|
898
|
+
0,
|
|
899
|
+
`${validateResult.stdout}${validateResult.stderr}`,
|
|
900
|
+
);
|
|
901
|
+
} finally {
|
|
902
|
+
rmSync(fixtureRoot, { force: true, recursive: true });
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
it("rejects experimental 2.0-dev protobuf export until cdx-proto supports it", () => {
|
|
907
|
+
const fixtureRoot = mkdtempSync(join(tmpdir(), "cdxgen-proto20-"));
|
|
908
|
+
try {
|
|
909
|
+
const jsonPath = join(fixtureRoot, "bom-2.0.json");
|
|
910
|
+
const protoPath = join(fixtureRoot, "bom-2.0.cdx");
|
|
911
|
+
const generateResult = spawnSync(
|
|
912
|
+
process.execPath,
|
|
913
|
+
[
|
|
914
|
+
join(repoDir, "bin", "cdxgen.js"),
|
|
915
|
+
"-t",
|
|
916
|
+
"js",
|
|
917
|
+
mcpFixtureDir,
|
|
918
|
+
"-o",
|
|
919
|
+
jsonPath,
|
|
920
|
+
"--spec-version",
|
|
921
|
+
"2.0",
|
|
922
|
+
"--export-proto",
|
|
923
|
+
"--proto-bin-file",
|
|
924
|
+
protoPath,
|
|
925
|
+
"--no-banner",
|
|
926
|
+
],
|
|
927
|
+
{
|
|
928
|
+
cwd: repoDir,
|
|
929
|
+
encoding: "utf8",
|
|
930
|
+
env: buildMinimalCliEnv(),
|
|
931
|
+
},
|
|
932
|
+
);
|
|
933
|
+
assert.strictEqual(generateResult.status, 1);
|
|
934
|
+
assert.match(
|
|
935
|
+
`${generateResult.stdout}${generateResult.stderr}`,
|
|
936
|
+
/CycloneDX 2\.0 is not currently supported for protobuf export/i,
|
|
937
|
+
);
|
|
741
938
|
} finally {
|
|
742
939
|
rmSync(fixtureRoot, { force: true, recursive: true });
|
|
743
940
|
}
|
package/lib/evinser/evinser.js
CHANGED
|
@@ -4,7 +4,19 @@ import process from "node:process";
|
|
|
4
4
|
|
|
5
5
|
import { PackageURL } from "packageurl-js";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
collectDosaiCryptoComponents,
|
|
9
|
+
findCryptoAlgos,
|
|
10
|
+
} from "../helpers/cbomutils.js";
|
|
11
|
+
import { mergeServices } from "../helpers/depsUtils.js";
|
|
12
|
+
import {
|
|
13
|
+
collectDosaiDataFlowFrames,
|
|
14
|
+
collectDosaiPurlEvidence,
|
|
15
|
+
collectDosaiServicesFromMethods,
|
|
16
|
+
createDosaiDataFlowSlice,
|
|
17
|
+
createDosaiMethodsSlice,
|
|
18
|
+
isDosaiDotnetLanguage,
|
|
19
|
+
} from "../helpers/dosai.js";
|
|
8
20
|
import { parseOccurrenceEvidenceLocation } from "../helpers/evidenceUtils.js";
|
|
9
21
|
import {
|
|
10
22
|
collectGradleDependencies,
|
|
@@ -230,6 +242,8 @@ export async function createSlice(
|
|
|
230
242
|
language = "python";
|
|
231
243
|
} else if (PROJECT_TYPE_ALIASES.scala.includes(language)) {
|
|
232
244
|
language = "scala";
|
|
245
|
+
} else if (isDosaiDotnetLanguage(language)) {
|
|
246
|
+
language = "csharp";
|
|
233
247
|
}
|
|
234
248
|
if (
|
|
235
249
|
PROJECT_TYPE_ALIASES.swift.includes(language) &&
|
|
@@ -270,6 +284,33 @@ export async function createSlice(
|
|
|
270
284
|
}
|
|
271
285
|
return { tempDir: sliceOutputDir, tempDirOwned, slicesFile };
|
|
272
286
|
}
|
|
287
|
+
if (isDosaiDotnetLanguage(language)) {
|
|
288
|
+
console.log(
|
|
289
|
+
`Creating ${sliceType} slice for ${resolve(filePath)} using dosai. Please wait ...`,
|
|
290
|
+
);
|
|
291
|
+
const sliceResult =
|
|
292
|
+
sliceType === "data-flow"
|
|
293
|
+
? createDosaiDataFlowSlice(
|
|
294
|
+
resolve(filePath),
|
|
295
|
+
resolve(slicesFile),
|
|
296
|
+
options,
|
|
297
|
+
)
|
|
298
|
+
: createDosaiMethodsSlice(
|
|
299
|
+
resolve(filePath),
|
|
300
|
+
resolve(slicesFile),
|
|
301
|
+
options,
|
|
302
|
+
);
|
|
303
|
+
if (!sliceResult) {
|
|
304
|
+
console.warn(
|
|
305
|
+
`Unable to generate ${sliceType} slice using dosai. Check if this is a supported .NET project.`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
tempDir: sliceOutputDir,
|
|
310
|
+
tempDirOwned,
|
|
311
|
+
slicesFile,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
273
314
|
console.log(
|
|
274
315
|
`Creating ${sliceType} slice for ${resolve(filePath)}. Please wait ...`,
|
|
275
316
|
);
|
|
@@ -393,6 +434,9 @@ export function purlToLanguage(purl, filePath) {
|
|
|
393
434
|
case "gem":
|
|
394
435
|
language = "ruby";
|
|
395
436
|
break;
|
|
437
|
+
case "nuget":
|
|
438
|
+
language = "csharp";
|
|
439
|
+
break;
|
|
396
440
|
case "generic":
|
|
397
441
|
language = "c";
|
|
398
442
|
}
|
|
@@ -474,6 +518,77 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
474
518
|
// Load any existing purl-location information from the sbom.
|
|
475
519
|
// For eg: cdxgen populates this information for javascript projects
|
|
476
520
|
let { purlLocationMap, purlImportsMap } = initFromSbom(components, language);
|
|
521
|
+
if (isDosaiDotnetLanguage(language)) {
|
|
522
|
+
if (options.profile === "research") {
|
|
523
|
+
options.withDataFlow = true;
|
|
524
|
+
options.includeCrypto = true;
|
|
525
|
+
}
|
|
526
|
+
if (
|
|
527
|
+
options.usagesSlicesFile &&
|
|
528
|
+
usableSlicesFile(options.usagesSlicesFile)
|
|
529
|
+
) {
|
|
530
|
+
usageSlice = JSON.parse(
|
|
531
|
+
fs.readFileSync(options.usagesSlicesFile, "utf-8"),
|
|
532
|
+
);
|
|
533
|
+
usagesSlicesFile = options.usagesSlicesFile;
|
|
534
|
+
} else {
|
|
535
|
+
retMap = await createSlice(language, dirPath, "usages", options);
|
|
536
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
537
|
+
usageSlice = JSON.parse(fs.readFileSync(retMap.slicesFile, "utf-8"));
|
|
538
|
+
usagesSlicesFile = retMap.slicesFile;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (usageSlice && Object.keys(usageSlice).length) {
|
|
542
|
+
const dosaiEvidence = collectDosaiPurlEvidence(usageSlice, components);
|
|
543
|
+
for (const [purl, locations] of Object.entries(
|
|
544
|
+
dosaiEvidence.purlLocationMap,
|
|
545
|
+
)) {
|
|
546
|
+
purlLocationMap[purl] ??= new Set();
|
|
547
|
+
for (const location of locations) {
|
|
548
|
+
purlLocationMap[purl].add(location);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
servicesMap = collectDosaiServicesFromMethods(usageSlice, servicesMap);
|
|
552
|
+
userDefinedTypesMap = {};
|
|
553
|
+
}
|
|
554
|
+
if (options.withDataFlow) {
|
|
555
|
+
if (
|
|
556
|
+
options.dataFlowSlicesFile &&
|
|
557
|
+
safeExistsSync(options.dataFlowSlicesFile)
|
|
558
|
+
) {
|
|
559
|
+
dataFlowSlicesFile = options.dataFlowSlicesFile;
|
|
560
|
+
dataFlowSlice = JSON.parse(
|
|
561
|
+
fs.readFileSync(options.dataFlowSlicesFile, "utf-8"),
|
|
562
|
+
);
|
|
563
|
+
} else {
|
|
564
|
+
retMap = await createSlice(language, dirPath, "data-flow", options);
|
|
565
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
566
|
+
dataFlowSlicesFile = retMap.slicesFile;
|
|
567
|
+
dataFlowSlice = JSON.parse(
|
|
568
|
+
fs.readFileSync(retMap.slicesFile, "utf-8"),
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (dataFlowSlice && Object.keys(dataFlowSlice).length) {
|
|
573
|
+
dataFlowFrames = collectDosaiDataFlowFrames(dataFlowSlice, components);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (options.includeCrypto) {
|
|
577
|
+
cryptoComponents = await collectDosaiCryptoComponents(dirPath, options);
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
usagesSlicesFile,
|
|
581
|
+
dataFlowSlicesFile,
|
|
582
|
+
purlLocationMap,
|
|
583
|
+
servicesMap,
|
|
584
|
+
dataFlowFrames,
|
|
585
|
+
tempDir: retMap?.tempDir,
|
|
586
|
+
tempDirOwned: retMap?.tempDirOwned,
|
|
587
|
+
userDefinedTypesMap,
|
|
588
|
+
cryptoComponents,
|
|
589
|
+
cryptoGeneratePurls,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
477
592
|
// Do reachables first so that usages slicing can reuse the atom file
|
|
478
593
|
// We need reachables slicing even when trying to infer crypto packages
|
|
479
594
|
if (options.withReachables || options.includeCrypto) {
|
|
@@ -1472,8 +1587,8 @@ export function createEvinseFile(sliceArtefacts, options) {
|
|
|
1472
1587
|
properties: servicesMap[serviceName].properties,
|
|
1473
1588
|
});
|
|
1474
1589
|
}
|
|
1475
|
-
// Add to existing services
|
|
1476
|
-
bomJson.services = (bomJson.services || []
|
|
1590
|
+
// Add to existing services while preserving CycloneDX uniqueItems validity
|
|
1591
|
+
bomJson.services = mergeServices(bomJson.services || [], services);
|
|
1477
1592
|
servicesPresent = true;
|
|
1478
1593
|
}
|
|
1479
1594
|
// Add the crypto components to the components list
|