@cyclonedx/cdxgen 8.1.9 → 8.2.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
@@ -23,6 +23,7 @@ When used with plugins, cdxgen could generate an SBoM for Linux docker images an
23
23
  | elixir | mix.lock | Yes |
24
24
  | c/c++ | conan.lock, conanfile.txt | Yes only for conan.lock |
25
25
  | clojure | Clojure CLI (deps.edn), Leiningen (project.clj) | Yes unless the files are parsed manually due to lack of clojure cli or leiningen command |
26
+ | swift | Package.resolved, Package.swift (swiftpm) | Yes |
26
27
  | docker / oci image | All supported languages. Linux OS packages with plugins [4] | Best effort based on lock files |
27
28
  | GitHub Actions | .github/workflows/\*.yml | N/A |
28
29
  | Linux | All supported languages. Linux OS packages with plugins [5] | Best effort based on lock files |
package/binary.js CHANGED
@@ -312,7 +312,9 @@ const getOSPackages = (src) => {
312
312
  if (DEBUG_MODE) {
313
313
  console.log(`Cleaning up ${tempDir}`);
314
314
  }
315
- fs.rmSync(tempDir, { recursive: true, force: true });
315
+ if (fs.rmSync) {
316
+ fs.rmSync(tempDir, { recursive: true, force: true });
317
+ }
316
318
  }
