@cyclonedx/cdxgen 8.1.8 → 8.2.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/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 |
@@ -262,6 +263,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
262
263
  | FETCH_LICENSE | Set this variable to fetch license information from the registry. npm and golang only |
263
264
  | USE_GOSUM | Set to true to generate BOMs for golang projects using go.sum as the dependency source of truth, instead of go.mod |
264
265
  | CDXGEN_TIMEOUT_MS | Default timeout for known execution involving maven, gradle or sbt |
266
+ | CDXGEN_SERVER_TIMEOUT_MS | Default timeout in server mode |
265
267
  | BAZEL_TARGET | Bazel target to build. Default :all (Eg: //java-maven) |
266
268
  | CLJ_CMD | Set to override the clojure cli command |
267
269
  | LEIN_CMD | Set to override the leiningen command |
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) {
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) {
@@ -1008,13 +1036,13 @@ const createJavaBom = async (path, options) => {
1008
1036
  "Resolve the above maven error. This could be due to the following:\n"
1009
1037
  );
1010
1038
  console.log(
1011
- "1. Java version requirement - Scan or the CI build agent could be using an incompatible version"
1039
+ "1. Java version requirement: cdxgen container image bundles Java 17 with gradle 8 which might be incompatible."
1012
1040
  );
1013
1041
  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"
1042
+ "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
1043
  );
1016
1044
  console.log(
1017
- "3. Check if all required environment variables including any maven profile arguments are passed correctly to this tool"
1045
+ "3. Check if all required environment variables including any maven profile arguments are passed correctly to this tool."
1018
1046
  );
1019
1047
  // Do not fall back to methods that can produce incomplete results when failOnError is set
1020
1048
  options.failOnError && process.exit(1);
@@ -1125,7 +1153,7 @@ const createJavaBom = async (path, options) => {
1125
1153
  console.error(result.stdout, result.stderr);
1126
1154
  }
1127
1155
  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."
1156
+ "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
1157
  );
1130
1158
  options.failOnError && process.exit(1);
1131
1159
  }
@@ -1251,7 +1279,7 @@ const createJavaBom = async (path, options) => {
1251
1279
  }
1252
1280
  if (DEBUG_MODE || !result.stderr || options.failOnError) {
1253
1281
  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."
1282
+ "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
1283
  );
1256
1284
  console.log(
1257
1285
  "2. When using tools such as sdkman, the init script must be invoked to set the PATH variables correctly."
@@ -2805,6 +2833,91 @@ const createHelmBom = async (path, options) => {
2805
2833
  return {};
2806
2834
  };
2807
2835
 
2836
+ /**
2837
+ * Function to create bom string for swift projects
2838
+ *
2839
+ * @param path to the project
2840
+ * @param options Parse options from the cli
2841
+ */
2842
+ const createSwiftBom = async (path, options) => {
2843
+ const swiftFiles = utils.getAllFiles(
2844
+ path,
2845
+ (options.multiProject ? "**/" : "") + "Package*.swift"
2846
+ );
2847
+ const pkgResolvedFiles = utils.getAllFiles(
2848
+ path,
2849
+ (options.multiProject ? "**/" : "") + "Package.resolved"
2850
+ );
2851
+ let pkgList = [];
2852
+ let dependencies = [];
2853
+ let parentComponent = {};
2854
+ let completedPath = [];
2855
+ if (pkgResolvedFiles.length) {
2856
+ for (let f of pkgResolvedFiles) {
2857
+ if (!parentComponent || !Object.keys(parentComponent).length) {
2858
+ parentComponent = createDefaultParentComponent(f);
2859
+ }
2860
+ if (DEBUG_MODE) {
2861
+ console.log("Parsing", f);
2862
+ }
2863
+ const dlist = utils.parseSwiftResolved(f);
2864
+ if (dlist && dlist.length) {
2865
+ pkgList = pkgList.concat(dlist);
2866
+ }
2867
+ }
2868
+ } else if (swiftFiles.length) {
2869
+ for (let f of swiftFiles) {
2870
+ const basePath = pathLib.dirname(f);
2871
+ if (completedPath.includes(basePath)) {
2872
+ continue;
2873
+ }
2874
+ let treeData = undefined;
2875
+ if (DEBUG_MODE) {
2876
+ console.log("Executing 'swift package show-dependencies' in", basePath);
2877
+ }
2878
+ const result = spawnSync(
2879
+ SWIFT_CMD,
2880
+ ["package", "show-dependencies", "--format", "json"],
2881
+ {
2882
+ cwd: basePath,
2883
+ encoding: "utf-8",
2884
+ timeout: TIMEOUT_MS
2885
+ }
2886
+ );
2887
+ if (result.status === 0 && result.stdout) {
2888
+ completedPath.push(basePath);
2889
+ treeData = Buffer.from(result.stdout).toString();
2890
+ const retData = utils.parseSwiftJsonTree(treeData, f);
2891
+ if (retData.pkgList && retData.pkgList.length) {
2892
+ parentComponent = retData.pkgList.splice(0, 1)[0];
2893
+ parentComponent.type = "application";
2894
+ pkgList = pkgList.concat(retData.pkgList);
2895
+ }
2896
+ if (retData.dependenciesList) {
2897
+ dependencies = mergeDependencies(
2898
+ dependencies,
2899
+ retData.dependenciesList
2900
+ );
2901
+ }
2902
+ } else {
2903
+ if (DEBUG_MODE) {
2904
+ console.log(
2905
+ "Please install swift from https://www.swift.org/download/ or use the cdxgen container image"
2906
+ );
2907
+ }
2908
+ console.error(result.stderr);
2909
+ options.failOnError && process.exit(1);
2910
+ }
2911
+ }
2912
+ }
2913
+ return buildBomNSData(options, pkgList, "swift", {
2914
+ src: path,
2915
+ filename: swiftFiles.join(", "),
2916
+ parentComponent,
2917
+ dependencies
2918
+ });
2919
+ };
2920
+
2808
2921
  /**
2809
2922
  * Function to create bom string for docker compose
2810
2923
  *
@@ -4041,6 +4154,19 @@ const createXBom = async (path, options) => {
4041
4154
  if (cbFiles.length) {
4042
4155
  return await createCloudBuildBom(path, options);
4043
4156
  }
4157
+
4158
+ // Swift
4159
+ const swiftFiles = utils.getAllFiles(
4160
+ path,
4161
+ (options.multiProject ? "**/" : "") + "Package*.swift"
4162
+ );
4163
+ const pkgResolvedFiles = utils.getAllFiles(
4164
+ path,
4165
+ (options.multiProject ? "**/" : "") + "Package.resolved"
4166
+ );
4167
+ if (swiftFiles.length || pkgResolvedFiles.length) {
4168
+ return await createSwiftBom(path, options);
4169
+ }
4044
4170
  };
