@cyclonedx/cdxgen 9.9.3 → 9.9.4

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
@@ -49,6 +49,8 @@ Most SBOM tools are like barcode scanners. They can scan a few package manifest
49
49
  | Gradle Cache | $HOME/caches/modules-2/files-2.1/\*\*/\*.jar | N/A | |
50
50
  | Helm Index | $HOME/.cache/helm/repository/\*\*/\*.yaml | N/A | |
51
51
  | Docker compose | docker-compose\*.yml. Images would also be scanned. | N/A | |
52
+ | Dockerfile | `*Dockerfile*` Images would also be scanned. | N/A | |
53
+ | Containerfile | `*Containerfile*`. Images would also be scanned. | N/A | |
52
54
  | Google CloudBuild configuration | cloudbuild.yaml | N/A | |
53
55
  | OpenAPI | openapi\*.json, openapi\*.yaml | N/A | |
54
56
 
@@ -173,9 +175,9 @@ Options:
173
175
  es. [boolean] [default: false]
174
176
  --spec-version CycloneDX Specification version to use. Defaults
175
177
  to 1.5 [default: 1.5]
176
- --filter Filter components containining this word in purl.
178
+ --filter Filter components containing this word in purl.
177
179
  Multiple values allowed. [array]
178
- --only Include components only containining this word in
180
+ --only Include components only containing this word in
179
181
  purl. Useful to generate BOM with first party co
180
182
  mponents alone. Multiple values allowed. [array]
181
183
  --author The person(s) who created the BOM. Set this value
package/bin/cdxgen.js CHANGED
@@ -193,11 +193,11 @@ const args = yargs(hideBin(process.argv))
193
193
  })
194
194
  .option("filter", {
195
195
  description:
196
- "Filter components containining this word in purl. Multiple values allowed."
196
+ "Filter components containing this word in purl. Multiple values allowed."
197
197
  })
198
198
  .option("only", {
199
199
  description:
200
- "Include components only containining this word in purl. Useful to generate BOM with first party components alone. Multiple values allowed."
200
+ "Include components only containing this word in purl. Useful to generate BOM with first party components alone. Multiple values allowed."
201
201
  })
