@cyclonedx/cdxgen 8.1.5 → 8.1.7

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
@@ -52,7 +52,7 @@ NOTE:
52
52
  Footnotes:
53
53
 
54
54
  - [1] - For multi-module application, the BoM file could include components that may not be included in the packaged war or ear file.
55
- - [2] - Use pip freeze to improve the accuracy for requirements.txt based parsing. `python -m pip freeze > requirements.txt`
55
+ - [2] - Pip freeze is automatically performed to improve precision.
56
56
  - [3] - Perform dotnet or nuget restore to generate project.assets.json. Without this file cdxgen would not include indirect dependencies.
57
57
  - [4] - See section on plugins
58
58
  - [5] - Powered by osquery. See section on plugins
@@ -86,36 +86,38 @@ docker run --rm -v /tmp:/tmp -v $(pwd):/app:rw -t ghcr.io/cyclonedx/cdxgen -r /a
86
86
  ```text
87
87
  $ cdxgen -h
88
88
  Options:
89
- -o, --output Output file for bom.xml or bom.json. Default console
90
- -t, --type Project type
91
- -r, --recurse Recurse mode suitable for mono-repos [boolean]
92
- -p, --print Print the SBoM as a table [boolean]
93
- -c, --resolve-class Resolve class names for packages. jars only for now.
94
- [boolean]
95
- --deep Perform deep searches for components. Useful while
96
- scanning live OS and oci images. [boolean]
97
- --server-url Dependency track url. Eg:
98
- https://deptrack.cyclonedx.io
99
- --api-key Dependency track api key
100
- --project-group Dependency track project group
101
- --project-name Dependency track project name. Default use
102
- the directory name
103
- --project-version Dependency track project version
104
- --project-id Dependency track project id. Either
105
- provide the id or the project name and version together
106
- --required-only Include only the packages with required scope on the
107
- SBoM. [boolean]
108
- --fail-on-error Fail if any dependency extractor fails. [boolean]
109
- --no-babel Do not use babel to perform usage analysis for
110
- JavaScript/TypeScript projects. [boolean]
89
+ -o, --output Output file for bom.xml or bom.json. Default
90
+ bom.json
91
+ -t, --type Project type
92
+ -r, --recurse Recurse mode suitable for mono-repos [boolean]
93
+ -p, --print Print the SBoM as a table. Defaults to true if
94
+ output file is not specified with -o [boolean]
95
+ -c, --resolve-class Resolve class names for packages. jars only for
96
+ now. [boolean]
97
+ --deep Perform deep searches for components. Useful
98
+ while scanning live OS and oci images. [boolean]
99
+ --server-url Dependency track url. Eg:
100
+ https://deptrack.cyclonedx.io
101
+ --api-key Dependency track api key
102
+ --project-group Dependency track project group
103
+ --project-name Dependency track project name. Default use the
104
+ directory name
105
+ --project-version Dependency track project version [default: ""]
106
+ --project-id Dependency track project id. Either provide the
107
+ id or the project name and version together
108
+ --required-only Include only the packages with required scope on
109
+ the SBoM. [boolean]
110
+ --fail-on-error Fail if any dependency extractor fails. [boolean]
111
+ --no-babel Do not use babel to perform usage analysis for
112
+ JavaScript/TypeScript projects. [boolean]
111
113
  --generate-key-and-sign Generate an RSA public/private key pair and then
112
114
  sign the generated SBoM using JSON Web
113
115
  Signatures. [boolean]
114
116
  --server Run cdxgen as a server [boolean]
115
117
  --server-host Listen address [default: "127.0.0.1"]
116
118
  --server-port Listen port [default: "9090"]
117
- --version Show version number [boolean]
118
- -h Show help [boolean]
119
+ --version Show version number [boolean]
120
+ -h Show help [boolean]
119
121
  ```
120
122
 
121
123
  ## Example
package/analyzer.js CHANGED
@@ -13,10 +13,19 @@ const IGNORE_DIRS = [
13
13
  "e2e",
14
14
  "examples",
15
15
  "cypress",
16
- "site-packages"
16
+ "site-packages",
17
+ "typings",
18
+ "api_docs",
19
+ "dev_docs",
20
+ "types",
21
+ "mock",
22
+ "mocks"
17
23
  ];
18
24
 
19
- const IGNORE_FILE_PATTERN = new RegExp("(conf|test|spec|mock)\\.(js|ts)$", "i");
25
+ const IGNORE_FILE_PATTERN = new RegExp(
26
+ "(conf|test|spec|mock|\\.d)\\.(js|ts|tsx)$",
27
+ "i"
28
+ );
20
29
 