4045
4171
 
4046
4172
  /**
@@ -4167,6 +4293,10 @@ const createBom = async (path, options) => {
4167
4293
  case "kotlin":
4168
4294
  case "scala":
4169
4295
  case "jvm":
4296
+ case "gradle":
4297
+ case "mvn":
4298
+ case "maven":
4299
+ case "sbt":
4170
4300
  return await createJavaBom(path, options);
4171
4301
  case "jar":
4172
4302
  options.multiProject = true;
@@ -4192,6 +4322,7 @@ const createBom = async (path, options) => {
4192
4322
  case "javascript":
4193
4323
  case "typescript":
4194
4324
  case "ts":
4325
+ case "tsx":
4195
4326
  return await createNodejsBom(path, options);
4196
4327
  case "python":
4197
4328
  case "py":
@@ -4282,6 +4413,9 @@ const createBom = async (path, options) => {
4282
4413
  case "cloudbuild":
4283
4414
  options.multiProject = true;
4284
4415
  return await createCloudBuildBom(path, options);
4416
+ case "swift":
4417
+ options.multiProject = true;
4418
+ return await createSwiftBom(path, options);
4285
4419
  default:
4286
4420
  // In recurse mode return multi-language Bom
4287
4421
  // 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.8",
3
+ "version": "8.2.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>",
package/server.js CHANGED
@@ -9,6 +9,10 @@ const path = require("path");
9
9
  const bom = require("./index.js");
10
10
  const compression = require("compression");
11
11
 
12
+ // Timeout milliseconds. Default 10 mins
13
+ const TIMEOUT_MS =
14
+ parseInt(process.env.CDXGEN_SERVER_TIMEOUT_MS) || 10 * 60 * 1000;
15
+
12
16
  const app = connect();
13
17
 
14
18
  app.use(
@@ -68,9 +72,19 @@ const parseQueryString = (q, body, options = {}) => {
68
72
  return options;
69
73
  };
70
74
 
75
+ const configureServer = (cdxgenServer) => {
76
+ cdxgenServer.headersTimeout = TIMEOUT_MS;
77
+ cdxgenServer.requestTimeout = TIMEOUT_MS;
78
+ cdxgenServer.timeout = 0;
79
+ cdxgenServer.keepAliveTimeout = 0;
80
+ };
81
+
71
82
  const start = async (options) => {
72
83
  console.log("Listening on", options.serverHost, options.serverPort);
73
- http.createServer(app).listen(options.serverPort, options.serverHost);
84
+ const cdxgenServer = http
85
+ .createServer(app)
86
+ .listen(options.serverPort, options.serverHost);
87
+ configureServer(cdxgenServer);
74
88
  app.use("/sbom", async function (req, res) {
75
89
  const q = url.parse(req.url, true).query;
76
90
  let cleanup = false;
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
  *