202
202
  .option("author", {
203
203
  description:
@@ -520,7 +520,6 @@ const checkPermissions = (filePath) => {
520
520
  if (bomNSData.nsMapping && Object.keys(bomNSData.nsMapping).length) {
521
521
  const nsFile = jsonFile + ".map";
522
522
  fs.writeFileSync(nsFile, JSON.stringify(bomNSData.nsMapping));
523
- console.log("Namespace mapping file written to", nsFile);
524
523
  }
525
524
  }
526
525
  } else if (!options.print) {
package/docker.js CHANGED
@@ -126,8 +126,8 @@ const getDefaultOptions = () => {
126
126
  opts.prefixUrl = isWin
127
127
  ? WIN_LOCAL_TLS
128
128
  : isDockerRootless
129
- ? `http://unix:${homedir()}/.docker/run/docker.sock:`
130
- : "http://unix:/var/run/docker.sock:";
129
+ ? `http://unix:${homedir()}/.docker/run/docker.sock:`
130
+ : "http://unix:/var/run/docker.sock:";
131
131
  }
132
132
  }
133
133
  } else {
package/evinser.js CHANGED
@@ -498,14 +498,16 @@ export const parseSliceUsages = async (
498
498
  !isFilterableType(language, userDefinedTypesMap, atype[1])
499
499
  ) {
500
500
  if (!atype[1].includes("(") && !atype[1].includes(".py")) {
501
- typesToLookup.add(atype[1]);
501
+ typesToLookup.add(simplifyType(atype[1]));
502
502
  // Javascript calls can be resolved to a precise line number only from the call nodes
503
503
  if (
504
504
  ["javascript", "js", "ts", "typescript"].includes(language) &&
505
505
  ausageLine
506
506
  ) {
507
507
  if (atype[1].includes(":")) {
508
- typesToLookup.add(atype[1].split("::")[0].replace(/:/g, "/"));
508
+ typesToLookup.add(
509
+ simplifyType(atype[1].split("::")[0].replace(/:/g, "/"))
510
+ );
509
511
  }
510
512
  addToOverrides(lKeyOverrides, atype[1], fileName, ausageLine);
511
513
  }
@@ -532,7 +534,7 @@ export const parseSliceUsages = async (
532
534
  !acall?.resolvedMethod.includes("(") &&
533
535
  !acall?.resolvedMethod.includes(".py")
534
536
  ) {
535
- typesToLookup.add(acall?.resolvedMethod);
537
+ typesToLookup.add(simplifyType(acall?.resolvedMethod));
536
538
  // Javascript calls can be resolved to a precise line number only from the call nodes
537
539
  if (acall.lineNumber) {
538
540
  addToOverrides(
@@ -560,10 +562,12 @@ export const parseSliceUsages = async (
560
562
  for (const aparamType of acall?.paramTypes || []) {
561
563
  if (!isFilterableType(language, userDefinedTypesMap, aparamType)) {
562
564
  if (!aparamType.includes("(") && !aparamType.includes(".py")) {
563
- typesToLookup.add(aparamType);
565
+ typesToLookup.add(simplifyType(aparamType));
564
566
  if (acall.lineNumber) {
565
567
  if (aparamType.includes(":")) {
566
- typesToLookup.add(aparamType.split("::")[0].replace(/:/g, "/"));
568
+ typesToLookup.add(
569
+ simplifyType(aparamType.split("::")[0].replace(/:/g, "/"))
570
+ );
567
571
  }
568
572
  addToOverrides(
569
573
  lKeyOverrides,
@@ -609,7 +613,7 @@ export const parseSliceUsages = async (
609
613
  } else {
610
614
  // Check the namespaces db
611
615
  let nsHits = typePurlsCache[atype];
612
- if (["java", "jar"].includes(language)) {
616
+ if (!nsHits && ["java", "jar"].includes(language)) {
613
617
  nsHits = await dbObjMap.Namespaces.findAll({
614
618
  attributes: ["purl"],
615
619
  where: {
@@ -629,6 +633,9 @@ export const parseSliceUsages = async (
629
633
  }
630
634
  }
631
635
  typePurlsCache[atype] = nsHits;
636
+ } else {
637
+ // Avoid persistent lookups
638
+ typePurlsCache[atype] = [];
632
639
  }
633
640
  }
634
641
  }
@@ -834,7 +841,7 @@ export const extractEndpoints = (language, code) => {
834
841
  case "jar":
835
842
  if (
836
843
  code.startsWith("@") &&
837
- code.includes("Mapping") &&
844
+ (code.includes("Mapping") || code.includes("Path")) &&
838
845
  code.includes("(")
839
846
  ) {
840
847
  const matches = code.match(/['"](.*?)['"]/gi) || [];
@@ -1190,6 +1197,16 @@ export const framePicker = (dfFrames) => {
1190
1197
  return aframe;
1191
1198
  };
1192
1199
 
1200
+ /**
1201
+ * Method to simplify types. For example, arrays ending with [] could be simplified.
1202
+ *
1203
+ * @param {string} typeFullName Full name of the type to simplify
1204
+ * @returns Simplified type string
1205
+ */
1206
+ export const simplifyType = (typeFullName) => {
1207
+ return typeFullName.replace("[]", "");
1208
+ };
1209
+
1193
1210
  export const getClassTypeFromSignature = (language, typeFullName) => {
1194
1211
  if (["java", "jar"].includes(language) && typeFullName.includes(":")) {
1195
1212
  typeFullName = typeFullName.split(":")[0];
@@ -1225,7 +1242,7 @@ export const getClassTypeFromSignature = (language, typeFullName) => {
1225
1242
  if (typeFullName.includes("$")) {
1226
1243
  typeFullName = typeFullName.split("$")[0];
1227
1244
  }
1228
- return typeFullName;
1245
+ return simplifyType(typeFullName);
1229
1246
  };
1230
1247
 
1231
1248
  const addToOverrides = (lKeyOverrides, atype, fileName, ausageLineNumber) => {
package/index.js CHANGED
@@ -104,7 +104,8 @@ import {
104
104
  TIMEOUT_MS,
105
105
  MAX_BUFFER,
106
106
  getNugetMetadata,
107
- frameworksList
107
+ frameworksList,
108
+ parseContainerFile
108
109
  } from "./utils.js";
109
110
  import { spawnSync } from "node:child_process";
110
111
  import { fileURLToPath } from "node:url";
@@ -3713,6 +3714,16 @@ export const createContainerSpecLikeBom = async (path, options) => {
3713
3714
  (options.multiProject ? "**/" : "") + "*.yml",
3714
3715
  options
3715
3716
  );
3717
+ const dfFiles = getAllFiles(
3718
+ path,
3719
+ (options.multiProject ? "**/" : "") + "*Dockerfile*",
3720
+ options
3721
+ );
3722
+ const cfFiles = getAllFiles(
3723
+ path,
3724
+ (options.multiProject ? "**/" : "") + "*Containerfile*",
3725
+ options
3726
+ );
3716
3727
  const yamlFiles = getAllFiles(
3717
3728
  path,
3718
3729
  (options.multiProject ? "**/" : "") + "*.yaml",
@@ -3736,14 +3747,22 @@ export const createContainerSpecLikeBom = async (path, options) => {
3736
3747
  }
3737
3748
  // Privado.ai json files
3738
3749
  const privadoFiles = getAllFiles(path, ".privado/" + "*.json", options);
3739
- // parse yaml manifest files
3740
- if (dcFiles.length) {
3741
- for (const f of dcFiles) {
3750
+ // Parse yaml manifest files, dockerfiles or containerfiles
3751
+ if (dcFiles.length || dfFiles.length || cfFiles.length) {
3752
+ for (const f of [...dcFiles, ...dfFiles, ...cfFiles]) {
3742
3753
  if (DEBUG_MODE) {
3743
3754
  console.log(`Parsing ${f}`);
3744
3755
  }
3745
- const dcData = readFileSync(f, { encoding: "utf-8" });
3746
- const imglist = parseContainerSpecData(dcData);
3756
+
3757
+ const dData = readFileSync(f, { encoding: "utf-8" });
3758
+ let imglist = [];
3759
+ // parse yaml manifest files
3760
+ if (f.endsWith(".yml") || f.endsWith(".yaml")) {
3761
+ imglist = parseContainerSpecData(dData);
3762
+ } else {
3763
+ imglist = parseContainerFile(dData);
3764
+ }
3765
+
3747
3766
  if (imglist && imglist.length) {
3748
3767
  if (DEBUG_MODE) {
3749
3768
  console.log("Images identified in", f, "are", imglist);
@@ -5186,12 +5205,22 @@ export const createXBom = async (path, options) => {
5186
5205
  return createHelmBom(path, options);
5187
5206
  }
5188
5207
 
5189
- // Docker compose, kubernetes and skaffold
5208
+ // Docker compose, dockerfile, containerfile, kubernetes and skaffold
5190
5209
  const dcFiles = getAllFiles(
5191
5210
  path,
5192
5211
  (options.multiProject ? "**/" : "") + "docker-compose*.yml",
5193
5212
  options
5194
5213
  );
5214
+ const dfFiles = getAllFiles(
5215
+ path,
5216
+ (options.multiProject ? "**/" : "") + "*Dockerfile*",
5217
+ options
5218
+ );
5219
+ const cfFiles = getAllFiles(
5220
+ path,
5221
+ (options.multiProject ? "**/" : "") + "*Containerfile*",
5222
+ options
5223
+ );
5195
5224
  const skFiles = getAllFiles(
5196
5225
  path,
5197
5226
  (options.multiProject ? "**/" : "") + "skaffold.yaml",
@@ -5202,7 +5231,13 @@ export const createXBom = async (path, options) => {
5202
5231
  (options.multiProject ? "**/" : "") + "deployment.yaml",
5203
5232
  options
5204
5233
  );
5205
- if (dcFiles.length || skFiles.length || deplFiles.length) {
5234
+ if (
5235
+ dcFiles.length ||
5236
+ dfFiles.length ||
5237
+ cfFiles.length ||
5238
+ skFiles.length ||
5239
+ deplFiles.length
5240
+ ) {
5206
5241
  return await createContainerSpecLikeBom(path, options);
5207
5242
  }
5208
5243
 
@@ -5468,7 +5503,9 @@ export const createBom = async (path, options) => {
5468
5503
  options
5469
5504
  );
5470
5505
  case "universal":
5506
+ case "containerfile":
5471
5507
  case "docker-compose":
5508
+ case "dockerfile":
5472
5509
  case "swarm":
5473
5510
  case "tekton":
5474
5511
  case "kustomize":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.9.3",
3
+ "version": "9.9.4",
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>",
@@ -55,8 +55,8 @@
55
55
  "url": "https://github.com/cyclonedx/cdxgen/issues"
56
56
  },
57
57
  "dependencies": {
58
- "@babel/parser": "^7.23.0",
59
- "@babel/traverse": "^7.23.2",
58
+ "@babel/parser": "^7.23.3",
59
+ "@babel/traverse": "^7.23.3",
60
60
  "@npmcli/arborist": "7.2.0",
61
61
  "ajv": "^8.12.0",
62
62
  "ajv-formats": "^2.1.1",
@@ -83,7 +83,7 @@
83
83
  "yargs": "^17.7.2"
84
84
  },
85
85
  "optionalDependencies": {
86
- "@appthreat/atom": "1.5.6",
86
+ "@appthreat/atom": "1.6.3",
87
87
  "@cyclonedx/cdxgen-plugins-bin": "^1.4.0",
88
88
  "@cyclonedx/cdxgen-plugins-bin-arm64": "^1.4.0",
89
89
  "@cyclonedx/cdxgen-plugins-bin-ppc64": "^1.4.0",
@@ -91,7 +91,7 @@
91
91
  "compression": "^1.7.4",
92
92
  "connect": "^3.7.0",
93
93
  "jsonata": "^2.0.3",
94
- "sequelize": "^6.33.0",
94
+ "sequelize": "^6.35.0",
95
95
  "sqlite3": "^5.1.6"
96
96
  },
97
97
  "files": [
@@ -102,10 +102,10 @@
102
102
  "devDependencies": {
103
103
  "caxa": "^3.0.1",
104
104
  "docsify-cli": "^4.4.4",
105
- "eslint": "^8.52.0",
105
+ "eslint": "^8.53.0",
106
106
  "eslint-config-prettier": "^9.0.0",
107
107
  "eslint-plugin-prettier": "^5.0.1",
108
108
  "jest": "^29.7.0",
109
- "prettier": "3.0.3"
109
+ "prettier": "3.1.0"
110
110
  }
111
111
  }
package/utils.js CHANGED
@@ -4388,6 +4388,43 @@ export const recurseImageNameLookup = (keyValueObj, pkgList, imgList) => {
4388
4388
  return imgList;
4389
4389
  };
4390
4390
 
4391
+ export const parseContainerFile = function (fileContents) {
4392
+ const imgList = [];
4393
+
4394
+ let buildStageNames = [];
4395
+ for (const line of fileContents.split("\n")) {
4396
+ if (line.trim().startsWith("#")) {
4397
+ continue; // skip commented out lines
4398
+ }
4399
+
4400
+ if (line.includes("FROM")) {
4401
+ const fromStatement = line.split("FROM")[1].split("AS");
4402
+
4403
+ const imageStatement = fromStatement[0].trim();
4404
+ const buildStageName = fromStatement[1]?.trim();
4405
+
4406
+ if (buildStageNames.includes(imageStatement)) {
4407
+ if (DEBUG_MODE) {
4408
+ console.log(
4409
+ `Skipping image ${imageStatement} which uses previously seen build stage name.`
4410
+ );
4411
+ }
4412
+ continue;
4413
+ }
4414
+
4415
+ imgList.push({
4416
+ image: imageStatement
4417
+ });
4418
+
4419
+ if (buildStageName) {
4420
+ buildStageNames.push(buildStageName);
4421
+ }
4422
+ }
4423
+ }
4424
+
4425
+ return imgList;
4426
+ };
4427
+
4391
4428
  export const parseContainerSpecData = function (dcData) {
4392
4429
  const pkgList = [];
4393
4430
  const imgList = [];
@@ -7690,7 +7727,7 @@ export const parseCmakeLikeFile = (cmakeListFile, pkgType, options = {}) => {
7690
7727
  .split(")")[0]
7691
7728
  .split(",")
7692
7729
  .filter((v) => v.length > 1);
7693
- const parentName = tmpB[0];
7730
+ const parentName = tmpB[0].replace(":", "");
7694
7731
  let parentVersion = undefined;
7695
7732
  // In case of meson.build we can find the version number after the word version
7696
7733
  // thanks to our replaces and splits
package/utils.test.js CHANGED
@@ -73,7 +73,8 @@ import {
73
73
  parsePyProjectToml,
74
74
  parseSbtTree,
75
75
  parseCmakeDotFile,
76
- parseCmakeLikeFile
76
+ parseCmakeLikeFile,
77
+ parseContainerFile
77
78
  } from "./utils.js";
78
79
  import { readFileSync } from "node:fs";
79
80
  import { parse } from "ssri";
@@ -2727,6 +2728,31 @@ test("parse container spec like files", async () => {
2727
2728
  });
2728
2729
  });
2729
2730
 
2731
+ test("parse containerfiles / dockerfiles", async () => {
2732
+ let dep_list = parseContainerFile(
2733
+ readFileSync("./test/data/Dockerfile", { encoding: "utf-8" })
2734
+ );
2735
+ expect(dep_list.length).toEqual(5);
2736
+ expect(dep_list[0]).toEqual({
2737
+ image: "hello-world"
2738
+ });
2739
+ expect(dep_list[0]).toEqual({
2740
+ image: "hello-world"
2741
+ });
2742
+ expect(dep_list[1]).toEqual({
2743
+ image: "hello-world"
2744
+ });
2745
+ expect(dep_list[2]).toEqual({
2746
+ image: "hello-world:latest"
2747
+ });
2748
+ expect(dep_list[3]).toEqual({
2749
+ image: "hello-world@sha256:1234567890abcdef"
2750
+ });
2751
+ expect(dep_list[4]).toEqual({
2752
+ image: "hello-world:latest@sha256:1234567890abcdef"
2753
+ });
2754
+ });
2755
+
2730
2756
  test("parse cloudbuild data", async () => {
2731
2757
  expect(parseCloudBuildData(null)).toEqual([]);
2732
2758
  const dep_list = parseCloudBuildData(