21
30
  const getAllFiles = (dir, extn, files, result, regex) => {
22
31
  files = files || fs.readdirSync(dir);
package/bin/cdxgen CHANGED
@@ -10,7 +10,7 @@ const bomServer = require("../server.js");
10
10
  const args = require("yargs")
11
11
  .option("output", {
12
12
  alias: "o",
13
- description: "Output file for bom.xml or bom.json. Default console"
13
+ description: "Output file for bom.xml or bom.json. Default bom.json"
14
14
  })
15
15
  .option("type", {
16
16
  alias: "t",
@@ -24,7 +24,8 @@ const args = require("yargs")
24
24
  .option("print", {
25
25
  alias: "p",
26
26
  type: "boolean",
27
- description: "Print the SBoM as a table"
27
+ description:
28
+ "Print the SBoM as a table. Defaults to true if output file is not specified with -o"
28
29
  })
29
30
  .option("resolve-class", {
30
31
  alias: "c",
@@ -154,7 +155,10 @@ let options = {
154
155
  return await bomServer.start(options);
155
156
  }
156
157
  const bomNSData = (await bom.createBom(filePath, options)) || {};
157
-
158
+ if (!args.output) {
159
+ args.output = "bom.json";
160
+ args.print = true;
161
+ }
158
162
  if (
159
163
  args.output &&
160
164
  (typeof args.output === "string" || args.output instanceof String)
@@ -286,8 +290,9 @@ let options = {
286
290
  }
287
291
 
288
292
  // Automatically submit the bom data
289
- if (args.serverUrl && args.apiKey) {
293
+ if (args.serverUrl && args.serverUrl != true && args.apiKey) {
290
294
  try {
295
+ // TODO: Need to use json format for v9
291
296
  const dbody = await bom.submitBom(args, bomNSData.bomXml);
292
297
  console.log("Response from server", dbody);
293
298
  } catch (err) {
package/index.js CHANGED
@@ -39,6 +39,11 @@ if (process.env.LEIN_CMD) {
39
39
  LEIN_CMD = process.env.LEIN_CMD;
40
40
  }
41
41
 
42
+ let PIP_CMD = "pip";
43
+ if (process.env.PIP_CMD) {
44
+ PIP_CMD = process.env.PIP_CMD;
45
+ }
46
+
42
47
  // Construct sbt cache directory
43
48
  let SBT_CACHE_DIR =
44
49
  process.env.SBT_CACHE_DIR || pathLib.join(os.homedir(), ".ivy2", "cache");
@@ -1887,7 +1892,7 @@ const createPythonBom = async (path, options) => {
1887
1892
  );
1888
1893
  const reqFiles = utils.getAllFiles(
1889
1894
  path,
1890
- (options.multiProject ? "**/" : "") + "requirements.txt"
1895
+ (options.multiProject ? "**/" : "") + "*requirements.txt"
1891
1896
  );
1892
1897
  const reqDirFiles = utils.getAllFiles(
1893
1898
  path,
@@ -1977,7 +1982,28 @@ const createPythonBom = async (path, options) => {
1977
1982
  metadataFilename = "requirements.txt";
1978
1983
  if (reqFiles && reqFiles.length) {
1979
1984
  for (let f of reqFiles) {
1980
- const reqData = fs.readFileSync(f, { encoding: "utf-8" });
1985
+ const basePath = pathLib.dirname(f);
1986
+ let reqData = undefined;
1987
+ // Attempt to pip freeze to improve precision
1988
+ if (options.installDeps) {
1989
+ const result = spawnSync(PIP_CMD, ["freeze", "-r", f, "-l"], {
1990
+ cwd: basePath,
1991
+ encoding: "utf-8",
1992
+ timeout: TIMEOUT_MS
1993
+ });
1994
+ if (result.status === 0 && result.stdout) {
1995
+ reqData = Buffer.from(result.stdout).toString();
1996
+ }
1997
+ }
1998
+ // Fallback to parsing requirements file
1999
+ if (!reqData) {
2000
+ if (DEBUG_MODE) {
2001
+ console.log(
2002
+ `Falling back to manually parsing ${f}. The result would be incomplete!`
2003
+ );
2004
+ }
2005
+ reqData = fs.readFileSync(f, { encoding: "utf-8" });
2006
+ }
1981
2007
  const dlist = await utils.parseReqFile(reqData);
1982
2008
  if (dlist && dlist.length) {
1983
2009
  pkgList = pkgList.concat(dlist);
@@ -3817,7 +3843,7 @@ const createXBom = async (path, options) => {
3817
3843
  const poetryMode = fs.existsSync(pathLib.join(path, "poetry.lock"));
3818
3844
  const reqFiles = utils.getAllFiles(
3819
3845
  path,
3820
- (options.multiProject ? "**/" : "") + "requirements.txt"
3846
+ (options.multiProject ? "**/" : "") + "*requirements.txt"
3821
3847
  );
3822
3848
  const reqDirFiles = utils.getAllFiles(
3823
3849
  path,
@@ -4280,15 +4306,25 @@ exports.submitBom = async (args, bomContents) => {
4280
4306
  if (encodedBomContents.startsWith("77u/")) {
4281
4307
  encodedBomContents = encodedBomContents.substring(4);
4282
4308
  }
4309
+ let projectVersion = args.projectVersion || "master";
4310
+ if (projectVersion == true) {
4311
+ projectVersion = "master";
4312
+ }
4283
4313
  const bomPayload = {
4284
4314
  project: args.projectId,
4285
4315
  projectName: args.projectName,
4286
- projectVersion: args.projectVersion,
4316
+ projectVersion: projectVersion,
4287
4317
  autoCreate: "true",
4288
4318
  bom: encodedBomContents
4289
4319
  };
4290
4320
  if (DEBUG_MODE) {
4291
- console.log("Submitting BOM to", serverUrl);
4321
+ console.log(
4322
+ "Submitting BOM to",
4323
+ serverUrl,
4324
+ "params",
4325
+ args.projectName,
4326
+ projectVersion
4327
+ );
4292
4328
  }
4293
4329
  return await got(serverUrl, {
4294
4330
  method: "PUT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "8.1.5",
3
+ "version": "8.1.7",
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/utils.js CHANGED
@@ -1756,7 +1756,13 @@ exports.parsePoetrylockData = parsePoetrylockData;
1756
1756
  const parseReqFile = async function (reqData) {
1757
1757
  const pkgList = [];
1758
1758
  let fetchIndirectDeps = false;
1759
+ let compScope = undefined;
1759
1760
  reqData.split("\n").forEach((l) => {
1761
+ if (l.includes("# Basic requirements")) {
1762
+ compScope = "required";
1763
+ } else if (l.includes("added by pip freeze")) {
1764
+ compScope = undefined;
1765
+ }
1760
1766
  if (!l.startsWith("#")) {
1761
1767
  if (l.indexOf("=") > -1) {
1762
1768
  let tmpA = l.split(/(==|<=|~=|>=)/);
@@ -1773,7 +1779,8 @@ const parseReqFile = async function (reqData) {
1773
1779
  if (!tmpA[0].includes("=") && !tmpA[0].trim().includes(" ")) {
1774
1780
  pkgList.push({
1775
1781
  name: tmpA[0].trim(),
1776
- version: versionStr
1782
+ version: versionStr,
1783
+ scope: compScope
1777
1784
  });
1778
1785
  }
1779
1786
  } else if (/[>|[|@]/.test(l)) {
@@ -1784,7 +1791,8 @@ const parseReqFile = async function (reqData) {
1784
1791
  if (!tmpA[0].trim().includes(" ")) {
1785
1792
  pkgList.push({
1786
1793
  name: tmpA[0].trim(),
1787
- version: null
1794
+ version: null,
1795
+ scope: compScope
1788
1796
  });
1789
1797
  }
1790
1798
  } else if (l) {
@@ -1795,7 +1803,8 @@ const parseReqFile = async function (reqData) {
1795
1803
  if (!l.includes(" ")) {
1796
1804
  pkgList.push({
1797
1805
  name: l,
1798
- version: null
1806
+ version: null,
1807
+ scope: compScope
1799
1808
  });
1800
1809
  }
1801
1810
  }
package/utils.test.js CHANGED
@@ -1348,7 +1348,7 @@ test("parseGemspecData", async () => {
1348
1348
  });
1349
1349
  });
1350
1350
 
1351
- test("parse requirements.txt with comments", async () => {
1351
+ test("parse requirements.txt", async () => {
1352
1352
  jest.setTimeout(120000);
1353
1353
  let deps = await utils.parseReqFile(
1354
1354
  fs.readFileSync(
@@ -1357,6 +1357,15 @@ test("parse requirements.txt with comments", async () => {
1357
1357
  )
1358
1358
  );
1359
1359
  expect(deps.length).toEqual(31);
1360
+ deps = await utils.parseReqFile(
1361
+ fs.readFileSync("./test/data/requirements.freeze.txt", (encoding = "utf-8"))
1362
+ );
1363
+ expect(deps.length).toEqual(113);
1364
+ expect(deps[0]).toEqual({
1365
+ name: "elasticsearch",
1366
+ version: "8.6.2",
1367
+ scope: "required"
1368
+ });
1360
1369
  });
1361
1370
 
1362
1371
  test("parse poetry.lock", async () => {