@cyclonedx/cdxgen 10.5.2 → 10.6.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 CHANGED
@@ -10,12 +10,16 @@
10
10
 
11
11
  ![cdxgen logo](./docs/_media/cdxgen.png)
12
12
 
13
- cdxgen is a CLI tool, library, [REPL](./ADVANCED.md), and server to create a valid and compliant [CycloneDX][cyclonedx-homepage] Bill of Materials (BOM) containing an aggregate of all project dependencies for C/C++, Node.js, PHP, Python, Ruby, Rust, Java, .Net, Dart, Haskell, Elixir, and Go projects in JSON format. CycloneDX is a full-stack BOM specification that is easily created, human and machine-readable, and simple to parse. The tool supports CycloneDX specification versions from 1.4 - 1.6.
13
+ cdxgen is a CLI tool, library, [REPL](./ADVANCED.md), and server to create a valid and compliant [CycloneDX][cyclonedx-homepage] Bill of Materials (BOM) containing an aggregate of all project dependencies in JSON format. CycloneDX is a full-stack BOM specification that is easily created, human and machine-readable, and simple to parse. The tool supports CycloneDX specification versions from 1.4 - 1.6.
14
14
 
15
- When used with plugins:
15
+ Supported BOM formats:
16
16
 
17
- - cdxgen could generate an OBOM for Linux docker images and even VMs running Linux or Windows operating systems
18
- - cdxgen also includes an evinse tool to generate component evidence, CBOM, and SaaSBOM for some languages
17
+ - Software (SBOM) - For many languages and container images.
18
+ - Cryptography (CBOM) - For Java and Python projects.
19
+ - Operations (OBOM) - For Linux container images and VMs running Linux or Windows operating systems.
20
+ - Software-as-a-Service (SaaSBOM) - For Java, Python, JavaScript, TypeScript, and PHP projects.
21
+ - Attestations (CDXA) - Generate SBOM with templates for multiple standards. Sign the BOM document at a granular level to improve authenticity.
22
+ - Vulnerability Disclosure Report (VDR) - Use cdxgen with [OWASP depscan](https://github.com/owasp-dep-scan/dep-scan) to automate the generation of VDR at scale.
19
23
 
20
24
  ## Why cdxgen?
21
25
 
@@ -23,11 +27,19 @@ Most SBOM tools are like simple barcode scanners. For easy applications, they ca
23
27
 
24
28
  <img src="./docs/_media/why-cdxgen.jpg" alt="why cdxgen" width="256">
25
29
 
30
+ Our philosophy:
31
+
32
+ - Explainability: Don't list, but explain with evidence.
33
+ - Precision: Try using multiple techniques to improve precision, even if it takes extra time.
34
+ - Personas: Cater to the needs of a range of personas such as security researchers, compliance auditors, developers, and SOC.
35
+ - Lifecycle: Support BOM generation for various product lifecycles.
36
+
26
37
  ## Documentation
27
38
 
28
- Please visit our [documentation site][docs-homepage] for detailed usage, tutorials and support documentation.
39
+ Please visit our [documentation site][docs-homepage] for detailed usage, tutorials, and support documentation.
29
40
 
30
41
  Sections include:
42
+
31
43
  - [Getting Started][docs-homepage]
32
44
  - [CLI Usage][docs-cli]
33
45
  - [Server Usage][docs-server]
@@ -37,7 +49,6 @@ Sections include:
37
49
  - [Permissions][docs-permissions]
38
50
  - [Support (Enterprise & Community)][docs-support]
39
51
 
40
-
41
52
  ### Automatic usage detection
42
53
 
43
54
  For node.js projects, lock files are parsed initially, so the SBOM would include all dependencies, including dev ones. An AST parser powered by babel-parser is then used to detect packages that are imported and used by non-test code. Such imported packages would automatically set their scope property to `required` in the resulting SBOM. You can turn off this analysis by passing the argument `--no-babel`. Scope property would then be set based on the `dev` attribute in the lock file.
@@ -472,8 +483,6 @@ Use the [CycloneDX CLI][cyclonedx-cli-github] tool for advanced use cases such a
472
483
 
473
484
  Permission to modify and redistribute is granted under the terms of the Apache 2.0 license. See the [LICENSE][github-license] file for the full license.
474
485
 
475
-
476
-
477
486
  ## Integration as library
478
487
 
479
488
  cdxgen is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) and could be imported and used with both deno and Node.js >= 20
@@ -498,21 +507,22 @@ const dbody = await submitBom(args, bomNSData.bomJson);
498
507
 
499
508
  Please check out our [contribute to CycloneDX/cdxgen documentation][github-contribute] if you are interested in helping.
500
509
 
501
-
502
510
  Before raising a PR, please run the following commands.
503
511
 
504
512
  ```bash
513
+ corepack enable
514
+ corepack pnpm install
505
515
  # Generate types using jsdoc syntax
506
- npm run gen-types
516
+ corepack pnpm run gen-types
507
517
  # Run biomejs formatter and linter with auto fix
508
- npm run lint
518
+ corepack pnpm run lint
509
519
  # Run jest tests
510
- npm test
520
+ corepack pnpm test
511
521
  ```
512
522
 
513
-
514
523
  <!-- LINK LABELS -->
515
524
  <!-- Badges -->
525
+
516
526
  [badge-github-contributors]: https://img.shields.io/github/contributors/cyclonedx/cdxgen
517
527
  [badge-github-license]: https://img.shields.io/github/license/cyclonedx/cdxgen
518
528
  [badge-github-releases]: https://img.shields.io/github/v/release/cyclonedx/cdxgen
@@ -522,6 +532,7 @@ npm test
522
532
  [badge-swh]: https://archive.softwareheritage.org/badge/origin/https://github.com/CycloneDX/cdxgen/
523
533
 
524
534
  <!-- cdxgen github project -->
535
+
525
536
  [github-contribute]: https://github.com/CycloneDX/cdxgen/contribute
526
537
  [github-contributors]: https://github.com/CycloneDX/cdxgen/graphs/contributors
527
538
  [github-issues]: https://github.com/CycloneDX/cdxgen/issues
@@ -529,6 +540,7 @@ npm test
529
540
  [github-releases]: https://github.com/CycloneDX/cdxgen/releases
530
541
 
531
542
  <!-- cdxgen documentation site -->
543
+
532
544
  [docs-homepage]: https://cyclonedx.github.io/cdxgen
533
545
  [docs-advanced-usage]: https://cyclonedx.github.io/cdxgen/#/ADVANCED
534
546
  [docs-cli]: https://cyclonedx.github.io/cdxgen/#/CLI
@@ -539,6 +551,7 @@ npm test
539
551
  [docs-support]: https://cyclonedx.github.io/cdxgen/#/PROJECT_TYPES
540
552
 
541
553
  <!-- web links-->
554
+
542
555
  [appthreat-homepage]: https://www.appthreat.com
543
556
  [cyclonedx-homepage]: https://cyclonedx.org
544
557
  [cyclonedx-cli-github]: https://github.com/CycloneDX/cyclonedx-cli
@@ -552,4 +565,4 @@ npm test
552
565
  [npmjs-cdxgen]: https://www.npmjs.com/package/@cyclonedx/cdxgen
553
566
  [podman-github-rootless]: https://github.com/containers/podman/blob/master/docs/tutorials/rootless_tutorial.md
554
567
  [podman-github-remote]: https://github.com/containers/podman/blob/master/docs/tutorials/mac_win_client.md