317
319
  if (tmpBom && tmpBom.components) {
318
320
  for (const comp of tmpBom.components) {
package/docker.js CHANGED
@@ -117,7 +117,11 @@ const getDefaultOptions = () => {
117
117
  ? "npipe//./pipe/docker_engine:"
118
118
  : "unix:/var/run/docker.sock:";
119
119
  */
120
- opts.prefixUrl = isWin ? WIN_LOCAL_TLS : "unix:/var/run/docker.sock:";
120
+ opts.prefixUrl = isWin
121
+ ? WIN_LOCAL_TLS
122
+ : isDockerRootless
123
+ ? `unix:${os.homedir()}/.docker/run/docker.sock:`
124
+ : "unix:/var/run/docker.sock:";
121
125
  }
122
126
  }
123
127
  } else {
@@ -162,6 +166,18 @@ const getConnection = async (options) => {
162
166
  }
163
167
  } catch (err) {
164
168
  // console.log(err, opts);
169
+ opts.prefixUrl = `unix:${os.homedir()}/.docker/run/docker.sock:`;
170
+ try {
171
+ await got.get("_ping", opts);
172
+ dockerConn = got.extend(opts);
173
+ isDockerRootless = true;
174
+ if (DEBUG_MODE) {
175
+ console.log("Docker service in rootless mode detected!");
176
+ }
177
+ return dockerConn;
178
+ } catch (err) {
179
+ // console.log(err, opts);
180
+ }
165
181
  try {
166
182
  if (isWin) {
167
183
  opts.prefixUrl = WIN_LOCAL_TLS;
@@ -323,7 +339,7 @@ const getImage = async (fullImageName) => {
323
339
  }
324
340
  try {
325
341
  localData = await makeRequest(`images/${repo}/json`);
326
- if (DEBUG_MODE) {
342
+ if (DEBUG_MODE && localData) {
327
343
  console.log(localData);
328
344
  }
329
345
  } catch (err) {
@@ -603,7 +619,9 @@ const exportImage = async (fullImageName) => {
603
619
  if (DEBUG_MODE) {
604
620
  console.log(`Cleaning up ${imageTarFile}`);
605
621
  }
606
- fs.rmSync(imageTarFile, { force: true });
622
+ if (fs.rmSync) {
623
+ fs.rmSync(imageTarFile, { force: true });
624
+ }
607
625
  }
608
626
  } else {
609
627
  let client = await getConnection();
package/index.js CHANGED
@@ -44,6 +44,11 @@ if (process.env.PIP_CMD) {
44
44
  PIP_CMD = process.env.PIP_CMD;
45
45
  }
46
46
 
47
+ let SWIFT_CMD = "swift";
48
+ if (process.env.SWIFT_CMD) {
49
+ SWIFT_CMD = process.env.SWIFT_CMD;
50
+ }
51
+
47
52
  // Construct sbt cache directory
48
53
  let SBT_CACHE_DIR =
49
54
  process.env.SBT_CACHE_DIR || pathLib.join(os.homedir(), ".ivy2", "cache");
@@ -61,6 +66,29 @@ const HASH_PATTERN =
61
66
  // Timeout milliseconds. Default 10 mins
62
67
  const TIMEOUT_MS = parseInt(process.env.CDXGEN_TIMEOUT_MS) || 10 * 60 * 1000;
63
68
 
69
+ const createDefaultParentComponent = (path) => {
70
+ // Create a parent component based on the directory name
71
+ let dirName = pathLib.dirname(path);
72
+ const tmpA = dirName.split(pathLib.sep);
73
+ dirName = tmpA[tmpA.length - 1];
74
+ const parentComponent = {
75
+ group: "",
76
+ name: dirName,
77
+ type: "application"
78
+ };
79
+ const ppurl = new PackageURL(
80
+ "application",
81
+ parentComponent.group,
82
+ parentComponent.name,
83
+ parentComponent.version,
84
+ null,
85
+ null
86
+ ).toString();
87
+ parentComponent["bom-ref"] = ppurl;
88
+ parentComponent["purl"] = ppurl;
89
+ return parentComponent;
90
+ };
91
+
64
92
  const determineParentComponent = (options) => {
65
93
  let parentComponent = undefined;
66
94
  if (options.projectName && options.projectVersion) {
@@ -491,7 +519,10 @@ function addComponent(
491
519
  // Skip @types package for npm
492
520
  if (
493
521
  ptype == "npm" &&
494
- (group === "types" || !name || name.startsWith("@types"))
522
+ (group === "types" ||
523
+ group === "@types" ||
524
+ !name ||
525
+ name.startsWith("@types"))
495
526
  ) {
496
527
  return;
497
528
  }
@@ -503,7 +534,14 @@ function addComponent(
503
534
 
504
535
  let purl =
505
536
  pkg.purl ||
506
- new PackageURL(ptype, group, name, version, pkg.qualifiers, pkg.subpath);
537
+ new PackageURL(
538
+ ptype,
539
+ encodeURIComponent(group),
540
+ encodeURIComponent(name),
541
+ version,
542
+ pkg.qualifiers,
543
+ pkg.subpath
544
+ );
507
545
  let purlString = purl.toString();
508
546
  purlString = decodeURIComponent(purlString);
509
547
  let description = { "#cdata": pkg.description };
@@ -1008,13 +1046,13 @@ const createJavaBom = async (path, options) => {
1008
1046
  "Resolve the above maven error. This could be due to the following:\n"
1009
1047
  );
1010
1048
  console.log(
1011
- "1. Java version requirement - Scan or the CI build agent could be using an incompatible version"
1049
+ "1. Java version requirement: cdxgen container image bundles Java 17 with gradle 8 which might be incompatible."
1012
1050
  );
1013
1051
  console.log(
1014
- "2. Private maven repository is not serving all the required maven plugins correctly. Refer to your registry documentation to add support for jitpack.io"
1052
+ "2. Private dependencies cannot be downloaded: Check if any additional arguments must be passed to maven and set them via MVN_ARGS environment variable."
1015
1053
  );
1016
1054
  console.log(
1017
- "3. Check if all required environment variables including any maven profile arguments are passed correctly to this tool"
1055
+ "3. Check if all required environment variables including any maven profile arguments are passed correctly to this tool."
1018
1056
  );
1019
1057
  // Do not fall back to methods that can produce incomplete results when failOnError is set
1020
1058
  options.failOnError && process.exit(1);
@@ -1125,7 +1163,7 @@ const createJavaBom = async (path, options) => {
1125
1163
  console.error(result.stdout, result.stderr);
1126
1164
  }
1127
1165
  console.log(
1128
- "1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7."
1166
+ "1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7.\n cdxgen container image bundles Java 17 with gradle 8 which might be incompatible."
1129
1167
  );
1130
1168
  options.failOnError && process.exit(1);
1131
1169
  }
@@ -1251,7 +1289,7 @@ const createJavaBom = async (path, options) => {
1251
1289
  }
1252
1290
  if (DEBUG_MODE || !result.stderr || options.failOnError) {
1253
1291
  console.log(
1254
- "1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7."
1292
+ "1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7.\n cdxgen container image bundles Java 17 with gradle 8 which might be incompatible."
1255
1293
  );
1256
1294
  console.log(
1257
1295
  "2. When using tools such as sdkman, the init script must be invoked to set the PATH variables correctly."
@@ -2805,6 +2843,91 @@ const createHelmBom = async (path, options) => {
2805
2843
  return {};
2806
2844
  };
2807
2845
 
2846
+ /**
2847
+ * Function to create bom string for swift projects
2848
+ *
2849
+ * @param path to the project
2850
+ * @param options Parse options from the cli
2851
+ */
2852
+ const createSwiftBom = async (path, options) => {
2853
+ const swiftFiles = utils.getAllFiles(
2854
+ path,
2855
+ (options.multiProject ? "**/" : "") + "Package*.swift"
2856
+ );
2857
+ const pkgResolvedFiles = utils.getAllFiles(
2858
+ path,
2859
+ (options.multiProject ? "**/" : "") + "Package.resolved"
2860
+ );
2861
+ let pkgList = [];
2862
+ let dependencies = [];
2863
+ let parentComponent = {};
2864
+ let completedPath = [];
2865
+ if (pkgResolvedFiles.length) {
2866
+ for (let f of pkgResolvedFiles) {
2867
+ if (!parentComponent || !Object.keys(parentComponent).length) {
2868
+ parentComponent = createDefaultParentComponent(f);
2869
+ }
2870
+ if (DEBUG_MODE) {
2871
+ console.log("Parsing", f);
2872
+ }
2873
+ const dlist = utils.parseSwiftResolved(f);
2874
+ if (dlist && dlist.length) {
2875
+ pkgList = pkgList.concat(dlist);
2876
+ }
2877
+ }
2878
+ } else if (swiftFiles.length) {
2879
+ for (let f of swiftFiles) {
2880
+ const basePath = pathLib.dirname(f);
2881
+ if (completedPath.includes(basePath)) {
2882
+ continue;
2883
+ }
2884
+ let treeData = undefined;
2885
+ if (DEBUG_MODE) {
2886
+ console.log("Executing 'swift package show-dependencies' in", basePath);
2887
+ }
2888
+ const result = spawnSync(
2889
+ SWIFT_CMD,
2890
+ ["package", "show-dependencies", "--format", "json"],
2891
+ {
2892
+ cwd: basePath,
2893
+ encoding: "utf-8",
2894
+ timeout: TIMEOUT_MS
2895
+ }
2896
+ );
2897
+ if (result.status === 0 && result.stdout) {
2898
+ completedPath.push(basePath);
2899
+ treeData = Buffer.from(result.stdout).toString();
2900
+ const retData = utils.parseSwiftJsonTree(treeData, f);
2901
+ if (retData.pkgList && retData.pkgList.length) {
2902
+ parentComponent = retData.pkgList.splice(0, 1)[0];
2903
+ parentComponent.type = "application";
2904
+ pkgList = pkgList.concat(retData.pkgList);
2905
+ }
2906
+ if (retData.dependenciesList) {
2907
+ dependencies = mergeDependencies(
2908
+ dependencies,
2909
+ retData.dependenciesList
2910
+ );
2911
+ }
2912
+ } else {
2913
+ if (DEBUG_MODE) {
2914
+ console.log(
2915
+ "Please install swift from https://www.swift.org/download/ or use the cdxgen container image"
2916
+ );
2917
+ }
2918
+ console.error(result.stderr);
2919
+ options.failOnError && process.exit(1);
2920
+ }
2921
+ }
2922
+ }
2923
+ return buildBomNSData(options, pkgList, "swift", {
2924
+ src: path,
2925
+ filename: swiftFiles.join(", "),
2926
+ parentComponent,
2927
+ dependencies
2928
+ });
2929
+ };
2930
+
2808
2931
  /**
2809
2932
  * Function to create bom string for docker compose
2810
2933
  *
@@ -4041,6 +4164,19 @@ const createXBom = async (path, options) => {
4041
4164
  if (cbFiles.length) {
4042
4165
  return await createCloudBuildBom(path, options);
4043
4166
  }
4167
+
4168
+ // Swift
4169
+ const swiftFiles = utils.getAllFiles(
4170
+ path,
4171
+ (options.multiProject ? "**/" : "") + "Package*.swift"
4172
+ );
4173
+ const pkgResolvedFiles = utils.getAllFiles(
4174
+ path,
4175
+ (options.multiProject ? "**/" : "") + "Package.resolved"
4176
+ );
4177
+ if (swiftFiles.length || pkgResolvedFiles.length) {
4178
+ return await createSwiftBom(path, options);
4179
+ }
4044
4180
  };
4045
4181
 
4046
4182
  /**
@@ -4287,6 +4423,9 @@ const createBom = async (path, options) => {
4287
4423
  case "cloudbuild":
4288
4424
  options.multiProject = true;
4289
4425
  return await createCloudBuildBom(path, options);
4426
+ case "swift":
4427
+ options.multiProject = true;
4428
+ return await createSwiftBom(path, options);
4290
4429
  default:
4291
4430
  // In recurse mode return multi-language Bom
4292
4431
  // https://github.com/cyclonedx/cdxgen/issues/95
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "8.1.9",
3
+ "version": "8.2.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>",
package/server.js CHANGED
@@ -115,7 +115,7 @@ const start = async (options) => {
115
115
  }
116
116
  }
117
117
  res.end("\n");
118
- if (cleanup && srcDir && srcDir.startsWith(os.tmpdir())) {
118
+ if (cleanup && srcDir && srcDir.startsWith(os.tmpdir()) && fs.rmSync) {
119
119
  console.log(`Cleaning up ${srcDir}`);
120
120
  fs.rmSync(srcDir, { recursive: true, force: true });
121
121
  }
package/utils.js CHANGED
@@ -377,7 +377,7 @@ const parsePkgLock = async (pkgLockFile) => {
377
377
  type: "application"
378
378
  };
379
379
  }
380
- if (rootPkg) {
380
+ if (rootPkg && rootPkg.name) {
381
381
  const purl = new PackageURL(
382
382
  "application",
383
383
  "",
@@ -1018,7 +1018,7 @@ exports.parsePom = parsePom;
1018
1018
  */
1019
1019
  const parseMavenTree = function (rawOutput) {
1020
1020
  if (!rawOutput) {
1021
- return [];
1021
+ return {};
1022
1022
  }
1023
1023
  const deps = [];
1024
1024
  const dependenciesList = [];
@@ -1128,7 +1128,7 @@ const parseGradleDep = function (rawOutput) {
1128
1128
  level_trees[last_purl] = [];
1129
1129
  let stack = [last_purl];
1130
1130
  const depRegex =
1131
- /^.*?--- +(?<group>[^\s:]+):(?<name>[^\s:]+)(?::(?:{strictly )?(?<versionspecified>[^\s:}]+))?(?:})?(?: +-> +(?<versionoverride>[^\s:]+))?/gm;
1131
+ /^.*?--- +(?<group>[^\s:]+):(?<name>[^\s:]+)(?::(?:{strictly [[]?)?(?<versionspecified>[^,\s:}]+))?(?:})?(?:[^->]* +-> +(?<versionoverride>[^\s:]+))?/gm;
1132
1132
  while ((match = depRegex.exec(rawOutput))) {
1133
1133
  const [line, group, name, versionspecified, versionoverride] = match;
1134
1134
  const version = versionoverride || versionspecified;
@@ -3773,6 +3773,207 @@ const convertOSQueryResults = function (queryCategory, queryObj, results) {
3773
3773
  };
3774
3774
  exports.convertOSQueryResults = convertOSQueryResults;
3775
3775
 
3776
+ const _swiftDepPkgList = (pkgList, dependenciesList, depKeys, jsonData) => {
3777
+ if (jsonData && jsonData.dependencies) {
3778
+ for (let adep of jsonData.dependencies) {
3779
+ const urlOrPath = adep.url || adep.path;
3780
+ const apkg = {
3781
+ group: adep.identity || "",
3782
+ name: adep.name,
3783
+ version: adep.version
3784
+ };
3785
+ const purl = new PackageURL(
3786
+ "swift",
3787
+ apkg.group,
3788
+ apkg.name,
3789
+ apkg.version,
3790
+ null,
3791
+ null
3792
+ );
3793
+ const purlString = decodeURIComponent(purl.toString());
3794
+ if (urlOrPath) {
3795
+ if (urlOrPath.startsWith("http")) {
3796
+ apkg.repository = { url: urlOrPath };
3797
+ if (apkg.path) {
3798
+ apkg.properties = [
3799
+ {
3800
+ name: "SrcPath",
3801
+ value: apkg.path
3802
+ }
3803
+ ];
3804
+ }
3805
+ } else {
3806
+ apkg.properties = [
3807
+ {
3808
+ name: "SrcPath",
3809
+ value: urlOrPath
3810
+ }
3811
+ ];
3812
+ }
3813
+ }
3814
+ pkgList.push(apkg);
3815
+ // Handle the immediate dependencies before recursing
3816
+ if (adep.dependencies && adep.dependencies.length) {
3817
+ const deplist = [];
3818
+ for (let cdep of adep.dependencies) {
3819
+ const deppurl = new PackageURL(
3820
+ "swift",
3821
+ cdep.identity || "",
3822
+ cdep.name,
3823
+ cdep.version,
3824
+ null,
3825
+ null
3826
+ );
3827
+ const deppurlString = decodeURIComponent(deppurl.toString());
3828
+ deplist.push(deppurlString);
3829
+ }
3830
+ if (!depKeys[purlString]) {
3831
+ dependenciesList.push({
3832
+ ref: purlString,
3833
+ dependsOn: deplist
3834
+ });
3835
+ depKeys[purlString] = true;
3836
+ }
3837
+ if (adep.dependencies && adep.dependencies.length) {
3838
+ _swiftDepPkgList(pkgList, dependenciesList, depKeys, adep);
3839
+ }
3840
+ } else {
3841
+ if (!depKeys[purlString]) {
3842
+ dependenciesList.push({
3843
+ ref: purlString,
3844
+ dependsOn: []
3845
+ });
3846
+ depKeys[purlString] = true;
3847
+ }
3848
+ }
3849
+ }
3850
+ }
3851
+ return { pkgList, dependenciesList };
3852
+ };
3853
+
3854
+ /**
3855
+ * Parse swift dependency tree output
3856
+ * @param {string} rawOutput Swift dependencies json output
3857
+ * @param {string} pkgFile Package.swift file
3858
+ */
3859
+ const parseSwiftJsonTree = (rawOutput, pkgFile) => {
3860
+ if (!rawOutput) {
3861
+ return {};
3862
+ }
3863
+ const pkgList = [];
3864
+ const dependenciesList = [];
3865
+ let depKeys = {};
3866
+ let rootPkg = {};
3867
+ let jsonData = {};
3868
+ try {
3869
+ jsonData = JSON.parse(rawOutput);
3870
+ if (jsonData && jsonData.name) {
3871
+ rootPkg = {
3872
+ group: jsonData.identity || "",
3873
+ name: jsonData.name,
3874
+ version: jsonData.version
3875
+ };
3876
+ const urlOrPath = jsonData.url || jsonData.path;
3877
+ if (urlOrPath) {
3878
+ if (urlOrPath.startsWith("http")) {
3879
+ rootPkg.repository = { url: urlOrPath };
3880
+ } else {
3881
+ rootPkg.properties = [
3882
+ {
3883
+ name: "SrcPath",
3884
+ value: urlOrPath
3885
+ },
3886
+ {
3887
+ name: "SrcFile",
3888
+ value: pkgFile
3889
+ }
3890
+ ];
3891
+ }
3892
+ }
3893
+ const purl = new PackageURL(
3894
+ "application",
3895
+ rootPkg.group,
3896
+ rootPkg.name,
3897
+ rootPkg.version,
3898
+ null,
3899
+ null
3900
+ );
3901
+ const purlString = decodeURIComponent(purl.toString());
3902
+ rootPkg["bom-ref"] = purlString;
3903
+ pkgList.push(rootPkg);
3904
+ const deplist = [];
3905
+ for (const rd of jsonData.dependencies) {
3906
+ const deppurl = new PackageURL(
3907
+ "swift",
3908
+ rd.identity || "",
3909
+ rd.name,
3910
+ rd.version,
3911
+ null,
3912
+ null
3913
+ );
3914
+ const deppurlString = decodeURIComponent(deppurl.toString());
3915
+ deplist.push(deppurlString);
3916
+ }
3917
+ dependenciesList.push({
3918
+ ref: purlString,
3919
+ dependsOn: deplist
3920
+ });
3921
+ _swiftDepPkgList(pkgList, dependenciesList, depKeys, jsonData);
3922
+ }
3923
+ } catch (e) {
3924
+ if (DEBUG_MODE) {
3925
+ console.log(e);
3926
+ }
3927
+ return {};
3928
+ }
3929
+ return {
3930
+ pkgList,
3931
+ dependenciesList
3932
+ };
3933
+ };
3934
+ exports.parseSwiftJsonTree = parseSwiftJsonTree;
3935
+
3936
+ /**
3937
+ * Parse swift package resolved file
3938
+ * @param {string} resolvedFile Package.resolved file
3939
+ */
3940
+ const parseSwiftResolved = (resolvedFile) => {
3941
+ const pkgList = [];
3942
+ if (fs.existsSync(resolvedFile)) {
3943
+ try {
3944
+ const pkgData = JSON.parse(fs.readFileSync(resolvedFile, "utf8"));
3945
+ let resolvedList = [];
3946
+ if (pkgData.pins) {
3947
+ resolvedList = pkgData.pins;
3948
+ } else if (pkgData.object && pkgData.object.pins) {
3949
+ resolvedList = pkgData.object.pins;
3950
+ }
3951
+ for (const adep of resolvedList) {
3952
+ const apkg = {
3953
+ name: adep.package || adep.identity,
3954
+ group: "",
3955
+ version: adep.state.version || adep.state.revision,
3956
+ properties: [
3957
+ {
3958
+ name: "SrcFile",
3959
+ value: resolvedFile
3960
+ }
3961
+ ]
3962
+ };
3963
+ const repLocation = adep.location || adep.repositoryURL;
3964
+ if (repLocation) {
3965
+ apkg.repository = { url: repLocation };
3966
+ }
3967
+ pkgList.push(apkg);
3968
+ }
3969
+ } catch (err) {
3970
+ // continue regardless of error
3971
+ }
3972
+ }
3973
+ return pkgList;
3974
+ };
3975
+ exports.parseSwiftResolved = parseSwiftResolved;
3976
+
3776
3977
  /**
3777
3978
  * Collect maven dependencies
3778
3979
  *
@@ -4007,6 +4208,7 @@ const extractJarArchive = function (jarFile, tempDir) {
4007
4208
  jarMetadata["Extension-Name"] ||
4008
4209
  jarMetadata["Implementation-Vendor-Id"] ||
4009
4210
  jarMetadata["Bundle-SymbolicName"] ||
4211
+ jarMetadata["Bundle-Vendor"] ||
4010
4212
  jarMetadata["Automatic-Module-Name"];
4011
4213
  let name = "";
4012
4214
  if (
@@ -4073,8 +4275,8 @@ const extractJarArchive = function (jarFile, tempDir) {
4073
4275
  }
4074
4276
  if (name && version) {
4075
4277
  pkgList.push({
4076
- group: group === "." ? "" : group || "",
4077
- name: name || "",
4278
+ group: group === "." ? "" : encodeURIComponent(group) || "",
4279
+ name: name ? encodeURIComponent(name) : "",
4078
4280
  version,
4079
4281
  properties: [
4080
4282
  {