@cyclonedx/cdxgen 10.8.9 → 10.9.0

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/bin/cdxgen.js CHANGED
@@ -275,6 +275,11 @@ const args = yargs(hideBin(process.argv))
275
275
  description:
276
276
  "Do not show the donation banner. Set this attribute if you are an active sponsor for OWASP CycloneDX.",
277
277
  })
278
+ .option("feature-flags", {
279
+ description: "Experimental feature flags to enable. Advanced users only.",
280
+ hidden: true,
281
+ choices: ["safe-pip-install"],
282
+ })
278
283
  .completion("completion", "Generate bash/zsh completion")
279
284
  .array("type")
280
285
  .array("excludeType")
@@ -283,6 +288,7 @@ const args = yargs(hideBin(process.argv))
283
288
  .array("author")
284
289
  .array("exclude")
285
290
  .array("standard")
291
+ .array("feature-flags")
286
292
  .option("auto-compositions", {
287
293
  type: "boolean",
288
294
  default: true,
@@ -438,7 +444,6 @@ const applyAdvancedOptions = (options) => {
438
444
  }
439
445
  return options;
440
446
  };
441
-
442
447
  applyAdvancedOptions(options);
443
448
 
444
449
  /**
package/binary.js CHANGED
@@ -350,7 +350,7 @@ export function getOSPackages(src) {
350
350
  "--skip-java-db-update",
351
351
  "--offline-scan",
352
352
  "--skip-files",
353
- "**/*.jar",
353
+ "**/*.jar,**/*.war,**/*.par,**/*.ear",
354
354
  "--no-progress",
355
355
  "--exit-code",
356
356
  "0",
package/index.js CHANGED
@@ -63,12 +63,15 @@ import {
63
63
  getMvnMetadata,
64
64
  getNugetMetadata,
65
65
  getPipFrozenTree,
66
+ getPipTreeForPackages,
66
67
  getPyMetadata,
67
68
  getPyModules,
68
69
  getSwiftPackageMetadata,
69
70
  getTimestamp,
70
71
  hasAnyProjectType,
71
72
  includeMavenTestScope,
73
+ isFeatureEnabled,
74
+ isPartialTree,
72
75
  isValidIriReference,
73
76
  parseBazelActionGraph,
74
77
  parseBazelSkyframe,
@@ -2749,6 +2752,10 @@ export async function createPythonBom(path, options) {
2749
2752
  if (pyProjectMode) {
2750
2753
  const tmpParentComponent = parsePyProjectToml(pyProjectFile);
2751
2754
  if (tmpParentComponent?.name) {
2755
+ // Bug fix. Version could be missing in pyproject.toml
2756
+ if (!tmpParentComponent.version && parentComponent.version) {
2757
+ tmpParentComponent.version = parentComponent.version;
2758
+ }
2752
2759
  parentComponent = tmpParentComponent;
2753
2760
  delete parentComponent.homepage;
2754
2761
  delete parentComponent.repository;
@@ -2815,6 +2822,7 @@ export async function createPythonBom(path, options) {
2815
2822
  };
2816
2823
  dependencies.splice(0, 0, pdependencies);
2817
2824
  }
2825
+ options.parentComponent = parentComponent;
2818
2826
  return buildBomNSData(options, pkgList, "pypi", {
2819
2827
  src: path,
2820
2828
  filename: poetryFiles.join(", "),
@@ -2822,7 +2830,7 @@ export async function createPythonBom(path, options) {
2822
2830
  parentComponent,
2823
2831
  formulationList,
2824
2832
  });
2825
- }
2833
+ } // poetryMode
2826
2834
  if (metadataFiles?.length) {
2827
2835
  // dist-info directories
2828
2836
  for (const mf of metadataFiles) {
@@ -3049,6 +3057,45 @@ export async function createPythonBom(path, options) {
3049
3057
  pkgList = pkgList.concat(dlist);
3050
3058
  }
3051
3059
  }
3060
+ // Check and complete the dependency tree
3061
+ if (
3062
+ isFeatureEnabled(options, "safe-pip-install") &&
3063
+ pkgList.length &&
3064
+ isPartialTree(dependencies)
3065
+ ) {
3066
+ // Trim the current package list first
3067
+ pkgList = trimComponents(pkgList);
3068
+ console.log(
3069
+ `Attempting to recover the pip dependency tree from ${pkgList.length} packages. Please wait ...`,
3070
+ );
3071
+ const newPkgMap = getPipTreeForPackages(
3072
+ path,
3073
+ pkgList,
3074
+ tempDir,
3075
+ parentComponent,
3076
+ );
3077
+ if (DEBUG_MODE && newPkgMap.failedPkgList.length) {
3078
+ if (newPkgMap.failedPkgList.length < pkgList.length) {
3079
+ console.log(
3080
+ `${newPkgMap.failedPkgList.length} packages failed to install.`,
3081
+ );
3082
+ }
3083
+ }
3084
+ if (newPkgMap?.pkgList?.length) {
3085
+ pkgList = pkgList.concat(newPkgMap.pkgList);
3086
+ pkgList = trimComponents(pkgList);
3087
+ }
3088
+ if (newPkgMap.dependenciesList) {
3089
+ dependencies = mergeDependencies(
3090
+ dependencies,
3091
+ newPkgMap.dependenciesList,
3092
+ parentComponent,
3093
+ );
3094
+ if (DEBUG_MODE && dependencies.length > 1) {
3095
+ console.log("Recovered", dependencies.length, "dependencies.");
3096
+ }
3097
+ }
3098
+ }
3052
3099
  // Clean up
3053
3100
  if (tempDir?.startsWith(tmpdir()) && rmSync) {
3054
3101
  rmSync(tempDir, { recursive: true, force: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "10.8.9",
3
+ "version": "10.9.0",
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>",
@@ -68,7 +68,7 @@
68
68
  "find-up": "7.0.0",
69
69
  "glob": "^11.0.0",
70
70
  "global-agent": "^3.0.0",
71
- "got": "14.4.1",
71
+ "got": "14.4.2",
72
72
  "iconv-lite": "^0.6.3",
73
73
  "js-yaml": "^4.1.0",
74
74
  "jws": "^4.0.0",
@@ -111,7 +111,7 @@
111
111
  "devDependencies": {
112
112
  "@biomejs/biome": "1.8.3",
113
113
  "jest": "^29.7.0",
114
- "typescript": "^5.5.3"
114
+ "typescript": "^5.5.4"
115
115
  },
116
116
  "scripts": {
117
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",
package/server.js CHANGED
@@ -178,14 +178,17 @@ const start = (options) => {
178
178
  let bomNSData = (await createBom(srcDir, reqOptions)) || {};
179
179
  bomNSData = postProcess(bomNSData, reqOptions);
180
180
  if (reqOptions.serverUrl && reqOptions.apiKey) {
181
- console.log("Publishing SBOM to Dependency Track");
181
+ console.log(
182
+ `Publishing SBOM ${reqOptions.projectName} to Dependency Track`,
183
+ reqOptions.serverUrl,
184
+ );
182
185
  const response = await submitBom(reqOptions, bomNSData.bomJson);
183
186
  const errorMessages = response?.errors;
184
187
  if (errorMessages) {
185
188
  res.writeHead(500, { "Content-Type": "application/json" });
186
189
  return res.end(
187
190
  JSON.stringify({
188
- error: "Unable to submit the SBOM to the Dependency-Track server",
191
+ error: `Unable to submit the SBOM ${reqOptions.projectName} to the Dependency Track server ${reqOptions.serverUrl}`,
189
192
  details: errorMessages,
190
193
  }),
191
194
  );
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":"AAsvBA;;;;;;;;GAQG;AACH,gFAFW,MAAM,SAchB;AAyUD;;;;;;;GAOG;AACH,mCALW,MAAM,qBAiEhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM;;;;EAKhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM;;;;EAkBhB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAi/BhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BA2chB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BAsXhB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,8BAkUhB;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,qBAiUhB;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,EAAE,8BAmclB;AAED;;;;;GAKG;AACH,iCAHW,MAAM,8BAiUhB;AAED;;;;;GAKG;AACH,gCAHW,MAAM,qBAsOhB;AAED;;;;;;GAMG;AACH,wDAFY,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,SAAS,CAAC,CA2FxE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":"AAyvBA;;;;;;;;GAQG;AACH,gFAFW,MAAM,SAchB;AAyUD;;;;;;;GAOG;AACH,mCALW,MAAM,qBAiEhB;AAED;;;;;GAKG;AACH,uCAHW,MAAM;;;;EAKhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM;;;;EAkBhB;AAED;;;;;GAKG;AACH,oCAHW,MAAM,8BAi/BhB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BA2chB;AAED;;;;;GAKG;AACH,sCAHW,MAAM,8BAkahB;AAED;;;;;GAKG;AACH,kCAHW,MAAM,8BAkUhB;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,qBAiUhB;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,EAAE,8BAmclB;AAED;;;;;GAKG;AACH,iCAHW,MAAM,8BAiUhB;AAED;;;;;GAKG;AACH,gCAHW,MAAM,qBAsOhB;AAED;;;;;;GAMG;AACH,wDAFY,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,SAAS,CAAC,CA2FxE"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.js"],"names":[],"mappings":"AAuIA,yDAKC;AAED,0CAoEC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.js"],"names":[],"mappings":"AAuIA,yDAKC;AAED,0CAuEC"}
package/types/utils.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Method to check if a given feature flag is enabled.
3
+ *
4
+ * @param {Object} cliOptions CLI options
5
+ * @param {String} feature Feature flag
6
+ *
7
+ * @returns {Boolean} True if the feature is enabled
8
+ */
9
+ export function isFeatureEnabled(cliOptions: any, feature: string): boolean;
1
10
  /**
2
11
  * Method to check if the given project types are allowed by checking against include and exclude types passed from the CLI arguments.
3
12
  *
@@ -607,6 +616,7 @@ export function parseCabalData(cabalData: any): any[];
607
616
  export function parseMixLockData(mixData: any): any[];
608
617
  export function parseGitHubWorkflowData(ghwData: any): any[];
609
618
  export function parseCloudBuildData(cbwData: any): any[];
619
+ export function mapConanPkgRefToPurlStringAndNameAndVersion(conanPkgRef: any): any[];
610
620
  export function parseConanLockData(conanLockData: any): any[];
611
621
  export function parseConanData(conanData: any): any[];
612
622
  export function parseLeiningenData(leinData: any): any[];
@@ -1076,6 +1086,8 @@ export function getPipFrozenTree(basePath: string, reqOrSetupFile: string, tempV
1076
1086
  rootList: {
1077
1087
  name: any;
1078
1088
  version: any;
1089
+ purl: string;
1090
+ "bom-ref": string;
1079
1091
  }[];
1080
1092
  dependenciesList: {
1081
1093
  ref: string;
@@ -1083,6 +1095,35 @@ export function getPipFrozenTree(basePath: string, reqOrSetupFile: string, tempV
1083
1095
  }[];
1084
1096
  frozen: boolean;
1085
1097
  };
1098
+ /**
1099
+ * The problem: pip installation can fail for a number of reasons such as missing OS dependencies and devel packages.
1100
+ * When it fails, we don't get any dependency tree. As a workaroud, this method would attempt to install one package at a time to the same virtual environment and then attempts to obtain a dependency tree.
1101
+ * Such a tree could be incorrect or quite approximate, but some users might still find it useful to know the names of the indirect dependencies.
1102
+ *
1103
+ * @param {string} basePath Base path
1104
+ * @param {Array} pkgList Existing package list
1105
+ * @param {string} tempVenvDir Temp venv dir
1106
+ * @param {Object} parentComponent Parent component
1107
+ *
1108
+ * @returns List of packages from the virtual env
1109
+ */
1110
+ export function getPipTreeForPackages(basePath: string, pkgList: any[], tempVenvDir: string, parentComponent: any): {
1111
+ failedPkgList?: undefined;
1112
+ rootList?: undefined;
1113
+ dependenciesList?: undefined;
1114
+ } | {
1115
+ failedPkgList: any[];
1116
+ rootList: {
1117
+ name: any;
1118
+ version: any;
1119
+ purl: string;
1120
+ "bom-ref": string;
1121
+ }[];
1122
+ dependenciesList: {
1123
+ ref: string;
1124
+ dependsOn: any;
1125
+ }[];
1126
+ };
1086
1127
  export function parsePackageJsonName(name: any): {
1087
1128
  scope: any;
1088
1129
  fullName: string;
@@ -1166,6 +1207,13 @@ export function parseMakeDFile(dfile: string): any;
1166
1207
  *
1167
1208
  */
1168
1209
  export function isValidIriReference(iri: string): boolean;
1210
+ /**
1211
+ * Method to check if a given dependency tree is partial or not.
1212
+ *
1213
+ * @param {Array} dependencies List of dependencies
1214
+ * @returns {Boolean} True if the dependency tree lacks any non-root parents without children. False otherwise.
1215
+ */
1216
+ export function isPartialTree(dependencies: any[]): boolean;
1169
1217
  export const dirNameStr: string;
1170
1218
  export const isWin: boolean;
1171
1219
  export const isMac: boolean;
@@ -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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../validator.js"],"names":[],"mappings":"AAmBO,qCAFI,MAAM,WA6ChB;AAOM,0CAFI,MAAM,WAiDhB;AAOM,uCAFI,MAAM,WAgEhB;AA6BM,sCAFI,MAAM,WA2ChB"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../validator.js"],"names":[],"mappings":"AAmBO,qCAFI,MAAM,WA6ChB;AAOM,0CAFI,MAAM,WAiDhB;AAOM,uCAFI,MAAM,WAgEhB;AA6BM,sCAFI,MAAM,WA8ChB"}
package/utils.js CHANGED
@@ -292,6 +292,36 @@ export const PROJECT_TYPE_ALIASES = {
292
292
  oci: ["docker", "oci", "container", "podman"],
293
293
  };
294
294
 
295
+ /**
296
+ * Method to check if a given feature flag is enabled.
297
+ *
298
+ * @param {Object} cliOptions CLI options
299
+ * @param {String} feature Feature flag
300
+ *
301
+ * @returns {Boolean} True if the feature is enabled
302
+ */
303
+ export function isFeatureEnabled(cliOptions, feature) {
304
+ if (cliOptions?.featureFlags?.includes(feature)) {
305
+ return true;
306
+ }
307
+ if (
308
+ process.env[feature.toUpperCase()] &&
309
+ ["true", "1"].includes(process.env[feature.toUpperCase()])
310
+ ) {
311
+ return true;
312
+ }
313
+ // Retry by replacing hyphens with underscore
314
+ if (
315
+ process.env[feature.replaceAll("-", "_").toUpperCase()] &&
316
+ ["true", "1"].includes(
317
+ process.env[feature.replaceAll("-", "_").toUpperCase()],
318
+ )
319
+ ) {
320
+ return true;
321
+ }
322
+ return false;
323
+ }
324
+
295
325
  /**
296
326
  * Method to check if the given project types are allowed by checking against include and exclude types passed from the CLI arguments.
297
327
  *
@@ -1252,8 +1282,11 @@ export function yarnLockToIdentMap(lockData) {
1252
1282
  }
1253
1283
  }
1254
1284
  }
1255
- } else if (l.startsWith(" version") && currentIdents.length) {
1256
- const tmpA = l.replace(/["']/g, "").split(" ");
1285
+ } else if (
1286
+ (l.startsWith(" version") || l.startsWith(' "version')) &&
1287
+ currentIdents.length
1288
+ ) {
1289
+ const tmpA = l.replace(/"/g, "").split(" ");
1257
1290
  const version = tmpA[tmpA.length - 1].trim();
1258
1291
  for (const id of currentIdents) {
1259
1292
  identMap[id] = version;
@@ -1418,9 +1451,14 @@ export async function parseYarnLock(yarnLockFile) {
1418
1451
  } else if (
1419
1452
  name !== "" &&
1420
1453
  (l.startsWith(" dependencies:") ||
1421
- l.startsWith(" optionalDependencies:"))
1454
+ l.startsWith(' "dependencies:') ||
1455
+ l.startsWith(" optionalDependencies:") ||
1456
+ l.startsWith(' "optionalDependencies:'))
1422
1457
  ) {
1423
- if (l.startsWith(" dependencies:")) {
1458
+ if (
1459
+ l.startsWith(" dependencies:") ||
1460
+ l.startsWith(' "dependencies:')
1461
+ ) {
1424
1462
  depsMode = true;
1425
1463
  optionalDepsMode = false;
1426
1464
  } else {
@@ -1432,9 +1470,17 @@ export async function parseYarnLock(yarnLockFile) {
1432
1470
  // We need the resolved version from identMap
1433
1471
  // Deal with values with space within the quotes. Eg: minimatch "2 || 3"
1434
1472
  // vinyl-sourcemaps-apply ">=0.1.1 <0.2.0-0"
1435
- const tmpA = l.trim().split(' "');
1473
+ l = l.trim();
1474
+ let splitPattern = ' "';
1475
+ // yarn v7 has a different split pattern
1476
+ if (l.includes('": ')) {
1477
+ splitPattern = '": ';
1478
+ } else if (l.includes(": ")) {
1479
+ splitPattern = ": ";
1480
+ }
1481
+ const tmpA = l.trim().split(splitPattern);
1436
1482
  if (tmpA && tmpA.length === 2) {
1437
- let dgroupname = tmpA[0];
1483
+ let dgroupname = tmpA[0].replace(/"/g, "");
1438
1484
  if (dgroupname.endsWith(":")) {
1439
1485
  dgroupname = dgroupname.substring(0, dgroupname.length - 1);
1440
1486
  }
@@ -1459,7 +1505,7 @@ export async function parseYarnLock(yarnLockFile) {
1459
1505
  depsMode = false;
1460
1506
  optionalDepsMode = false;
1461
1507
  }
1462
- l = l.trim();
1508
+ l = l.replace(/"/g, "").trim();
1463
1509
  const parts = l.split(" ");
1464
1510
  if (l.startsWith("version")) {
1465
1511
  version = parts[1].replace(/"/g, "");
@@ -2764,6 +2810,13 @@ export function parseGradleProperties(rawOutput) {
2764
2810
  * @returns {string} The combined output for all subprojects of the Gradle properties task
2765
2811
  */
2766
2812
  export function executeParallelGradleProperties(dir, rootPath, allProjectsStr) {
2813
+ const defaultProps = {
2814
+ rootProject: subProject,
2815
+ projects: [],
2816
+ metadata: {
2817
+ version: "latest",
2818
+ },
2819
+ };
2767
2820
  let parallelPropTaskArgs = ["properties"];
2768
2821
  for (const spstr of allProjectsStr) {
2769
2822
  parallelPropTaskArgs.push(`${spstr}:properties`);
@@ -6481,6 +6534,170 @@ export function parseCloudBuildData(cbwData) {
6481
6534
  return pkgList;
6482
6535
  }
6483
6536
 
6537
+ function createConanPurlString(name, version, user, channel, rrev, prev) {
6538
+ // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#conan
6539
+
6540
+ const qualifiers = {};
6541
+
6542
+ if (user) qualifiers["user"] = user;
6543
+ if (channel) qualifiers["channel"] = channel;
6544
+ if (rrev) qualifiers["rrev"] = rrev;
6545
+ if (prev) qualifiers["prev"] = prev;
6546
+
6547
+ return new PackageURL(
6548
+ "conan",
6549
+ "",
6550
+ name,
6551
+ version,
6552
+ Object.keys(qualifiers).length ? qualifiers : null,
6553
+ null,
6554
+ ).toString();
6555
+ }
6556
+
6557
+ function untilFirst(separator, inputStr) {
6558
+ // untilFirst("/", "a/b") -> ["/", "a", "b"]
6559
+ // untilFirst("/", "abc") -> ["/", "abc", null]
6560
+
6561
+ if (!inputStr || inputStr.length === 0) {
6562
+ return [null, null, null];
6563
+ }
6564
+
6565
+ const separatorIndex = inputStr.search(separator);
6566
+ if (separatorIndex === -1) {
6567
+ return ["", inputStr, null];
6568
+ }
6569
+ return [
6570
+ inputStr[separatorIndex],
6571
+ inputStr.substring(0, separatorIndex),
6572
+ inputStr.substring(separatorIndex + 1),
6573
+ ];
6574
+ }
6575
+
6576
+ export function mapConanPkgRefToPurlStringAndNameAndVersion(conanPkgRef) {
6577
+ // A full Conan package reference may be composed of the following segments:
6578
+ // conanPkgRef = "name/version@user/channel#recipe_revision:package_id#package_revision"
6579
+ // See also https://docs.conan.io/1/cheatsheet.html#package-terminology
6580
+
6581
+ // The components 'package_id' and 'package_revision' do not appear in any files processed by cdxgen.
6582
+ // The components 'user' and 'channel' are not mandatory.
6583
+ // 'name/version' is a valid Conan package reference, so is 'name/version@user/channel' or 'name/version@user/channel#recipe_revision'.
6584
+ // pURL for Conan does not recognize 'package_id'.
6585
+
6586
+ const UNABLE_TO_PARSE_CONAN_PKG_REF = [null, null, null];
6587
+
6588
+ if (!conanPkgRef) {
6589
+ if (DEBUG_MODE)
6590
+ console.warn(
6591
+ `Could not parse Conan package reference '${conanPkgRef}', input does not seem valid.`,
6592
+ );
6593
+
6594
+ return UNABLE_TO_PARSE_CONAN_PKG_REF;
6595
+ }
6596
+
6597
+ const separatorRegex = /[@#:\/]/;
6598
+
6599
+ const info = {
6600
+ name: null,
6601
+ version: null,
6602
+ user: null,
6603
+ channel: null,
6604
+ recipe_revision: null,
6605
+ package_id: null,
6606
+ package_revision: null,
6607
+ phase_history: [],
6608
+ };
6609
+
6610
+ const transitions = {
6611
+ ["name"]: {
6612
+ "/": "version",
6613
+ "#": "recipe_revision",
6614
+ "": "end",
6615
+ },
6616
+ ["version"]: {
6617
+ "@": "user",
6618
+ "#": "recipe_revision",
6619
+ "": "end",
6620
+ },
6621
+ ["user"]: {
6622
+ "/": "channel",
6623
+ },
6624
+ ["channel"]: {
6625
+ "#": "recipe_revision",
6626
+ "": "end",
6627
+ },
6628
+ ["recipe_revision"]: {
6629
+ ":": "package_id",
6630
+ "": "end",
6631
+ },
6632
+ ["package_id"]: {
6633
+ "#": "package_revision",
6634
+ },
6635
+ ["package_revision"]: {
6636
+ "": "end",
6637
+ },
6638
+ };
6639
+
6640
+ let phase = "name";
6641
+ let remainder = conanPkgRef;
6642
+ let separator;
6643
+ let item;
6644
+
6645
+ while (remainder) {
6646
+ [separator, item, remainder] = untilFirst(separatorRegex, remainder);
6647
+
6648
+ if (!item) {
6649
+ if (DEBUG_MODE)
6650
+ console.warn(
6651
+ `Could not parse Conan package reference '${conanPkgRef}', empty item in phase '${phase}', separator=${separator}, remainder=${remainder}, info=${JSON.stringify(info)}`,
6652
+ );
6653
+ return UNABLE_TO_PARSE_CONAN_PKG_REF;
6654
+ }
6655
+
6656
+ info[phase] = item;
6657
+ info.phase_history.push(phase);
6658
+
6659
+ if (!(phase in transitions)) {
6660
+ if (DEBUG_MODE)
6661
+ console.warn(
6662
+ `Could not parse Conan package reference '${conanPkgRef}', no transition from '${phase}', separator=${separator}, item=${item}, remainder=${remainder}, info=${JSON.stringify(info)}`,
6663
+ );
6664
+ return UNABLE_TO_PARSE_CONAN_PKG_REF;
6665
+ }
6666
+
6667
+ const possibleTransitions = transitions[phase];
6668
+ if (!(separator in possibleTransitions)) {
6669
+ if (DEBUG_MODE)
6670
+ console.warn(
6671
+ `Could not parse Conan package reference '${conanPkgRef}', transition '${separator}' not allowed from '${phase}', item=${item}, remainder=${remainder}, info=${JSON.stringify(info)}`,
6672
+ );
6673
+ return UNABLE_TO_PARSE_CONAN_PKG_REF;
6674
+ }
6675
+
6676
+ phase = possibleTransitions[separator];
6677
+ }
6678
+
6679
+ if (phase !== "end") {
6680
+ if (DEBUG_MODE)
6681
+ console.warn(
6682
+ `Could not parse Conan package reference '${conanPkgRef}', end of input string reached unexpectedly in phase '${phase}', info=${JSON.stringify(info)}.`,
6683
+ );
6684
+ return UNABLE_TO_PARSE_CONAN_PKG_REF;
6685
+ }
6686
+
6687
+ if (!info.version) info.version = "latest";
6688
+
6689
+ const purl = createConanPurlString(
6690
+ info.name,
6691
+ info.version,
6692
+ info.user,
6693
+ info.channel,
6694
+ info.recipe_revision,
6695
+ info.package_revision,
6696
+ );
6697
+
6698
+ return [purl, info.name, info.version];
6699
+ }
6700
+
6484
6701
  export function parseConanLockData(conanLockData) {
6485
6702
  const pkgList = [];
6486
6703
  if (!conanLockData) {
@@ -6493,27 +6710,15 @@ export function parseConanLockData(conanLockData) {
6493
6710
  const nodes = graphLock.graph_lock.nodes;
6494
6711
  for (const nk of Object.keys(nodes)) {
6495
6712
  if (nodes[nk].ref) {
6496
- const tmpA = nodes[nk].ref.split("/");
6497
- if (tmpA.length === 2) {
6498
- let version = tmpA[1] || "latest";
6499
- if (tmpA[1].includes("@")) {
6500
- version = version.split("@")[0];
6501
- } else if (tmpA[1].includes("#")) {
6502
- version = version.split("#")[0];
6503
- }
6504
- const purlString = new PackageURL(
6505
- "conan",
6506
- "",
6507
- tmpA[0],
6508
- version,
6509
- null,
6510
- null,
6511
- ).toString();
6713
+ const [purl, name, version] = mapConanPkgRefToPurlStringAndNameAndVersion(
6714
+ nodes[nk].ref,
6715
+ );
6716
+ if (purl !== null) {
6512
6717
  pkgList.push({
6513
- name: tmpA[0],
6718
+ name,
6514
6719
  version,
6515
- purl: purlString,
6516
- "bom-ref": decodeURIComponent(purlString),
6720
+ purl,
6721
+ "bom-ref": decodeURIComponent(purl),
6517
6722
  });
6518
6723
  }
6519
6724
  }
@@ -6535,39 +6740,19 @@ export function parseConanData(conanData) {
6535
6740
  if (l.includes("[requires]")) {
6536
6741
  scope = "required";
6537
6742
  }
6538
- if (!l.includes("/")) {
6539
- return;
6540
- }
6541
- if (l.includes("/")) {
6542
- const tmpA = l.trim().split("#")[0].split("/");
6543
- if (tmpA.length >= 2 && /^\d+/.test(tmpA[1])) {
6544
- let version = tmpA[1] || "latest";
6545
- let qualifiers = undefined;
6546
- if (tmpA[1].includes("#")) {
6547
- const tmpB = version.split("#");
6548
- version = tmpB[0];
6549
- qualifiers = { revision: tmpB[1] };
6550
- }
6551
- if (l.includes("#")) {
6552
- const tmpB = l.split("#");
6553
- qualifiers = { revision: tmpB[1] };
6554
- }
6555
- if (tmpA[1].includes("@")) {
6556
- version = version.split("@")[0];
6557
- }
6558
- const purlString = new PackageURL(
6559
- "conan",
6560
- "",
6561
- tmpA[0],
6562
- version,
6563
- qualifiers,
6564
- null,
6565
- ).toString();
6743
+
6744
+ // The line must start with sequence non-whitespace characters, followed by a slash,
6745
+ // followed by at least one more non-whitespace character.
6746
+ // Provides a heuristic for locating Conan package references inside conanfile.txt files.
6747
+ if (l.match(/^[^\s\/]+\/\S+/)) {
6748
+ const [purl, name, version] =
6749
+ mapConanPkgRefToPurlStringAndNameAndVersion(l);
6750
+ if (purl !== null) {
6566
6751
  pkgList.push({
6567
- name: tmpA[0],
6752
+ name,
6568
6753
  version,
6569
- purl: purlString,
6570
- "bom-ref": decodeURIComponent(purlString),
6754
+ purl,
6755
+ "bom-ref": decodeURIComponent(purl),
6571
6756
  scope,
6572
6757
  });
6573
6758
  }
@@ -9514,18 +9699,20 @@ function flattenDeps(dependenciesMap, pkgList, reqOrSetupFile, t) {
9514
9699
  null,
9515
9700
  null,
9516
9701
  ).toString();
9517
- pkgList.push({
9702
+ const apkg = {
9518
9703
  name: d.name,
9519
9704
  version: d.version,
9520
9705
  purl: purlString,
9521
9706
  "bom-ref": decodeURIComponent(purlString),
9522
- properties: [
9707
+ };
9708
+ if (reqOrSetupFile) {
9709
+ apkg.properties = [
9523
9710
  {
9524
9711
  name: "SrcFile",
9525
9712
  value: reqOrSetupFile,
9526
9713
  },
9527
- ],
9528
- evidence: {
9714
+ ];
9715
+ apkg.evidence = {
9529
9716
  identity: {
9530
9717
  field: "purl",
9531
9718
  confidence: 0.8,
@@ -9537,8 +9724,9 @@ function flattenDeps(dependenciesMap, pkgList, reqOrSetupFile, t) {
9537
9724
  },
9538
9725
  ],
9539
9726
  },
9540
- },
9541
- });
9727
+ };
9728
+ }
9729
+ pkgList.push(apkg);
9542
9730
  // Recurse and flatten
9543
9731
  if (d.dependencies && d.dependencies) {
9544
9732
  flattenDeps(dependenciesMap, pkgList, reqOrSetupFile, d);
@@ -9914,6 +10102,8 @@ export function getPipFrozenTree(
9914
10102
  rootList.push({
9915
10103
  name,
9916
10104
  version,
10105
+ purl: purlString,
10106
+ "bom-ref": decodeURIComponent(purlString),
9917
10107
  });
9918
10108
  flattenDeps(dependenciesMap, pkgList, reqOrSetupFile, t);
9919
10109
  } else {
@@ -9933,6 +10123,190 @@ export function getPipFrozenTree(
9933
10123
  };
9934
10124
  }
9935
10125
 
10126
+ /**
10127
+ * The problem: pip installation can fail for a number of reasons such as missing OS dependencies and devel packages.
10128
+ * When it fails, we don't get any dependency tree. As a workaroud, this method would attempt to install one package at a time to the same virtual environment and then attempts to obtain a dependency tree.
10129
+ * Such a tree could be incorrect or quite approximate, but some users might still find it useful to know the names of the indirect dependencies.
10130
+ *
10131
+ * @param {string} basePath Base path
10132
+ * @param {Array} pkgList Existing package list
10133
+ * @param {string} tempVenvDir Temp venv dir
10134
+ * @param {Object} parentComponent Parent component
10135
+ *
10136
+ * @returns List of packages from the virtual env
10137
+ */
10138
+ export function getPipTreeForPackages(
10139
+ basePath,
10140
+ pkgList,
10141
+ tempVenvDir,
10142
+ parentComponent,
10143
+ ) {
10144
+ const failedPkgList = [];
10145
+ const rootList = [];
10146
+ const dependenciesList = [];
10147
+ let result = undefined;
10148
+ const env = {
10149
+ ...process.env,
10150
+ };
10151
+ if (!process.env.VIRTUAL_ENV && !process.env.CONDA_PREFIX) {
10152
+ // Create a virtual environment
10153
+ result = spawnSync(PYTHON_CMD, ["-m", "venv", tempVenvDir], {
10154
+ encoding: "utf-8",
10155
+ shell: isWin,
10156
+ });
10157
+ if (result.status !== 0 || result.error) {
10158
+ console.log("Virtual env creation has failed. Unable to continue.");
10159
+ return {};
10160
+ }
10161
+ if (DEBUG_MODE) {
10162
+ console.log("Using the virtual environment", tempVenvDir);
10163
+ }
10164
+ env.VIRTUAL_ENV = tempVenvDir;
10165
+ env.PATH = `${join(
10166
+ tempVenvDir,
10167
+ platform() === "win32" ? "Scripts" : "bin",
10168
+ )}${_delimiter}${process.env.PATH || ""}`;
10169
+ // When cdxgen is invoked with the container image, we seem to be including unnecessary packages from the image.
10170
+ // This workaround, unsets PYTHONPATH to suppress the pre-installed packages
10171
+ if (
10172
+ env?.PYTHONPATH === "/opt/pypi" &&
10173
+ env?.CDXGEN_IN_CONTAINER === "true"
10174
+ ) {
10175
+ env.PYTHONPATH = undefined;
10176
+ }
10177
+ }
10178
+ const python_cmd_for_tree = get_python_command_from_env(env);
10179
+ let pipInstallArgs = ["-m", "pip", "install", "--disable-pip-version-check"];
10180
+ // Support for passing additional arguments to pip
10181
+ // Eg: --python-version 3.10 --ignore-requires-python --no-warn-conflicts
10182
+ if (process?.env?.PIP_INSTALL_ARGS) {
10183
+ const addArgs = process.env.PIP_INSTALL_ARGS.split(" ");
10184
+ pipInstallArgs = pipInstallArgs.concat(addArgs);
10185
+ } else {
10186
+ pipInstallArgs = pipInstallArgs.concat([
10187
+ "--ignore-requires-python",
10188
+ "--no-compile",
10189
+ "--no-warn-script-location",
10190
+ "--no-warn-conflicts",
10191
+ ]);
10192
+ }
10193
+ if (DEBUG_MODE) {
10194
+ console.log(
10195
+ "Installing",
10196
+ pkgList.length,
10197
+ "using the command",
10198
+ python_cmd_for_tree,
10199
+ pipInstallArgs.join(" "),
10200
+ );
10201
+ }
10202
+ for (const apkg of pkgList) {
10203
+ let pkgSpecifier = apkg.name;
10204
+ if (apkg.version && apkg.version !== "latest") {
10205
+ pkgSpecifier = `${apkg.name}==${apkg.version}`;
10206
+ } else if (apkg.properties) {
10207
+ let versionSpecifierFound = false;
10208
+ for (const aprop of apkg.properties) {
10209
+ if (aprop.name === "cdx:pypi:versionSpecifiers") {
10210
+ pkgSpecifier = `${apkg.name}${aprop.value}`;
10211
+ versionSpecifierFound = true;
10212
+ break;
10213
+ }
10214
+ }
10215
+ if (!versionSpecifierFound) {
10216
+ failedPkgList.push(apkg);
10217
+ continue;
10218
+ }
10219
+ } else {
10220
+ failedPkgList.push(apkg);
10221
+ continue;
10222
+ }
10223
+ if (DEBUG_MODE) {
10224
+ console.log("Installing", pkgSpecifier);
10225
+ }
10226
+ // Attempt to perform pip install for pkgSpecifier
10227
+ const result = spawnSync(
10228
+ python_cmd_for_tree,
10229
+ [...pipInstallArgs, pkgSpecifier],
10230
+ {
10231
+ cwd: basePath,
10232
+ encoding: "utf-8",
10233
+ timeout: TIMEOUT_MS,
10234
+ shell: isWin,
10235
+ env,
10236
+ },
10237
+ );
10238
+ if (result.status !== 0 || result.error) {
10239
+ failedPkgList.push(apkg);
10240
+ if (DEBUG_MODE) {
10241
+ console.log(apkg.name, "failed to install.");
10242
+ }
10243
+ }
10244
+ }
10245
+ // Did any package get installed successfully?
10246
+ if (failedPkgList.length < pkgList.length) {
10247
+ const dependenciesMap = {};
10248
+ const tree = getTreeWithPlugin(env, python_cmd_for_tree, basePath);
10249
+ for (const t of tree) {
10250
+ const name = t.name.replace(/_/g, "-").toLowerCase();
10251
+ // We can ignore excluded components such as build tools
10252
+ if (PYTHON_EXCLUDED_COMPONENTS.includes(name)) {
10253
+ continue;
10254
+ }
10255
+ if (parentComponent && parentComponent.name === t.name) {
10256
+ t.version = parentComponent.version;
10257
+ } else if (t.version && t.version === "latest") {
10258
+ continue;
10259
+ }
10260
+ const version = t.version;
10261
+ const purlString = new PackageURL(
10262
+ "pypi",
10263
+ "",
10264
+ name,
10265
+ version,
10266
+ null,
10267
+ null,
10268
+ ).toString();
10269
+ const apkg = {
10270
+ name,
10271
+ version,
10272
+ purl: purlString,
10273
+ type: "library",
10274
+ "bom-ref": decodeURIComponent(purlString),
10275
+ evidence: {
10276
+ identity: {
10277
+ field: "purl",
10278
+ confidence: 0.5,
10279
+ methods: [
10280
+ {
10281
+ technique: "instrumentation",
10282
+ confidence: 0.5,
10283
+ value: env.VIRTUAL_ENV,
10284
+ },
10285
+ ],
10286
+ },
10287
+ },
10288
+ };
10289
+ // These packages have lower confidence
10290
+ pkgList.push(apkg);
10291
+ rootList.push({
10292
+ name,
10293
+ version,
10294
+ purl: purlString,
10295
+ "bom-ref": decodeURIComponent(purlString),
10296
+ });
10297
+ flattenDeps(dependenciesMap, pkgList, undefined, t);
10298
+ } // end for
10299
+ for (const k of Object.keys(dependenciesMap)) {
10300
+ dependenciesList.push({ ref: k, dependsOn: dependenciesMap[k] });
10301
+ }
10302
+ } // end if
10303
+ return {
10304
+ failedPkgList,
10305
+ rootList,
10306
+ dependenciesList,
10307
+ };
10308
+ }
10309
+
9936
10310
  // taken from a very old package https://github.com/keithamus/parse-packagejson-name/blob/master/index.js
9937
10311
  export function parsePackageJsonName(name) {
9938
10312
  const nameRegExp = /^(?:@([^/]+)\/)?(([^.]+)(?:\.(.*))?)$/;
@@ -11236,3 +11610,22 @@ export function isValidIriReference(iri) {
11236
11610
  }
11237
11611
  return false;
11238
11612
  }
11613
+
11614
+ /**
11615
+ * Method to check if a given dependency tree is partial or not.
11616
+ *
11617
+ * @param {Array} dependencies List of dependencies
11618
+ * @returns {Boolean} True if the dependency tree lacks any non-root parents without children. False otherwise.
11619
+ */
11620
+ export function isPartialTree(dependencies) {
11621
+ if (dependencies?.length <= 1) {
11622
+ return true;
11623
+ }
11624
+ let parentsWithChildsCount = 0;
11625
+ for (const adep of dependencies) {
11626
+ if (adep?.dependsOn.length > 0) {
11627
+ parentsWithChildsCount++;
11628
+ }
11629
+ }
11630
+ return parentsWithChildsCount <= 1;
11631
+ }
package/utils.test.js CHANGED
@@ -16,7 +16,9 @@ import {
16
16
  getRepoLicense,
17
17
  guessPypiMatchingVersion,
18
18
  hasAnyProjectType,
19
+ isPartialTree,
19
20
  isValidIriReference,
21
+ mapConanPkgRefToPurlStringAndNameAndVersion,
20
22
  parseBazelActionGraph,
21
23
  parseBazelBuild,
22
24
  parseBazelSkyframe,
@@ -1552,17 +1554,147 @@ test("parse conan data", () => {
1552
1554
  dep_list = parseConanData(
1553
1555
  readFileSync("./test/data/cmakes/conanfile1.txt", { encoding: "utf-8" }),
1554
1556
  );
1555
- expect(dep_list.length).toEqual(42);
1557
+ expect(dep_list.length).toEqual(43);
1556
1558
  expect(dep_list[0]).toEqual({
1557
1559
  "bom-ref":
1558
- "pkg:conan/7-Zip@19.00?revision=bb67aa9bc0da3feddc68ca9f334f4c8b",
1560
+ "pkg:conan/7-Zip@19.00?channel=stable&rrev=bb67aa9bc0da3feddc68ca9f334f4c8b&user=iw",
1559
1561
  name: "7-Zip",
1560
- purl: "pkg:conan/7-Zip@19.00?revision=bb67aa9bc0da3feddc68ca9f334f4c8b",
1562
+ purl: "pkg:conan/7-Zip@19.00?channel=stable&rrev=bb67aa9bc0da3feddc68ca9f334f4c8b&user=iw",
1561
1563
  scope: "required",
1562
1564
  version: "19.00",
1563
1565
  });
1564
1566
  });
1565
1567
 
1568
+ test("conan package reference mapper to pURL", () => {
1569
+ const checkParseResult = (inputPkgRef, expectedPurl) => {
1570
+ const [purl, name, version] =
1571
+ mapConanPkgRefToPurlStringAndNameAndVersion(inputPkgRef);
1572
+ expect(purl).toEqual(expectedPurl);
1573
+
1574
+ const expectedPurlPrefix = `pkg:conan/${name}@${version}`;
1575
+ expect(purl.substring(0, expectedPurlPrefix.length)).toEqual(
1576
+ expectedPurlPrefix,
1577
+ );
1578
+ };
1579
+
1580
+ checkParseResult("testpkg", "pkg:conan/testpkg@latest");
1581
+
1582
+ checkParseResult("testpkg/1.2.3", "pkg:conan/testpkg@1.2.3");
1583
+
1584
+ checkParseResult(
1585
+ "testpkg/1.2.3#recipe_revision",
1586
+ "pkg:conan/testpkg@1.2.3?rrev=recipe_revision",
1587
+ );
1588
+
1589
+ checkParseResult(
1590
+ "testpkg/1.2.3@someuser/somechannel",
1591
+ "pkg:conan/testpkg@1.2.3?channel=somechannel&user=someuser",
1592
+ );
1593
+
1594
+ checkParseResult(
1595
+ "testpkg/1.2.3@someuser/somechannel#recipe_revision",
1596
+ "pkg:conan/testpkg@1.2.3?channel=somechannel&rrev=recipe_revision&user=someuser",
1597
+ );
1598
+
1599
+ checkParseResult(
1600
+ "testpkg/1.2.3@someuser/somechannel#recipe_revision:package_id#package_revision",
1601
+ "pkg:conan/testpkg@1.2.3" +
1602
+ "?channel=somechannel" +
1603
+ "&prev=package_revision" +
1604
+ "&rrev=recipe_revision" +
1605
+ "&user=someuser",
1606
+ );
1607
+
1608
+ const expectParseError = (pkgRef) => {
1609
+ const result = mapConanPkgRefToPurlStringAndNameAndVersion(pkgRef);
1610
+ expect(result[0]).toBe(null);
1611
+ expect(result[1]).toBe(null);
1612
+ expect(result[2]).toBe(null);
1613
+ };
1614
+
1615
+ expectParseError("testpkg/"); // empty version
1616
+ expectParseError("testpkg/1.2.3@"); // empty user
1617
+ expectParseError("testpkg/1.2.3@someuser"); // pkg ref is not allowed to stop here
1618
+ expectParseError("testpkg/1.2.3@someuser/"); // empty channel
1619
+ expectParseError("testpkg/1.2.3@someuser/somechannel#"); // empty recipe revision
1620
+ expectParseError("testpkg/1.2.3@someuser/somechannel#recipe_revision:"); // empty package id
1621
+ expectParseError(
1622
+ "testpkg/1.2.3@someuser/somechannel#recipe_revision:package_id",
1623
+ ); // pkg ref is not allowed to stop here
1624
+ expectParseError(
1625
+ "testpkg/1.2.3@someuser/somechannel#recipe_revision:package_id#",
1626
+ ); // empty package revision
1627
+ expectParseError("testpkg/1.2.3/unexpected"); // unexpected pkg ref segment separator
1628
+ expectParseError("testpkg/1.2.3@someuser/somechannel/unexpected"); // unexpected pkg ref segment separator
1629
+ expectParseError(
1630
+ "testpkg/1.2.3@someuser/somechannel#recipe_revision/unexpected",
1631
+ ); // unexpected pkg ref segment separator
1632
+ expectParseError(
1633
+ "testpkg/1.2.3@someuser/somechannel#recipe_revision:package_id/unexpected",
1634
+ ); // unexpected pkg ref segment separator
1635
+ expectParseError(
1636
+ "testpkg/1.2.3@someuser/somechannel#recipe_revision:package_id#package_revision/unexpected",
1637
+ ); // unexpected pkg ref segment separator
1638
+ });
1639
+
1640
+ test("parse conan data where packages use custom user/channel", () => {
1641
+ let dep_list = parseConanLockData(
1642
+ readFileSync("./test/data/conan.with_custom_pkg_user_channel.lock", {
1643
+ encoding: "utf-8",
1644
+ }),
1645
+ );
1646
+ expect(dep_list.length).toEqual(4);
1647
+ expect(dep_list[0]).toEqual({
1648
+ name: "libcurl",
1649
+ version: "8.1.2",
1650
+ "bom-ref":
1651
+ "pkg:conan/libcurl@8.1.2?channel=stable&rrev=25215c550633ef0224152bc2c0556698&user=internal",
1652
+ purl: "pkg:conan/libcurl@8.1.2?channel=stable&rrev=25215c550633ef0224152bc2c0556698&user=internal",
1653
+ });
1654
+ expect(dep_list[1]).toEqual({
1655
+ name: "openssl",
1656
+ version: "3.1.0",
1657
+ "bom-ref":
1658
+ "pkg:conan/openssl@3.1.0?channel=stable&rrev=c9c6ab43aa40bafacf8b37c5948cdb1f&user=internal",
1659
+ purl: "pkg:conan/openssl@3.1.0?channel=stable&rrev=c9c6ab43aa40bafacf8b37c5948cdb1f&user=internal",
1660
+ });
1661
+ expect(dep_list[2]).toEqual({
1662
+ name: "zlib",
1663
+ version: "1.2.13",
1664
+ "bom-ref":
1665
+ "pkg:conan/zlib@1.2.13?channel=stable&rrev=aee6a56ff7927dc7261c55eb2db4fc5b&user=internal",
1666
+ purl: "pkg:conan/zlib@1.2.13?channel=stable&rrev=aee6a56ff7927dc7261c55eb2db4fc5b&user=internal",
1667
+ });
1668
+ expect(dep_list[3]).toEqual({
1669
+ name: "fmt",
1670
+ version: "10.0.0",
1671
+ purl: "pkg:conan/fmt@10.0.0?channel=stable&rrev=79e7cc169695bc058fb606f20df6bb10&user=internal",
1672
+ "bom-ref":
1673
+ "pkg:conan/fmt@10.0.0?channel=stable&rrev=79e7cc169695bc058fb606f20df6bb10&user=internal",
1674
+ });
1675
+
1676
+ dep_list = parseConanData(
1677
+ readFileSync("./test/data/conanfile.with_custom_pkg_user_channel.txt", {
1678
+ encoding: "utf-8",
1679
+ }),
1680
+ );
1681
+ expect(dep_list.length).toEqual(2);
1682
+ expect(dep_list[0]).toEqual({
1683
+ name: "libcurl",
1684
+ version: "8.1.2",
1685
+ "bom-ref": "pkg:conan/libcurl@8.1.2?channel=stable&user=internal",
1686
+ purl: "pkg:conan/libcurl@8.1.2?channel=stable&user=internal",
1687
+ scope: "required",
1688
+ });
1689
+ expect(dep_list[1]).toEqual({
1690
+ name: "fmt",
1691
+ version: "10.0.0",
1692
+ purl: "pkg:conan/fmt@10.0.0?channel=stable&user=internal",
1693
+ "bom-ref": "pkg:conan/fmt@10.0.0?channel=stable&user=internal",
1694
+ scope: "optional",
1695
+ });
1696
+ });
1697
+
1566
1698
  test("parse clojure data", () => {
1567
1699
  expect(parseLeiningenData(null)).toEqual([]);
1568
1700
  let dep_list = parseLeiningenData(
@@ -2249,6 +2381,8 @@ test("parsePomMetadata", async () => {
2249
2381
  expect(data.length).toEqual(deps.length);
2250
2382
  });
2251
2383
 
2384
+ // These tests are disabled because they are returning undefined
2385
+ /*
2252
2386
  test("get repo license", async () => {
2253
2387
  let license = await getRepoLicense(
2254
2388
  "https://github.com/ShiftLeftSecurity/sast-scan",
@@ -2271,8 +2405,6 @@ test("get repo license", async () => {
2271
2405
  url: "https://github.com/CycloneDX/cdxgen/blob/master/LICENSE",
2272
2406
  });
2273
2407
 
2274
- // These tests are disabled because they are returning undefined
2275
- /*
2276
2408
  license = await getRepoLicense("https://cloud.google.com/go", {
2277
2409
  group: "cloud.google.com",
2278
2410
  name: "go"
@@ -2287,8 +2419,8 @@ test("get repo license", async () => {
2287
2419
  id: "MIT",
2288
2420
  url: "https://github.com/ugorji/go/blob/master/LICENSE"
2289
2421
  });
2290
- */
2291
2422
  });
2423
+ */
2292
2424
 
2293
2425
  test("get go pkg license", async () => {
2294
2426
  let license = await getGoPkgLicense({
@@ -2852,8 +2984,8 @@ test("parsePnpmLock", async () => {
2852
2984
  expect(parsedList.dependenciesList).toHaveLength(462);
2853
2985
  expect(parsedList.pkgList.filter((pkg) => !pkg.scope)).toHaveLength(3);
2854
2986
  parsedList = await parsePnpmLock("./pnpm-lock.yaml");
2855
- expect(parsedList.pkgList.length).toEqual(653);
2856
- expect(parsedList.dependenciesList.length).toEqual(653);
2987
+ expect(parsedList.pkgList.length).toEqual(652);
2988
+ expect(parsedList.dependenciesList.length).toEqual(652);
2857
2989
  expect(parsedList.pkgList[0]).toEqual({
2858
2990
  group: "@ampproject",
2859
2991
  name: "remapping",
@@ -2919,6 +3051,7 @@ test("parseYarnLock", async () => {
2919
3051
  },
2920
3052
  });
2921
3053
  expect(parsedList.dependenciesList.length).toEqual(56);
3054
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
2922
3055
  identMap = yarnLockToIdentMap(
2923
3056
  readFileSync("./test/data/yarn_locks/yarn.lock", "utf8"),
2924
3057
  );
@@ -2962,6 +3095,7 @@ test("parseYarnLock", async () => {
2962
3095
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarn-multi.lock");
2963
3096
  expect(parsedList.pkgList.length).toEqual(1909);
2964
3097
  expect(parsedList.dependenciesList.length).toEqual(1909);
3098
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
2965
3099
  expect(parsedList.pkgList[0]).toEqual({
2966
3100
  _integrity:
2967
3101
  "sha512-zpruxnFMz6K94gs2pqc3sidzFDbQpKT5D6P/J/I9s8ekHZ5eczgnRp6pqXC86Bh7+44j/btpmOT0kwiboyqTnA==",
@@ -2994,6 +3128,7 @@ test("parseYarnLock", async () => {
2994
3128
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarn-light.lock");
2995
3129
  expect(parsedList.pkgList.length).toEqual(315);
2996
3130
  expect(parsedList.dependenciesList.length).toEqual(315);
3131
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
2997
3132
  expect(parsedList.pkgList[0]).toEqual({
2998
3133
  _integrity:
2999
3134
  "sha512-rZ1k9kQvJX21Vwgx1L6kSQ6yeXo9cCMyqURSnjG+MRoJn+Mr3LblxmVdzScHXRzv0N9yzy49oG7Bqxp9Knyv/g==",
@@ -3026,6 +3161,7 @@ test("parseYarnLock", async () => {
3026
3161
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarn3.lock");
3027
3162
  expect(parsedList.pkgList.length).toEqual(5);
3028
3163
  expect(parsedList.dependenciesList.length).toEqual(5);
3164
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3029
3165
  expect(parsedList.pkgList[1]).toEqual({
3030
3166
  _integrity:
3031
3167
  "sha512-+X9Jn4mPI+RYV0ITiiLyJSYlT9um111BocJSaztsxXR+9ZxWErpzdfQqyk+EYZUOklugjJkerQZRtJGLfJeClw==",
@@ -3058,6 +3194,7 @@ test("parseYarnLock", async () => {
3058
3194
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarnv2.lock");
3059
3195
  expect(parsedList.pkgList.length).toEqual(1088);
3060
3196
  expect(parsedList.dependenciesList.length).toEqual(1088);
3197
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3061
3198
  expect(parsedList.pkgList[0]).toEqual({
3062
3199
  _integrity:
3063
3200
  "sha512-G0U5NjBUYIs39l1J1ckgpVfVX2IxpzRAIT4/2An86O2Mcri3k5xNu7/RRkfObo12wN9s7BmnREAMhH7252oZiA==",
@@ -3089,6 +3226,7 @@ test("parseYarnLock", async () => {
3089
3226
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarnv3.lock");
3090
3227
  expect(parsedList.pkgList.length).toEqual(363);
3091
3228
  expect(parsedList.dependenciesList.length).toEqual(363);
3229
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3092
3230
  expect(parsedList.pkgList[0]).toEqual({
3093
3231
  _integrity:
3094
3232
  "sha512-vtU+q0TmdIDmezU7lKub73vObN6nmd3lkcKWz7R9hyNI8gz5o7grDb+FML9nykOLW+09gGIup2xyJ86j5vBKpg==",
@@ -3120,6 +3258,7 @@ test("parseYarnLock", async () => {
3120
3258
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarn4.lock");
3121
3259
  expect(parsedList.pkgList.length).toEqual(1);
3122
3260
  expect(parsedList.dependenciesList.length).toEqual(1);
3261
+ expect(isPartialTree(parsedList.dependenciesList)).toBeTruthy();
3123
3262
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarn-at.lock");
3124
3263
  expect(parsedList.pkgList.length).toEqual(4);
3125
3264
  expect(parsedList.dependenciesList.length).toEqual(4);
@@ -3151,30 +3290,51 @@ test("parseYarnLock", async () => {
3151
3290
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarn5.lock");
3152
3291
  expect(parsedList.pkgList.length).toEqual(1962);
3153
3292
  expect(parsedList.dependenciesList.length).toEqual(1962);
3293
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3154
3294
  expect(parsedList.pkgList[0].purl).toEqual(
3155
3295
  "pkg:npm/%40ampproject/remapping@2.2.0",
3156
3296
  );
3157
3297
  expect(parsedList.pkgList[0]["bom-ref"]).toEqual(
3158
3298
  "pkg:npm/@ampproject/remapping@2.2.0",
3159
3299
  );
3300
+ expect(parsedList.dependenciesList[1]).toEqual({
3301
+ ref: "pkg:npm/@babel/code-frame@7.12.11",
3302
+ dependsOn: ["pkg:npm/@babel/highlight@7.18.6"],
3303
+ });
3160
3304
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarn6.lock");
3161
3305
  expect(parsedList.pkgList.length).toEqual(1472);
3162
3306
  expect(parsedList.dependenciesList.length).toEqual(1472);
3307
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3163
3308
  expect(parsedList.pkgList[0].purl).toEqual(
3164
3309
  "pkg:npm/%40aashutoshrathi/word-wrap@1.2.6",
3165
3310
  );
3166
3311
  expect(parsedList.pkgList[0]["bom-ref"]).toEqual(
3167
3312
  "pkg:npm/@aashutoshrathi/word-wrap@1.2.6",
3168
3313
  );
3314
+ expect(parsedList.dependenciesList[1]).toEqual({
3315
+ ref: "pkg:npm/@ampproject/remapping@2.2.1",
3316
+ dependsOn: [
3317
+ "pkg:npm/@jridgewell/gen-mapping@0.3.3",
3318
+ "pkg:npm/@jridgewell/trace-mapping@0.3.19",
3319
+ ],
3320
+ });
3169
3321
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarn7.lock");
3170
3322
  expect(parsedList.pkgList.length).toEqual(1350);
3171
3323
  expect(parsedList.dependenciesList.length).toEqual(1347);
3324
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3172
3325
  expect(parsedList.pkgList[0].purl).toEqual(
3173
3326
  "pkg:npm/%40aashutoshrathi/word-wrap@1.2.6",
3174
3327
  );
3175
3328
  expect(parsedList.pkgList[0]["bom-ref"]).toEqual(
3176
3329
  "pkg:npm/@aashutoshrathi/word-wrap@1.2.6",
3177
3330
  );
3331
+ expect(parsedList.dependenciesList[1]).toEqual({
3332
+ ref: "pkg:npm/@ampproject/remapping@2.2.1",
3333
+ dependsOn: [
3334
+ "pkg:npm/@jridgewell/gen-mapping@0.3.3",
3335
+ "pkg:npm/@jridgewell/trace-mapping@0.3.19",
3336
+ ],
3337
+ });
3178
3338
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarnv4.lock");
3179
3339
  expect(parsedList.pkgList.length).toEqual(1851);
3180
3340
  expect(parsedList.dependenciesList.length).toEqual(1851);
@@ -3184,6 +3344,11 @@ test("parseYarnLock", async () => {
3184
3344
  expect(parsedList.pkgList[0]["bom-ref"]).toEqual(
3185
3345
  "pkg:npm/@aashutoshrathi/word-wrap@1.2.6",
3186
3346
  );
3347
+ expect(parsedList.dependenciesList[1]).toEqual({
3348
+ ref: "pkg:npm/@actions/core@1.2.6",
3349
+ dependsOn: [],
3350
+ });
3351
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3187
3352
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarnv4.1.lock");
3188
3353
  expect(parsedList.pkgList.length).toEqual(861);
3189
3354
  expect(parsedList.dependenciesList.length).toEqual(858);
@@ -3196,11 +3361,27 @@ test("parseYarnLock", async () => {
3196
3361
  expect(parsedList.pkgList[0]._integrity).toEqual(
3197
3362
  "sha512-U8KyMaYaRnkrOaDUO8T093a7RUKqV+4EkwZ2gC5VASgsL8iqwU5M0fESD/i1Jha2/1q1Oa0wqiJ31yZES3Fhnw==",
3198
3363
  );
3199
-
3364
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3200
3365
  parsedList = await parseYarnLock("./test/data/yarn_locks/yarnv1-fs.lock");
3201
3366
  expect(parsedList.pkgList.length).toEqual(882);
3202
3367
  expect(parsedList.dependenciesList.length).toEqual(882);
3203
3368
  expect(parsedList.pkgList[0].purl).toEqual("pkg:npm/abbrev@1.0.9");
3369
+ expect(parsedList.dependenciesList[1]).toEqual({
3370
+ ref: "pkg:npm/accepts@1.3.3",
3371
+ dependsOn: ["pkg:npm/mime-types@2.1.12", "pkg:npm/negotiator@0.6.1"],
3372
+ });
3373
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3374
+ parsedList = await parseYarnLock("./test/data/yarn_locks/yarnv1-empty.lock");
3375
+ expect(parsedList.pkgList.length).toEqual(770);
3376
+ expect(parsedList.dependenciesList.length).toEqual(770);
3377
+ expect(isPartialTree(parsedList.dependenciesList)).toBeFalsy();
3378
+ expect(parsedList.pkgList[0].purl).toEqual(
3379
+ "pkg:npm/%40ampproject/remapping@2.2.0",
3380
+ );
3381
+ expect(parsedList.dependenciesList[1]).toEqual({
3382
+ ref: "pkg:npm/@aws-sdk/shared-ini-file-loader@3.188.0",
3383
+ dependsOn: ["pkg:npm/@aws-sdk/types@3.188.0", "pkg:npm/tslib@2.4.0"],
3384
+ });
3204
3385
  });
3205
3386
 
3206
3387
  test("parseComposerLock", () => {
package/validator.js CHANGED
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
3
3
  import Ajv from "ajv";
4
4
  import addFormats from "ajv-formats";
5
5
  import { PackageURL } from "packageurl-js";
6
- import { DEBUG_MODE } from "./utils.js";
6
+ import { DEBUG_MODE, isPartialTree } from "./utils.js";
7
7
 
8
8
  import { URL, fileURLToPath } from "node:url";
9
9
  let url = import.meta.url;
@@ -217,6 +217,9 @@ export const validateRefs = (bomJson) => {
217
217
  const warningsList = [];
218
218
  const refMap = buildRefs(bomJson);
219
219
  if (bomJson?.dependencies) {
220
+ if (isPartialTree(bomJson.dependencies)) {
221
+ warningsList.push("Dependency tree is partial lacking child nodes.");
222
+ }
220
223
  for (const dep of bomJson.dependencies) {
221
224
  if (
222
225
  dep.ref.includes("%40") ||