555
- [swh-cdxgen]: https://archive.softwareheritage.org/browse/origin/?origin_url=https://github.com/CycloneDX/cdxgen
568
+ [swh-cdxgen]: https://archive.softwareheritage.org/browse/origin/?origin_url=https://github.com/CycloneDX/cdxgen
package/analyzer.js CHANGED
@@ -292,7 +292,7 @@ export const findJSImportsExports = async (src, deep) => {
292
292
  const errFiles = [];
293
293
  try {
294
294
  const promiseMap = await getAllSrcJSAndTSFiles(src, deep);
295
- const srcFiles = promiseMap.flatMap((d) => d);
295
+ const srcFiles = promiseMap.flat();
296
296
  for (const file of srcFiles) {
297
297
  try {
298
298
  parseFileASTTree(src, file, allImports, allExports);
package/index.js CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  FETCH_LICENSE,
36
36
  LEIN_CMD,
37
37
  MAX_BUFFER,
38
+ PREFER_MAVEN_DEPS_TREE,
38
39
  SWIFT_CMD,
39
40
  TIMEOUT_MS,
40
41
  addEvidenceForDotnet,
@@ -66,6 +67,7 @@ import {
66
67
  getSwiftPackageMetadata,
67
68
  getTimestamp,
68
69
  includeMavenTestScope,
70
+ isValidIriReference,
69
71
  parseBazelActionGraph,
70
72
  parseBazelSkyframe,
71
73
  parseBdistMetadata,
@@ -726,7 +728,9 @@ function addExternalReferences(opkg) {
726
728
  }
727
729
  }
728
730
  }
729
- return externalReferences;
731
+ return externalReferences
732
+ .map((reference) => ({ ...reference, url: reference.url.trim() }))
733
+ .filter((reference) => isValidIriReference(reference.url));
730
734
  }
731
735
 
732
736
  /**
@@ -1185,6 +1189,7 @@ export async function createJavaBom(path, options) {
1185
1189
  // Support for tracking all the tools that created the BOM
1186
1190
  // For java, this would correctly include the cyclonedx maven plugin.
1187
1191
  let tools = undefined;
1192
+ let possible_misses = false;
1188
1193
  // war/ear mode
1189
1194
  if (path.endsWith(".war") || path.endsWith(".jar")) {
1190
1195
  // Check if the file exists
@@ -1225,11 +1230,16 @@ export async function createJavaBom(path, options) {
1225
1230
  pomFiles?.length &&
1226
1231
  !["scala", "sbt", "gradle"].includes(options.projectType)
1227
1232
  ) {
1233
+ let result = undefined;
1228
1234
  const cdxMavenPlugin =
1229
1235
  process.env.CDX_MAVEN_PLUGIN ||
1230
1236
  "org.cyclonedx:cyclonedx-maven-plugin:2.8.0";
1231
1237
  const cdxMavenGoal = process.env.CDX_MAVEN_GOAL || "makeAggregateBom";
1232
- let mvnArgs = [`${cdxMavenPlugin}:${cdxMavenGoal}`, "-DoutputName=bom"];
1238
+ let mvnArgs = [
1239
+ "-fn",
1240
+ `${cdxMavenPlugin}:${cdxMavenGoal}`,
1241
+ "-DoutputName=bom",
1242
+ ];
1233
1243
  if (includeMavenTestScope) {
1234
1244
  mvnArgs.push("-DincludeTestScope=true");
1235
1245
  }
@@ -1247,6 +1257,7 @@ export async function createJavaBom(path, options) {
1247
1257
  if (options.specVersion === 1.4) {
1248
1258
  mvnArgs = mvnArgs.concat("-DschemaVersion=1.4");
1249
1259
  }
1260
+ const firstPom = pomFiles.length ? pomFiles[0] : undefined;
1250
1261
  for (const f of pomFiles) {
1251
1262
  const basePath = dirname(f);
1252
1263
  const settingsXml = join(basePath, "settings.xml");
@@ -1268,44 +1279,97 @@ export async function createJavaBom(path, options) {
1268
1279
  jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
1269
1280
  }
1270
1281
  }
1271
- console.log(`Executing '${mavenCmd} ${mvnArgs.join(" ")}' in`, basePath);
1272
- let result = spawnSync(mavenCmd, mvnArgs, {
1273
- cwd: basePath,
1274
- shell: true,
1275
- encoding: "utf-8",
1276
- timeout: TIMEOUT_MS,
1277
- maxBuffer: MAX_BUFFER,
1278
- });
1279
- // Check if the cyclonedx plugin created the required bom.json file
1280
- // Sometimes the plugin fails silently for complex maven projects
1281
- bomJsonFiles = getAllFiles(path, "**/target/*.json", options);
1282
- // Check if the bom json files got created in a directory other than target
1283
- if (!bomJsonFiles.length) {
1284
- bomJsonFiles = getAllFiles(path, "**/bom*.json", options);
1285
- }
1286
- const bomGenerated = bomJsonFiles.length;
1287
- if (!bomGenerated || result.status !== 0 || result.error) {
1288
- const tempDir = mkdtempSync(join(tmpdir(), "cdxmvn-"));
1289
- const tempMvnTree = join(tempDir, "mvn-tree.txt");
1290
- let mvnTreeArgs = ["dependency:tree", `-DoutputFile=${tempMvnTree}`];
1291
- if (process.env.MVN_ARGS) {
1292
- const addArgs = process.env.MVN_ARGS.split(" ");
1293
- mvnTreeArgs = mvnTreeArgs.concat(addArgs);
1294
- }
1282
+ // Use the cyclonedx maven plugin if there is no preference for maven deps tree
1283
+ if (!PREFER_MAVEN_DEPS_TREE) {
1295
1284
  console.log(
1296
- `Fallback to executing ${mavenCmd} ${mvnTreeArgs.join(" ")}`,
1285
+ `Executing '${mavenCmd} ${mvnArgs.join(" ")}' in`,
1286
+ basePath,
1297
1287
  );
1298
- result = spawnSync(mavenCmd, mvnTreeArgs, {
1288
+ result = spawnSync(mavenCmd, mvnArgs, {
1299
1289
  cwd: basePath,
1300
1290
  shell: true,
1301
1291
  encoding: "utf-8",
1302
1292
  timeout: TIMEOUT_MS,
1303
1293
  maxBuffer: MAX_BUFFER,
1304
1294
  });
1295
+ // Check if the cyclonedx plugin created the required bom.json file
1296
+ // Sometimes the plugin fails silently for complex maven projects
1297
+ bomJsonFiles = getAllFiles(path, "**/target/*.json", options);
1298
+ // Check if the bom json files got created in a directory other than target
1299
+ if (!bomJsonFiles.length) {
1300
+ bomJsonFiles = getAllFiles(
1301
+ path,
1302
+ "target/**/*{cdx,bom}*.json",
1303
+ options,
1304
+ );
1305
+ }
1306
+ }
1307
+ // Also check if the user has a preference for maven deps tree command
1308
+ if (
1309
+ PREFER_MAVEN_DEPS_TREE ||
1310
+ !bomJsonFiles.length ||
1311
+ result?.status !== 0 ||
1312
+ result?.error
1313
+ ) {
1314
+ const tempDir = mkdtempSync(join(tmpdir(), "cdxmvn-"));
1315
+ const tempMvnTree = join(tempDir, "mvn-tree.txt");
1316
+ const tempMvnParentTree = join(tempDir, "mvn-parent-tree.txt");
1317
+ let mvnTreeArgs = ["dependency:tree", `-DoutputFile=${tempMvnTree}`];
1318
+ if (process.env.MVN_ARGS) {
1319
+ const addArgs = process.env.MVN_ARGS.split(" ");
1320
+ mvnTreeArgs = mvnTreeArgs.concat(addArgs);
1321
+ }
1322
+ // Automatically use settings.xml to improve the success for fallback
1323
+ if (existsSync(settingsXml)) {
1324
+ mvnTreeArgs.push("-s");
1325
+ mvnTreeArgs.push(settingsXml);
1326
+ }
1327
+ // For the first pom alone, we need to execute first in non-recursive mode to capture
1328
+ // the parent component. Then, we execute all of them in recursive mode
1329
+ if (f === firstPom) {
1330
+ result = spawnSync(
1331
+ "mvn",
1332
+ ["dependency:tree", "-N", `-DoutputFile=${tempMvnParentTree}`],
1333
+ {
1334
+ cwd: basePath,
1335
+ shell: true,
1336
+ encoding: "utf-8",
1337
+ timeout: TIMEOUT_MS,
1338
+ maxBuffer: MAX_BUFFER,
1339
+ },
1340
+ );
1341
+ if (result.status === 0) {
1342
+ if (existsSync(tempMvnParentTree)) {
1343
+ const mvnTreeString = readFileSync(tempMvnParentTree, {
1344
+ encoding: "utf-8",
1345
+ });
1346
+ const parsedList = parseMavenTree(mvnTreeString, f);
1347
+ const dlist = parsedList.pkgList;
1348
+ const tmpParentComponent = dlist.splice(0, 1)[0];
1349
+ tmpParentComponent.type = "application";
1350
+ parentComponent = tmpParentComponent;
1351
+ parentComponent.components = [];
1352
+ }
1353
+ }
1354
+ }
1355
+ console.log(`Executing 'mvn ${mvnTreeArgs.join(" ")}' in ${basePath}`);
1356
+ // Prefer the built-in maven
1357
+ result = spawnSync(
1358
+ PREFER_MAVEN_DEPS_TREE ? "mvn" : mavenCmd,
1359
+ mvnTreeArgs,
1360
+ {
1361
+ cwd: basePath,
1362
+ shell: true,
1363
+ encoding: "utf-8",
1364
+ timeout: TIMEOUT_MS,
1365
+ maxBuffer: MAX_BUFFER,
1366
+ },
1367
+ );
1305
1368
  if (result.status !== 0 || result.error) {
1369
+ possible_misses = true;
1306
1370
  // Our approach to recursively invoking the maven plugin for each sub-module is bound to result in failures
1307
1371
  // These could be due to a range of reasons that are covered below.
1308
- if (pomFiles.length === 1 || DEBUG_MODE) {
1372
+ if (pomFiles.length === 1 || DEBUG_MODE || PREFER_MAVEN_DEPS_TREE) {
1309
1373
  console.error(result.stdout, result.stderr);
1310
1374
  console.log("The above build errors could be due to:\n");
1311
1375
  if (
@@ -1319,7 +1383,10 @@ export async function createJavaBom(path, options) {
1319
1383
  } else if (
1320
1384
  result.stdout &&
1321
1385
  (result.stdout.includes("Could not resolve dependencies") ||
1322
- result.stdout.includes("no dependency information available"))
1386
+ result.stdout.includes("no dependency information available") ||
1387
+ result.stdout.includes(
1388
+ "The following artifacts could not be resolved",
1389
+ ))
1323
1390
  ) {
1324
1391
  console.log(
1325
1392
  "1. Try building the project with 'mvn package -Dmaven.test.skip=true' using the correct version of Java and maven before invoking cdxgen.",
@@ -1358,95 +1425,120 @@ export async function createJavaBom(path, options) {
1358
1425
  const mvnTreeString = readFileSync(tempMvnTree, {
1359
1426
  encoding: "utf-8",
1360
1427
  });
1361
- const parsedList = parseMavenTree(mvnTreeString);
1428
+ const parsedList = parseMavenTree(mvnTreeString, f);
1362
1429
  const dlist = parsedList.pkgList;
1363
- parentComponent = dlist.splice(0, 1)[0];
1364
- parentComponent.type = "application";
1430
+ const tmpParentComponent = dlist.splice(0, 1)[0];
1431
+ tmpParentComponent.type = "application";
1365
1432
  if (dlist?.length) {
1366
1433
  pkgList = pkgList.concat(dlist);
1367
1434
  }
1435
+ // Retain the parent hierarchy
1436
+ if (!Object.keys(parentComponent).length) {
1437
+ parentComponent = tmpParentComponent;
1438
+ parentComponent.components = [];
1439
+ } else {
1440
+ parentComponent.components.push(tmpParentComponent);
1441
+ }
1368
1442
  if (parsedList.dependenciesList && parsedList.dependenciesList) {
1369
- dependencies = dependencies.concat(parsedList.dependenciesList);
1443
+ dependencies = mergeDependencies(
1444
+ dependencies,
1445
+ parsedList.dependenciesList,
1446
+ tmpParentComponent,
1447
+ );
1370
1448
  }
1371
1449
  unlinkSync(tempMvnTree);
1372
1450
  }
1373
1451
  }
1374
1452
  }
1375
1453
  } // for
1376
- for (const abjson of bomJsonFiles) {
1377
- let bomJsonObj = undefined;
1378
- try {
1379
- if (DEBUG_MODE) {
1380
- console.log(`Extracting data from generated bom file ${abjson}`);
1381
- }
1382
- bomJsonObj = JSON.parse(
1383
- readFileSync(abjson, {
1384
- encoding: "utf-8",
1385
- }),
1386
- );
1387
- if (bomJsonObj) {
1388
- if (
1389
- !tools &&
1390
- bomJsonObj.metadata &&
1391
- bomJsonObj.metadata.tools &&
1392
- Array.isArray(bomJsonObj.metadata.tools)
1393
- ) {
1394
- tools = bomJsonObj.metadata.tools;
1395
- }
1396
- if (
1397
- bomJsonObj.metadata?.component &&
1398
- !Object.keys(parentComponent).length
1399
- ) {
1400
- parentComponent = bomJsonObj.metadata.component;
1401
- options.parentComponent = parentComponent;
1402
- pkgList = [];
1454
+ // Locate and parse all bom.json files from the maven plugin
1455
+ if (!PREFER_MAVEN_DEPS_TREE) {
1456
+ for (const abjson of bomJsonFiles) {
1457
+ let bomJsonObj = undefined;
1458
+ try {
1459
+ if (DEBUG_MODE) {
1460
+ console.log(`Extracting data from generated bom file ${abjson}`);
1403
1461
  }
1404
- if (bomJsonObj.components) {
1405
- // Inject evidence into the components. #994
1406
- if (options.specVersion >= 1.5) {
1407
- // maven would usually generate a target directory closest to the pom.xml
1408
- // I am sure there would be cases where this assumption is not true :)
1409
- const srcPomFile = join(dirname(abjson), "..", "pom.xml");
1410
- for (const acomp of bomJsonObj.components) {
1411
- if (!acomp.evidence) {
1412
- acomp.evidence = {
1413
- identity: {
1414
- field: "purl",
1415
- confidence: 0.8,
1416
- methods: [
1417
- {
1418
- technique: "manifest-analysis",
1419
- confidence: 0.8,
1420
- value: srcPomFile,
1421
- },
1422
- ],
1423
- },
1424
- };
1425
- }
1426
- if (!acomp.properties) {
1427
- acomp.properties = [];
1462
+ bomJsonObj = JSON.parse(
1463
+ readFileSync(abjson, {
1464
+ encoding: "utf-8",
1465
+ }),
1466
+ );
1467
+ if (bomJsonObj) {
1468
+ if (
1469
+ !tools &&
1470
+ bomJsonObj.metadata &&
1471
+ bomJsonObj.metadata.tools &&
1472
+ Array.isArray(bomJsonObj.metadata.tools)
1473
+ ) {
1474
+ tools = bomJsonObj.metadata.tools;
1475
+ }
1476
+ if (
1477
+ bomJsonObj.metadata?.component &&
1478
+ !Object.keys(parentComponent).length
1479
+ ) {
1480
+ parentComponent = bomJsonObj.metadata.component;
1481
+ options.parentComponent = parentComponent;
1482
+ pkgList = [];
1483
+ }
1484
+ if (bomJsonObj.components) {
1485
+ // Inject evidence into the components. #994
1486
+ if (options.specVersion >= 1.5) {
1487
+ // maven would usually generate a target directory closest to the pom.xml
1488
+ // I am sure there would be cases where this assumption is not true :)
1489
+ const srcPomFile = join(dirname(abjson), "..", "pom.xml");
1490
+ for (const acomp of bomJsonObj.components) {
1491
+ if (!acomp.evidence) {
1492
+ acomp.evidence = {
1493
+ identity: {
1494
+ field: "purl",
1495
+ confidence: 0.8,
1496
+ methods: [
1497
+ {
1498
+ technique: "manifest-analysis",
1499
+ confidence: 0.8,
1500
+ value: srcPomFile,
1501
+ },
1502
+ ],
1503
+ },
1504
+ };
1505
+ }
1506
+ if (!acomp.properties) {
1507
+ acomp.properties = [];
1508
+ }
1509
+ acomp.properties.push({
1510
+ name: "SrcFile",
1511
+ value: srcPomFile,
1512
+ });
1428
1513
  }
1429
- acomp.properties.push({
1430
- name: "SrcFile",
1431
- value: srcPomFile,
1432
- });
1433
1514
  }
1515
+ pkgList = pkgList.concat(bomJsonObj.components);
1516
+ }
1517
+ if (bomJsonObj.dependencies) {
1518
+ dependencies = mergeDependencies(
1519
+ dependencies,
1520
+ bomJsonObj.dependencies,
1521
+ parentComponent,
1522
+ );
1434
1523
  }
1435
- pkgList = pkgList.concat(bomJsonObj.components);
1436
1524
  }
1437
- if (bomJsonObj.dependencies) {
1438
- dependencies = mergeDependencies(
1439
- dependencies,
1440
- bomJsonObj.dependencies,
1441
- parentComponent,
1442
- );
1525
+ } catch (err) {
1526
+ if (options.failOnError || DEBUG_MODE) {
1527
+ console.log(err);
1528
+ options.failOnError && process.exit(1);
1443
1529
  }
1444
1530
  }
1445
- } catch (err) {
1446
- if (options.failOnError || DEBUG_MODE) {
1447
- console.log(err);
1448
- options.failOnError && process.exit(1);
1449
- }
1531
+ }
1532
+ }
1533
+ if (possible_misses) {
1534
+ if (!DEBUG_MODE) {
1535
+ console.warn(
1536
+ "Multiple errors occurred while building this project with maven. The SBOM is therefore incomplete!",
1537
+ );
1538
+ } else if (!PREFER_MAVEN_DEPS_TREE) {
1539
+ console.log(
1540
+ "Try generating an SBOM with the maven dependency tree plugin. Set the environment variable PREFER_MAVEN_DEPS_TREE to true to enable this.",
1541
+ );
1450
1542
  }
1451
1543
  }
1452
1544
  if (pkgList) {
@@ -4708,12 +4800,7 @@ export async function createCsharpBom(path, options) {
4708
4800
  for (const f of filesToRestore) {
4709
4801
  const buildCmd =
4710
4802
  options.projectType === "dotnet-framework" ? "nuget" : "dotnet";
4711
- if (DEBUG_MODE) {
4712
- const basePath = dirname(f);
4713
- console.log(`Executing '${buildCmd} restore' in ${basePath}`);
4714
- }
4715
- const result = spawnSync(
4716
- buildCmd,
4803
+ const buildArgs =
4717
4804
  options.projectType === "dotnet-framework"
4718
4805
  ? [
4719
4806
  "restore",
@@ -4724,23 +4811,38 @@ export async function createCsharpBom(path, options) {
4724
4811
  "-Verbosity",
4725
4812
  "quiet",
4726
4813
  ]
4727
- : ["restore", "--force", "--ignore-failed-sources", f],
4728
- {
4729
- cwd: path,
4730
- encoding: "utf-8",
4731
- },
4732
- );
4733
- if (DEBUG_MODE && (result.status !== 0 || result.error)) {
4734
- console.error(
4735
- `Restore has failed. Check if ${buildCmd} is installed and available in PATH.`,
4736
- );
4737
- console.log(
4738
- "Authenticate with any private registries such as Azure Artifacts feed before running cdxgen.",
4739
- );
4814
+ : ["restore", "--force", "--ignore-failed-sources", f];
4815
+ if (DEBUG_MODE) {
4816
+ const basePath = dirname(f);
4740
4817
  console.log(
4741
- "Alternatively, try using the unofficial `ghcr.io/appthreat/cdxgen-dotnet6:v10` container image, which bundles nuget (mono) and a range of dotnet SDKs.",
4818
+ `Executing '${buildCmd} ${buildArgs.join(" ")}' in ${basePath}`,
4742
4819
  );
4743
- console.log(result.stderr);
4820
+ }
4821
+ const result = spawnSync(buildCmd, buildArgs, {
4822
+ cwd: path,
4823
+ encoding: "utf-8",
4824
+ env: { ...process.env, DOTNET_ROLL_FORWARD: "Major" },
4825
+ });
4826
+ if (DEBUG_MODE && (result.status !== 0 || result.error)) {
4827
+ if (result?.stderr?.includes("To install missing framework")) {
4828
+ console.log(
4829
+ "This project requires a specific version of dotnet sdk to be installed. The cdxgen container image bundles dotnet SDK 8.0, which might be incompatible.",
4830
+ );
4831
+ console.log(
4832
+ "Try using the unofficial `ghcr.io/appthreat/cdxgen-dotnet6:v10` or `ghcr.io/appthreat/cdxgen-dotnet7:v10` container images.",
4833
+ );
4834
+ } else {
4835
+ console.error(
4836
+ `Restore has failed. Check if ${buildCmd} is installed and available in PATH.`,
4837
+ );
4838
+ console.log(
4839
+ "Authenticate with any private registries such as Azure Artifacts feed before running cdxgen.",
4840
+ );
4841
+ console.log(
4842
+ "Alternatively, try using the unofficial `ghcr.io/appthreat/cdxgen-dotnet6:v10` container image, which bundles nuget (mono) and a range of dotnet SDKs.",
4843
+ );
4844
+ }
4845
+ console.log(result.stdout, result.stderr);
4744
4846
  options.failOnError && process.exit(1);
4745
4847
  }
4746
4848
  }
@@ -4932,7 +5034,7 @@ export async function createCsharpBom(path, options) {
4932
5034
  parentComponent = retMap.parentComponent;
4933
5035
  }
4934
5036
  }
4935
- if (retMap?.pkgList.length) {
5037
+ if (retMap?.pkgList?.length) {
4936
5038
  pkgList = pkgList.concat(retMap.pkgList);
4937
5039
  }
4938
5040
  if (retMap.dependencies?.length) {
@@ -5062,10 +5164,11 @@ export function mergeDependencies(
5062
5164
  }
5063
5165
  if (adep["dependsOn"]) {
5064
5166
  for (const eachDepends of adep["dependsOn"]) {
5065
- if (
5066
- parentRef &&
5067
- eachDepends.toLowerCase() !== parentRef.toLowerCase()
5068
- ) {
5167
+ if (parentRef) {
5168
+ if (eachDepends.toLowerCase() !== parentRef.toLowerCase()) {
5169
+ deps_map[adep.ref].add(eachDepends);
5170
+ }
5171
+ } else {
5069
5172
  deps_map[adep.ref].add(eachDepends);
5070
5173
  }
5071
5174
  }
@@ -5132,6 +5235,14 @@ export function trimComponents(components) {
5132
5235
  }
5133
5236
  }
5134
5237
  }
5238
+ // If the component is required in any of the child projects, then make it required
5239
+ if (
5240
+ existingComponent?.scope !== "required" &&
5241
+ comp?.scope === "required"
5242
+ ) {
5243
+ existingComponent.scope = "required";
5244
+ keyCache[key] = existingComponent;
5245
+ }
5135
5246
  if (compProps.length) {
5136
5247
  existingComponent.properties = compProps;
5137
5248
  keyCache[key] = existingComponent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "10.5.2",
3
+ "version": "10.6.1",
4
4
  "description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
5
5
  "homepage": "http://github.com/cyclonedx/cdxgen",
6
6
  "author": "Prabhu Subramanian <prabhu@appthreat.com>",
@@ -47,14 +47,6 @@
47
47
  "evinse": "bin/evinse.js",
48
48
  "cdx-verify": "bin/verify.js"
49
49
  },
50
- "scripts": {
51
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --inject-globals false docker.test.js utils.test.js display.test.js postgen.test.js",
52
- "watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --inject-globals false",
53
- "lint:check": "biome check *",
54
- "lint": "biome check --apply *",
55
- "lint:errors": "biome check * --diagnostic-level=error",
56
- "gen-types": "npx -p typescript tsc"
57
- },
58
50
  "engines": {
59
51
  "node": ">=20"
60
52
  },
@@ -90,7 +82,8 @@
90
82
  "tar": "^6.2.1",
91
83
  "uuid": "^9.0.1",
92
84
  "xml-js": "^1.6.11",
93
- "yargs": "^17.7.2"
85
+ "yargs": "^17.7.2",
86
+ "validate-iri": "^1.0.1"
94
87
  },
95
88
  "optionalDependencies": {
96
89
  "@appthreat/atom": "2.0.12",
@@ -109,10 +102,23 @@
109
102
  "sequelize": "^6.37.3",
110
103
  "sqlite3": "^5.1.7"
111
104
  },
112
- "files": ["*.js", "bin/", "data/", "types/"],
105
+ "files": [
106
+ "*.js",
107
+ "bin/",
108
+ "data/",
109
+ "types/"
110
+ ],
113
111
  "devDependencies": {
114
- "@biomejs/biome": "1.7.3",
112
+ "@biomejs/biome": "1.8.0",
115
113
  "jest": "^29.7.0",
116
114
  "typescript": "^5.4.5"
115
+ },
116
+ "scripts": {
117
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --inject-globals false docker.test.js utils.test.js display.test.js postgen.test.js",
118
+ "watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --inject-globals false",
119
+ "lint:check": "biome check",
120
+ "lint": "biome check --fix",
121
+ "lint:errors": "biome check --diagnostic-level=error",
122
+ "gen-types": "npx -p typescript tsc"
117
123
  }
118
- }
124
+ }
@@ -104,8 +104,8 @@ export function prepareDB(options: any): Promise<{
104
104
  count: number;
105
105
  }>;
106
106
  findAndCountAll<M_17 extends import("sequelize").Model<any, any>>(this: import("sequelize").ModelStatic<M_17>, options: {
107
- type?: string;
108
107
  attributes?: import("sequelize").FindAttributeOptions;
108
+ type?: string;
109
109
  plain?: boolean;
110
110
  logging?: boolean | ((sql: string, timing?: number) => void);
111
111
  where?: import("sequelize").WhereOptions<import("sequelize").Attributes<M_17>>;
@@ -127,7 +127,7 @@ export function prepareDB(options: any): Promise<{
127
127
  bind?: import("sequelize").BindOrReplacements;
128
128
  instance?: import("sequelize").Model<any, any>;
129
129
  mapToModel?: boolean;
130
- retry?: import("retry-as-promised").Options;
130
+ retry?: import(".pnpm/retry-as-promised@7.0.4/node_modules/retry-as-promised").Options;
131
131
  fieldMap?: import("sequelize").FieldMap;
132
132
  benchmark?: boolean;
133
133
  transaction?: import("sequelize").Transaction;
@@ -333,8 +333,8 @@ export function prepareDB(options: any): Promise<{
333
333
  count: number;
334
334
  }>;
335
335
  findAndCountAll<M_17 extends import("sequelize").Model<any, any>>(this: import("sequelize").ModelStatic<M_17>, options: {
336
- type?: string;
337
336
  attributes?: import("sequelize").FindAttributeOptions;
337
+ type?: string;
338
338
  plain?: boolean;
339
339
  logging?: boolean | ((sql: string, timing?: number) => void);
340
340
  where?: import("sequelize").WhereOptions<import("sequelize").Attributes<M_17>>;
@@ -356,7 +356,7 @@ export function prepareDB(options: any): Promise<{
356
356
  bind?: import("sequelize").BindOrReplacements;
357
357
  instance?: import("sequelize").Model<any, any>;
358
358
  mapToModel?: boolean;
359
- retry?: import("retry-as-promised").Options;
359
+ retry?: import(".pnpm/retry-as-promised@7.0.4/node_modules/retry-as-promised").Options;
360
360
  fieldMap?: import("sequelize").FieldMap;
361
361
  benchmark?: boolean;
362
362
  transaction?: import("sequelize").Transaction;
@@ -562,8 +562,8 @@ export function prepareDB(options: any): Promise<{
562
562
  count: number;
563
563
  }>;
564
564
  findAndCountAll<M_17 extends import("sequelize").Model<any, any>>(this: import("sequelize").ModelStatic<M_17>, options: {
565
- type?: string;
566
565
  attributes?: import("sequelize").FindAttributeOptions;
566
+ type?: string;
567
567
  plain?: boolean;
568
568
  logging?: boolean | ((sql: string, timing?: number) => void);
569
569
  where?: import("sequelize").WhereOptions<import("sequelize").Attributes<M_17>>;
@@ -585,7 +585,7 @@ export function prepareDB(options: any): Promise<{
585
585
  bind?: import("sequelize").BindOrReplacements;
586
586
  instance?: import("sequelize").Model<any, any>;
587
587
  mapToModel?: boolean;
588
- retry?: import("retry-as-promised").Options;
588
+ retry?: import(".pnpm/retry-as-promised@7.0.4/node_modules/retry-as-promised").Options;
589
589
  fieldMap?: import("sequelize").FieldMap;
590
590
  benchmark?: boolean;
591
591
  transaction?: import("sequelize").Transaction;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":"AA2tBA;;;;;;;;GAQG;AACH,gFAFW,MAAM,SAchB;AA4TD;;;;;;;GAOG;AACH,mCALW,MAAM,qBAiEhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM;;;;EAKhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM;;;;EAkBhB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BA64BhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BAochB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BAgWhB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,8BA8ThB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAqIhB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAiDhB;AAED;;;;;GAKG;AACH,mCAHW,MAAM,qBA+KhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM,qBAsHhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM,qBA2BhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,qBA2BhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,qBA2BhB;AAED;;;;;GAKG;AACH,0CAHW,MAAM,qBAuBhB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,8BAqDhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM,8BA4ChB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,qBA2BhB;AAED;;;;;GAKG;AACH,qCAHW,MAAM,8BAwFhB;AAED;;;;;GAKG;AACH,iDAHW,MAAM,qBA8ThB;AAED;;;;;GAKG;AACH,mCAHW,MAAM,qBAwJhB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAmFhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BA+VhB;AAED;;;;;GAKG;AACH,2CAHW,MAAM;;;;;;;;;;;;;;;;;;;;GAoChB;AAED;;;;;;;;KA8DC;AAED;;;;;;GAMG;AACH,yDAmCC;AAED;;;;;;;;;GASG;AACH,2GA6BC;AAED;;;;;GAKG;AACH,0CAHW,MAAM,8BAoZhB;AAED;;;;;GAKG;AACH,iCAHW,MAAM,8BAkUhB;AAED;;;;;GAKG;AACH,gCAHW,MAAM,qBAuQhB;AAED;;;;;GAKG;AACH,qEAyFC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":"AA+tBA;;;;;;;;GAQG;AACH,gFAFW,MAAM,SAchB;AA4TD;;;;;;;GAOG;AACH,mCALW,MAAM,qBAiEhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM;;;;EAKhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM;;;;EAkBhB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAq+BhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BAochB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BAgWhB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,8BA8ThB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAqIhB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAiDhB;AAED;;;;;GAKG;AACH,mCAHW,MAAM,qBA+KhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM,qBAsHhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM,qBA2BhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,qBA2BhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,qBA2BhB;AAED;;;;;GAKG;AACH,0CAHW,MAAM,qBAuBhB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,8BAqDhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM,8BA4ChB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,qBA2BhB;AAED;;;;;GAKG;AACH,qCAHW,MAAM,8BAwFhB;AAED;;;;;GAKG;AACH,iDAHW,MAAM,qBA8ThB;AAED;;;;;GAKG;AACH,mCAHW,MAAM,qBAwJhB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAmFhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BAyWhB;AAED;;;;;GAKG;AACH,2CAHW,MAAM;;;;;;;;;;;;;;;;;;;;GAoChB;AAED;;;;;;;;KA+DC;AAED;;;;;;GAMG;AACH,yDA2CC;AAED;;;;;;;;;GASG;AACH,2GA6BC;AAED;;;;;GAKG;AACH,0CAHW,MAAM,8BAoZhB;AAED;;;;;GAKG;AACH,iCAHW,MAAM,8BAkUhB;AAED;;;;;GAKG;AACH,gCAHW,MAAM,qBAuQhB;AAED;;;;;GAKG;AACH,qEAyFC"}
package/types/utils.d.ts CHANGED
@@ -172,17 +172,11 @@ export function parsePom(pomFile: any): {
172
172
  /**
173
173
  * Parse maven tree output
174
174
  * @param {string} rawOutput Raw string output
175
+ * @param {string} pomFile .pom file for evidence
176
+ *
177
+ * @returns {Object} Object containing packages and dependencies
175
178
  */
176
- export function parseMavenTree(rawOutput: string): {
177
- pkgList?: undefined;
178
- dependenciesList?: undefined;
179
- } | {
180
- pkgList: any[];
181
- dependenciesList: {
182
- ref: string;
183
- dependsOn: any;
184
- }[];
185
- };
179
+ export function parseMavenTree(rawOutput: string, pomFile: string): any;
186
180
  /**
187
181
  * Parse gradle dependencies output
188
182
  * @param {string} rawOutput Raw string output
@@ -1119,6 +1113,18 @@ export function addEvidenceForDotnet(pkgList: any, slicesFile: any): any;
1119
1113
  * @returns {Object} pkgFilesMap Object with package name and list of files
1120
1114
  */
1121
1115
  export function parseMakeDFile(dfile: string): any;
1116
+ /**
1117
+ * Function to validate an externalReference URL for conforming to the JSON schema or bomLink
1118
+ * https://github.com/CycloneDX/cyclonedx-core-java/blob/75575318b268dda9e2a290761d7db11b4f414255/src/main/resources/bom-1.5.schema.json#L1140
1119
+ * https://datatracker.ietf.org/doc/html/rfc3987#section-2.2
1120
+ * https://cyclonedx.org/capabilities/bomlink/
1121
+ *
1122
+ * @param {String} iri IRI to validate
1123
+ *
1124
+ * @returns {Boolean} Flag indicating whether the supplied URL is valid or not
1125
+ *
1126
+ */
1127
+ export function isValidIriReference(iri: string): boolean;
1122
1128
  export const dirNameStr: string;
1123
1129
  export const isWin: boolean;
1124
1130
  export const isMac: boolean;
@@ -1129,6 +1135,7 @@ export const TIMEOUT_MS: number;
1129
1135
  export const MAX_BUFFER: number;
1130
1136
  export let metadata_cache: {};
1131
1137
  export const includeMavenTestScope: boolean;
1138
+ export const PREFER_MAVEN_DEPS_TREE: boolean;
1132
1139
  export const FETCH_LICENSE: boolean;
1133
1140
  export const SEARCH_MAVEN_ORG: boolean;
1134
1141
  export let JAVA_CMD: string;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../utils.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../utils.js"],"names":[],"mappings":"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"}
package/utils.js CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  satisfies,
47
47
  valid,
48
48
  } from "semver";
49
+ import { IriValidationStrategy, validateIri } from "validate-iri";
49
50
  import { xml2js } from "xml-js";
50
51
  import { getTreeWithPlugin } from "./piptree.js";
51
52
 
@@ -121,6 +122,11 @@ export const includeMavenTestScope =
121
122
  !process.env.CDX_MAVEN_INCLUDE_TEST_SCOPE ||
122
123
  ["true", "1"].includes(process.env.CDX_MAVEN_INCLUDE_TEST_SCOPE);
123
124
 
125
+ // Whether to use the native maven dependency tree command
126
+ export const PREFER_MAVEN_DEPS_TREE =
127
+ process.env.PREFER_MAVEN_DEPS_TREE &&
128
+ ["true", "1"].includes(process.env.PREFER_MAVEN_DEPS_TREE);
129
+
124
130
  // Whether license information should be fetched
125
131
  export const FETCH_LICENSE =
126
132
  process.env.FETCH_LICENSE &&
@@ -1435,15 +1441,29 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
1435
1441
  if (!yamlObj) {
1436
1442
  return {};
1437
1443
  }
1444
+ let lockfileVersion = yamlObj.lockfileVersion;
1445
+ try {
1446
+ lockfileVersion = Number.parseFloat(lockfileVersion, 10);
1447
+ } catch (e) {
1448
+ // ignore parse errors
1449
+ }
1438
1450
  // This logic matches the pnpm list command to include only direct dependencies
1439
1451
  if (ppurl !== "") {
1440
- const ddeps = yamlObj.dependencies || {};
1452
+ // In lock file version 9, direct dependencies is under importers
1453
+ const ddeps =
1454
+ lockfileVersion >= 9
1455
+ ? yamlObj.importers["."]?.dependencies || {}
1456
+ : yamlObj.dependencies || {};
1441
1457
  const ddeplist = [];
1442
1458
  for (const dk of Object.keys(ddeps)) {
1443
1459
  let version = ddeps[dk];
1444
1460
  if (typeof version === "object" && version.version) {
1445
1461
  version = version.version;
1446
1462
  }
1463
+ // version: 3.0.1(ajv@8.14.0)
1464
+ if (version?.includes("(")) {
1465
+ version = version.split("(")[0];
1466
+ }
1447
1467
  const dpurl = new PackageURL(
1448
1468
  "npm",
1449
1469
  "",
@@ -1459,13 +1479,9 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
1459
1479
  dependsOn: ddeplist,
1460
1480
  });
1461
1481
  }
1462
- let lockfileVersion = yamlObj.lockfileVersion;
1463
- try {
1464
- lockfileVersion = Number.parseInt(lockfileVersion, 10);
1465
- } catch (e) {
1466
- // ignore parse errors
1467
- }
1468
1482
  const packages = yamlObj.packages || {};
1483
+ // snapshots is a new key under lockfile version 9
1484
+ const snapshots = yamlObj.snapshots || {};
1469
1485
  const pkgKeys = Object.keys(packages);
1470
1486
  for (const k in pkgKeys) {
1471
1487
  // Eg: @babel/code-frame/7.10.1
@@ -1477,20 +1493,40 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
1477
1493
  }
1478
1494
  const parts = fullName.split("/");
1479
1495
  const integrity = packages[pkgKeys[k]].resolution.integrity;
1480
- const deps = packages[pkgKeys[k]].dependencies || [];
1496
+ // In lock file version 9, dependencies is under snapshots
1497
+ const deps =
1498
+ packages[pkgKeys[k]].dependencies ||
1499
+ snapshots[pkgKeys[k]]?.dependencies ||
1500
+ [];
1481
1501
  const scope = packages[pkgKeys[k]].dev === true ? "optional" : undefined;
1482
1502
  if (parts?.length) {
1483
1503
  let name = "";
1484
1504
  let version = "";
1485
1505
  let group = "";
1486
- if (lockfileVersion >= 6 && fullName.includes("@")) {
1506
+ const hasBin = packages[pkgKeys[k]]?.hasBin;
1507
+ const deprecatedMessage = packages[pkgKeys[k]]?.deprecated;
1508
+ if (lockfileVersion >= 9 && fullName.includes("@")) {
1509
+ group = parts.length > 1 ? parts[0] : "";
1510
+ const tmpA = parts[parts.length - 1].split("@");
1511
+ if (tmpA.length > 1) {
1512
+ name = tmpA[0];
1513
+ version = tmpA[1];
1514
+ }
1515
+ if (version?.includes("(")) {
1516
+ version = version.split("(")[0];
1517
+ }
1518
+ } else if (
1519
+ lockfileVersion >= 6 &&
1520
+ lockfileVersion < 9 &&
1521
+ fullName.includes("@")
1522
+ ) {
1487
1523
  const tmpA = parts[parts.length - 1].split("@");
1488
1524
  group = parts[0];
1489
1525
  if (parts.length === 2 && tmpA.length > 1) {
1490
1526
  name = tmpA[0];
1491
1527
  version = tmpA[1];
1492
1528
  } else {
1493
- console.log(parts, fullName);
1529
+ console.log("Missed", parts, fullName);
1494
1530
  }
1495
1531
  } else {
1496
1532
  if (parts.length === 2) {
@@ -1519,12 +1555,24 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
1519
1555
  null,
1520
1556
  ).toString();
1521
1557
  const deplist = [];
1522
- for (const dpkgName of Object.keys(deps)) {
1558
+ for (let dpkgName of Object.keys(deps)) {
1559
+ let vers = deps[dpkgName];
1560
+ if (vers?.includes("(")) {
1561
+ vers = vers.split("(")[0];
1562
+ }
1563
+ // string-width-cjs: string-width@4.2.3
1564
+ if (dpkgName.endsWith("-cjs")) {
1565
+ const tmpB = vers.split("@");
1566
+ if (tmpB.length > 1) {
1567
+ dpkgName = tmpB[0];
1568
+ vers = tmpB[1];
1569
+ }
1570
+ }
1523
1571
  const dpurlString = new PackageURL(
1524
1572
  "npm",
1525
1573
  "",
1526
1574
  dpkgName,
1527
- deps[dpkgName],
1575
+ vers,
1528
1576
  null,
1529
1577
  null,
1530
1578
  ).toString();
@@ -1534,6 +1582,24 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
1534
1582
  ref: decodeURIComponent(purlString),
1535
1583
  dependsOn: deplist,
1536
1584
  });
1585
+ const properties = [
1586
+ {
1587
+ name: "SrcFile",
1588
+ value: pnpmLock,
1589
+ },
1590
+ ];
1591
+ if (hasBin) {
1592
+ properties.push({
1593
+ name: "cdx:npm:has_binary",
1594
+ value: `${hasBin}`,
1595
+ });
1596
+ }
1597
+ if (deprecatedMessage) {
1598
+ properties.push({
1599
+ name: "cdx:npm:deprecation_notice",
1600
+ value: deprecatedMessage,
1601
+ });
1602
+ }
1537
1603
  pkgList.push({
1538
1604
  group: group,
1539
1605
  name: name,
@@ -1542,12 +1608,7 @@ export async function parsePnpmLock(pnpmLock, parentComponent = null) {
1542
1608
  "bom-ref": decodeURIComponent(purlString),
1543
1609
  scope,
1544
1610
  _integrity: integrity,
1545
- properties: [
1546
- {
1547
- name: "SrcFile",
1548
- value: pnpmLock,
1549
- },
1550
- ],
1611
+ properties,
1551
1612
  evidence: {
1552
1613
  identity: {
1553
1614
  field: "purl",
@@ -1784,8 +1845,11 @@ export function parsePom(pomFile) {
1784
1845
  /**
1785
1846
  * Parse maven tree output
1786
1847
  * @param {string} rawOutput Raw string output
1848
+ * @param {string} pomFile .pom file for evidence
1849
+ *
1850
+ * @returns {Object} Object containing packages and dependencies
1787
1851
  */
1788
- export function parseMavenTree(rawOutput) {
1852
+ export function parseMavenTree(rawOutput, pomFile) {
1789
1853
  if (!rawOutput) {
1790
1854
  return {};
1791
1855
  }
@@ -1810,29 +1874,83 @@ export function parseMavenTree(rawOutput) {
1810
1874
  }
1811
1875
  l = tmpline[tmpline.length - 1];
1812
1876
  const pkgArr = l.split(":");
1877
+ // Support for classifiers
1878
+ // com.github.jnr:jffi:jar:1.3.11:compile
1879
+ // com.github.jnr:jffi:jar:native:1.3.11:runtime
1880
+ let classifier = undefined;
1813
1881
  if (pkgArr && pkgArr.length > 2) {
1814
1882
  let versionStr = pkgArr[pkgArr.length - 2];
1883
+ const componentScope = pkgArr[pkgArr.length - 1];
1884
+ if (pkgArr.length >= 6 && pkgArr[3] !== versionStr) {
1885
+ classifier = pkgArr[3];
1886
+ }
1887
+ // Ignore test scope
1888
+ if (!includeMavenTestScope && componentScope === "test") {
1889
+ return;
1890
+ }
1891
+ let scope = undefined;
1892
+ if (["compile", "runtime"].includes(componentScope)) {
1893
+ scope = "required";
1894
+ } else if (componentScope === "test") {
1895
+ scope = "optional";
1896
+ } else if (componentScope === "provided") {
1897
+ scope = "excluded";
1898
+ }
1815
1899
  if (pkgArr.length === 4) {
1816
1900
  versionStr = pkgArr[pkgArr.length - 1];
1817
1901
  }
1818
- const key = `${pkgArr[0]}-${pkgArr[1]}-${versionStr}`;
1902
+ const qualifiers = { type: pkgArr[2] };
1903
+ if (classifier) {
1904
+ qualifiers.classifier = classifier;
1905
+ }
1906
+ const purlString = new PackageURL(
1907
+ "maven",
1908
+ pkgArr[0],
1909
+ pkgArr[1],
1910
+ versionStr,
1911
+ qualifiers,
1912
+ null,
1913
+ ).toString();
1914
+ const key = purlString;
1819
1915
  if (!keys_cache[key]) {
1820
1916
  keys_cache[key] = key;
1821
- let purlString = new PackageURL(
1822
- "maven",
1823
- pkgArr[0],
1824
- pkgArr[1],
1825
- versionStr,
1826
- { type: pkgArr[2] },
1827
- null,
1828
- ).toString();
1829
- purlString = decodeURIComponent(purlString);
1830
- deps.push({
1917
+ const properties = [];
1918
+ if (scope) {
1919
+ properties.push({
1920
+ name: "cdx:maven:component_scope",
1921
+ value: componentScope,
1922
+ });
1923
+ }
1924
+ const apkg = {
1831
1925
  group: pkgArr[0],
1832
1926
  name: pkgArr[1],
1833
1927
  version: versionStr,
1834
- qualifiers: { type: pkgArr[2] },
1835
- });
1928
+ qualifiers,
1929
+ scope,
1930
+ properties,
1931
+ purl: purlString,
1932
+ "bom-ref": decodeURIComponent(purlString),
1933
+ };
1934
+ if (pomFile) {
1935
+ properties.push({
1936
+ name: "SrcFile",
1937
+ value: pomFile,
1938
+ });
1939
+ apkg.evidence = {
1940
+ identity: {
1941
+ field: "purl",
1942
+ confidence: 0.5,
1943
+ methods: [
1944
+ {
1945
+ technique: "manifest-analysis",
1946
+ confidence: 0.5,
1947
+ value: pomFile,
1948
+ },
1949
+ ],
1950
+ },
1951
+ };
1952
+ }
1953
+ deps.push(apkg);
1836
1954
  if (!level_trees[purlString]) {
1837
1955
  level_trees[purlString] = [];
1838
1956
  }
@@ -6282,14 +6400,19 @@ export function parseCsProjData(csProjData, projFile, pkgNameVersions = {}) {
6282
6400
  if (!csProjData) {
6283
6401
  return pkgList;
6284
6402
  }
6285
- const projects = xml2js(csProjData, {
6286
- compact: true,
6287
- alwaysArray: true,
6288
- spaces: 4,
6289
- textKey: "_",
6290
- attributesKey: "$",
6291
- commentKey: "value",
6292
- }).Project;
6403
+ let projects = undefined;
6404
+ try {
6405
+ projects = xml2js(csProjData, {
6406
+ compact: true,
6407
+ alwaysArray: true,
6408
+ spaces: 4,
6409
+ textKey: "_",
6410
+ attributesKey: "$",
6411
+ commentKey: "value",
6412
+ }).Project;
6413
+ } catch (e) {
6414
+ console.log(`Unable to parse ${projFile} with utf-8 encoding!`);
6415
+ }
6293
6416
  if (!projects || projects.length === 0) {
6294
6417
  return pkgList;
6295
6418
  }
@@ -10222,9 +10345,7 @@ async function queryNuget(p, NUGET_URL) {
10222
10345
  let version = `${tmpVersion.major}.${tmpVersion.minor}.${tmpVersion.patch}`;
10223
10346
  if (compare(version, upper) === 1) {
10224
10347
  if (tmpVersion.patch > 0) {
10225
- version = `${tmpVersion.major}.${tmpVersion.minor}.${(
10226
- tmpVersion.patch - 1
10227
- ).toString()}`;
10348
+ version = `${tmpVersion.major}.${tmpVersion.minor}.${(tmpVersion.patch - 1).toString()}`;
10228
10349
  }
10229
10350
  }
10230
10351
  return version;
@@ -10540,3 +10661,27 @@ export function parseMakeDFile(dfile) {
10540
10661
  pkgFilesMap[pkgName] = Array.from(filesList);
10541
10662
  return pkgFilesMap;
10542
10663
  }
10664
+
10665
+ /**
10666
+ * Function to validate an externalReference URL for conforming to the JSON schema or bomLink
10667
+ * https://github.com/CycloneDX/cyclonedx-core-java/blob/75575318b268dda9e2a290761d7db11b4f414255/src/main/resources/bom-1.5.schema.json#L1140
10668
+ * https://datatracker.ietf.org/doc/html/rfc3987#section-2.2
10669
+ * https://cyclonedx.org/capabilities/bomlink/
10670
+ *
10671
+ * @param {String} iri IRI to validate
10672
+ *
10673
+ * @returns {Boolean} Flag indicating whether the supplied URL is valid or not
10674
+ *
10675
+ */
10676
+ export function isValidIriReference(iri) {
10677
+ const result = validateIri(iri, IriValidationStrategy.Strict);
10678
+
10679
+ if (result instanceof Error) {
10680
+ if (DEBUG_MODE) {
10681
+ console.log(`IRI failed validation ${iri}`);
10682
+ }
10683
+ return false;
10684
+ }
10685
+
10686
+ return true;
10687
+ }
package/utils.test.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  getNugetMetadata,
14
14
  getPyMetadata,
15
15
  guessPypiMatchingVersion,
16
+ isValidIriReference,
16
17
  parseBazelActionGraph,
17
18
  parseBazelBuild,
18
19
  parseBazelSkyframe,
@@ -525,10 +526,14 @@ test("parse maven tree", () => {
525
526
  expect(parsedList.pkgList.length).toEqual(61);
526
527
  expect(parsedList.dependenciesList.length).toEqual(61);
527
528
  expect(parsedList.pkgList[0]).toEqual({
529
+ "bom-ref": "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=war",
528
530
  group: "com.pogeyan.cmis",
529
531
  name: "copper-server",
530
532
  version: "1.15.2",
531
533
  qualifiers: { type: "war" },
534
+ properties: [],
535
+ purl: "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=war",
536
+ scope: undefined,
532
537
  });
533
538
  expect(parsedList.dependenciesList[0]).toEqual({
534
539
  ref: "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=war",
@@ -559,17 +564,24 @@ test("parse maven tree", () => {
559
564
  encoding: "utf-8",
560
565
  }),
561
566
  );
562
- expect(parsedList.pkgList.length).toEqual(37);
563
- expect(parsedList.dependenciesList.length).toEqual(37);
567
+ expect(parsedList.pkgList.length).toEqual(39);
568
+ expect(parsedList.dependenciesList.length).toEqual(39);
564
569
  expect(parsedList.pkgList[0]).toEqual({
570
+ "bom-ref":
571
+ "pkg:maven/com.gitlab.security_products.tests/java-maven@1.0-SNAPSHOT?type=jar",
572
+ purl: "pkg:maven/com.gitlab.security_products.tests/java-maven@1.0-SNAPSHOT?type=jar",
565
573
  group: "com.gitlab.security_products.tests",
566
574
  name: "java-maven",
567
575
  version: "1.0-SNAPSHOT",
568
576
  qualifiers: { type: "jar" },
577
+ properties: [],
578
+ scope: undefined,
569
579
  });
570
580
  expect(parsedList.dependenciesList[0]).toEqual({
571
581
  ref: "pkg:maven/com.gitlab.security_products.tests/java-maven@1.0-SNAPSHOT?type=jar",
572
582
  dependsOn: [
583
+ "pkg:maven/com.github.jnr/jffi@1.3.11?type=jar",
584
+ "pkg:maven/com.github.jnr/jffi@1.3.11?classifier=native&type=jar",
573
585
  "pkg:maven/org.powermock/powermock-api-mockito@1.7.3?type=jar",
574
586
  "pkg:maven/io.netty/netty@3.9.1.Final?type=jar",
575
587
  "pkg:maven/junit/junit@3.8.1?type=jar",
@@ -586,16 +598,26 @@ test("parse maven tree", () => {
586
598
  );
587
599
  expect(parsedList.pkgList.length).toEqual(79);
588
600
  expect(parsedList.pkgList[0]).toEqual({
601
+ "bom-ref":
602
+ "pkg:maven/example.group/eclipse-repository@1.0.0-SNAPSHOT?type=eclipse-repository",
603
+ purl: "pkg:maven/example.group/eclipse-repository@1.0.0-SNAPSHOT?type=eclipse-repository",
589
604
  group: "example.group",
590
605
  name: "eclipse-repository",
591
606
  version: "1.0.0-SNAPSHOT",
592
607
  qualifiers: { type: "eclipse-repository" },
608
+ scope: undefined,
609
+ properties: [],
593
610
  });
594
611
  expect(parsedList.pkgList[4]).toEqual({
612
+ "bom-ref":
613
+ "pkg:maven/p2.eclipse.plugin/com.ibm.icu@67.1.0.v20200706-1749?type=eclipse-plugin",
614
+ purl: "pkg:maven/p2.eclipse.plugin/com.ibm.icu@67.1.0.v20200706-1749?type=eclipse-plugin",
595
615
  group: "p2.eclipse.plugin",
596
616
  name: "com.ibm.icu",
597
617
  version: "67.1.0.v20200706-1749",
598
618
  qualifiers: { type: "eclipse-plugin" },
619
+ scope: undefined,
620
+ properties: [],
599
621
  });
600
622
  expect(parsedList.dependenciesList.length).toEqual(79);
601
623
  expect(parsedList.dependenciesList[0]).toEqual({
@@ -2369,14 +2391,14 @@ test("parsePkgLock v2 workspace", async () => {
2369
2391
  });
2370
2392
 
2371
2393
  test("parsePkgLock v3", async () => {
2372
- let parsedList = await parsePkgLock(
2394
+ const parsedList = await parsePkgLock(
2373
2395
  "./test/data/package-json/v3/package-lock.json",
2374
2396
  {
2375
2397
  projectVersion: "latest",
2376
2398
  projectName: "cdxgen",
2377
2399
  },
2378
2400
  );
2379
- let deps = parsedList.pkgList;
2401
+ const deps = parsedList.pkgList;
2380
2402
  expect(deps.length).toEqual(161);
2381
2403
  expect(deps[1]._integrity).toEqual(
2382
2404
  "sha512-s93jiP6GkRApn5duComx6RLwtP23YrulPxShz+8peX7svd6Q+MS8nKLhKCCazbP92C13eTVaIOxgeLt0ezIiCg==",
@@ -2393,13 +2415,6 @@ test("parsePkgLock v3", async () => {
2393
2415
  });
2394
2416
  expect(deps[deps.length - 1].name).toEqual("uid2");
2395
2417
  expect(parsedList.dependenciesList.length).toEqual(161);
2396
- parsedList = await parsePkgLock("./package-lock.json", {
2397
- projectVersion: "latest",
2398
- projectName: "cdxgen",
2399
- });
2400
- deps = parsedList.pkgList;
2401
- expect(deps.length).toEqual(848);
2402
- expect(parsedList.dependenciesList.length).toEqual(848);
2403
2418
  });
2404
2419
 
2405
2420
  test("parseBowerJson", async () => {
@@ -2664,6 +2679,39 @@ test("parsePnpmLock", async () => {
2664
2679
  },
2665
2680
  },
2666
2681
  });
2682
+ parsedList = await parsePnpmLock("./pnpm-lock.yaml");
2683
+ expect(parsedList.pkgList.length).toEqual(644);
2684
+ expect(parsedList.dependenciesList.length).toEqual(644);
2685
+ expect(parsedList.pkgList[0]).toEqual({
2686
+ group: "@ampproject",
2687
+ name: "remapping",
2688
+ version: "2.3.0",
2689
+ purl: "pkg:npm/%40ampproject/remapping@2.3.0",
2690
+ "bom-ref": "pkg:npm/@ampproject/remapping@2.3.0",
2691
+ _integrity:
2692
+ "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
2693
+ properties: [{ name: "SrcFile", value: "./pnpm-lock.yaml" }],
2694
+ evidence: {
2695
+ identity: {
2696
+ field: "purl",
2697
+ confidence: 1,
2698
+ methods: [
2699
+ {
2700
+ technique: "manifest-analysis",
2701
+ confidence: 1,
2702
+ value: "./pnpm-lock.yaml",
2703
+ },
2704
+ ],
2705
+ },
2706
+ },
2707
+ });
2708
+ expect(parsedList.dependenciesList[0]).toEqual({
2709
+ ref: "pkg:npm/@ampproject/remapping@2.3.0",
2710
+ dependsOn: [
2711
+ "pkg:npm/@jridgewell/gen-mapping@0.3.5",
2712
+ "pkg:npm/@jridgewell/trace-mapping@0.3.25",
2713
+ ],
2714
+ });
2667
2715
  });
2668
2716
 
2669
2717
  test("parseYarnLock", async () => {
@@ -4040,3 +4088,20 @@ test("parseMakeDFile tests", () => {
4040
4088
  ],
4041
4089
  });
4042
4090
  });
4091
+
4092
+ test.each([
4093
+ ["", false],
4094
+ ["git@gitlab.com:behat-chrome/chrome-mink-driver.git", false],
4095
+ [" git@gitlab.com:behat-chrome/chrome-mink-driver.git ", false],
4096
+ ["${repository.url}", false],
4097
+ // bomLink - https://cyclonedx.org/capabilities/bomlink/]
4098
+ ["urn:cdx:f08a6ccd-4dce-4759-bd84-c626675d60a7/1#componentA", true],
4099
+ // http uri - https://www.ietf.org/rfc/rfc7230.txt]
4100
+ ["https://gitlab.com/behat-chrome/chrome-mink-driver.git", true],
4101
+ [" https://gitlab.com/behat-chrome/chrome-mink-driver.git ", false],
4102
+ ["http://gitlab.com/behat-chrome/chrome-mink-driver.git", true],
4103
+ ["git+https://github.com/Alex-D/check-disk-space.git", true],
4104
+ ["UNKNOWN", false],
4105
+ ])("isValidIriReference tests: %s", (url, isValid) => {
4106
+ expect(isValidIriReference(url)).toBe(isValid);
4107
+ });