@cyclonedx/cdxgen 9.9.4 → 9.9.6

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/docker.test.js CHANGED
@@ -24,35 +24,65 @@ test("parseImageName tests", () => {
24
24
  repo: "debian",
25
25
  tag: "",
26
26
  digest: "",
27
- platform: ""
27
+ platform: "",
28
+ group: "",
29
+ name: "debian"
28
30
  });
29
31
  expect(parseImageName("debian:latest")).toEqual({
30
32
  registry: "",
31
33
  repo: "debian",
32
34
  tag: "latest",
33
35
  digest: "",
34
- platform: ""
36
+ platform: "",
37
+ group: "",
38
+ name: "debian"
39
+ });
40
+ expect(parseImageName("library/debian:latest")).toEqual({
41
+ registry: "",
42
+ repo: "library/debian",
43
+ tag: "latest",
44
+ digest: "",
45
+ platform: "",
46
+ group: "library",
47
+ name: "debian"
35
48
  });
36
49
  expect(parseImageName("shiftleft/scan:v1.15.6")).toEqual({
37
50
  registry: "",
38
51
  repo: "shiftleft/scan",
39
52
  tag: "v1.15.6",
40
53
  digest: "",
41
- platform: ""
54
+ platform: "",
55
+ group: "shiftleft",
56
+ name: "scan"
42
57
  });
43
58
  expect(parseImageName("localhost:5000/shiftleft/scan:v1.15.6")).toEqual({
44
59
  registry: "localhost:5000",
45
60
  repo: "shiftleft/scan",
46
61
  tag: "v1.15.6",
47
62
  digest: "",
48
- platform: ""
63
+ platform: "",
64
+ group: "shiftleft",
65
+ name: "scan"
49
66
  });
50
67
  expect(parseImageName("localhost:5000/shiftleft/scan")).toEqual({
51
68
  registry: "localhost:5000",
52
69
  repo: "shiftleft/scan",
53
70
  tag: "",
54
71
  digest: "",
55
- platform: ""
72
+ platform: "",
73
+ group: "shiftleft",
74
+ name: "scan"
75
+ });
76
+ expect(
77
+ parseImageName("foocorp.jfrog.io/docker/library/eclipse-temurin:latest")
78
+ ).toEqual({
79
+ registry: "foocorp.jfrog.io",
80
+ repo: "docker/library/eclipse-temurin",
81
+ tag: "latest",
82
+ digest: "",
83
+ platform: "",
84
+ group: "docker/library",
85
+ name: "eclipse-temurin"
56
86
  });
57
87
  expect(
58
88
  parseImageName(
@@ -63,7 +93,9 @@ test("parseImageName tests", () => {
63
93
  repo: "shiftleft/scan-java",
64
94
  tag: "",
65
95
  digest: "5d008306a7c5d09ba0161a3408fa3839dc2c9dd991ffb68adecc1040399fe9e1",
66
- platform: ""
96
+ platform: "",
97
+ group: "shiftleft",
98
+ name: "scan-java"
67
99
  });
68
100
  }, 120000);
69
101
 
package/evinser.js CHANGED
@@ -322,6 +322,29 @@ export const analyzeProject = async (dbObjMap, options) => {
322
322
  // Load any existing purl-location information from the sbom.
323
323
  // For eg: cdxgen populates this information for javascript projects
324
324
  let { purlLocationMap, purlImportsMap } = initFromSbom(components);
325
+ // Do reachables first so that usages slicing can reuse the atom file
326
+ if (options.withReachables) {
327
+ if (
328
+ options.reachablesSlicesFile &&
329
+ fs.existsSync(options.reachablesSlicesFile)
330
+ ) {
331
+ reachablesSlicesFile = options.reachablesSlicesFile;
332
+ reachablesSlice = JSON.parse(
333
+ fs.readFileSync(options.reachablesSlicesFile, "utf-8")
334
+ );
335
+ } else {
336
+ retMap = createSlice(language, dirPath, "reachables", options);
337
+ if (retMap && retMap.slicesFile && fs.existsSync(retMap.slicesFile)) {
338
+ reachablesSlicesFile = retMap.slicesFile;
339
+ reachablesSlice = JSON.parse(
340
+ fs.readFileSync(retMap.slicesFile, "utf-8")
341
+ );
342
+ }
343
+ }
344
+ }
345
+ if (reachablesSlice && Object.keys(reachablesSlice).length) {
346
+ dataFlowFrames = await collectReachableFrames(language, reachablesSlice);
347
+ }
325
348
  // Reuse existing usages slices
326
349
  if (options.usagesSlicesFile && fs.existsSync(options.usagesSlicesFile)) {
327
350
  usageSlice = JSON.parse(fs.readFileSync(options.usagesSlicesFile, "utf-8"));
@@ -374,28 +397,6 @@ export const analyzeProject = async (dbObjMap, options) => {
374
397
  purlImportsMap
375
398
  );
376
399
  }
377
- if (options.withReachables) {
378
- if (
379
- options.reachablesSlicesFile &&
380
- fs.existsSync(options.reachablesSlicesFile)
381
- ) {
382
- reachablesSlicesFile = options.reachablesSlicesFile;
383
- reachablesSlice = JSON.parse(
384
- fs.readFileSync(options.reachablesSlicesFile, "utf-8")
385
- );
386
- } else {
387
- retMap = createSlice(language, dirPath, "reachables", options);
388
- if (retMap && retMap.slicesFile && fs.existsSync(retMap.slicesFile)) {
389
- reachablesSlicesFile = retMap.slicesFile;
390
- reachablesSlice = JSON.parse(
391
- fs.readFileSync(retMap.slicesFile, "utf-8")
392
- );
393
- }
394
- }
395
- }
396
- if (reachablesSlice && Object.keys(reachablesSlice).length) {
397
- dataFlowFrames = await collectReachableFrames(language, reachablesSlice);
398
- }
399
400
  return {
400
401
  atomFile: retMap.atomFile,
401
402
  usagesSlicesFile,
@@ -776,15 +777,19 @@ export const detectServicesFromUDT = (
776
777
  servicesMap
777
778
  ) => {
778
779
  if (
779
- ["python", "py"].includes(language) &&
780
+ ["python", "py", "c", "cpp", "c++"].includes(language) &&
780
781
  userDefinedTypes &&
781
782
  userDefinedTypes.length
782
783
  ) {
783
784
  for (const audt of userDefinedTypes) {
784
785
  if (
785
- audt.name.includes("route") ||
786
- audt.name.includes("path") ||
787
- audt.name.includes("url")
786
+ audt.name.toLowerCase().includes("route") ||
787
+ audt.name.toLowerCase().includes("path") ||
788
+ audt.name.toLowerCase().includes("url") ||
789
+ audt.name.toLowerCase().includes("registerhandler") ||
790
+ audt.name.toLowerCase().includes("endpoint") ||
791
+ audt.name.toLowerCase().includes("api") ||
792
+ audt.name.toLowerCase().includes("add_method")
788
793
  ) {
789
794
  const fields = audt.fields || [];
790
795
  if (
@@ -875,14 +880,11 @@ export const extractEndpoints = (language, code) => {
875
880
  );
876
881
  }
877
882
  break;
878
- case "py":
879
- case "python":
883
+ default:
880
884
  endpoints = (code.match(/['"](.*?)['"]/gi) || [])
881
885
  .map((v) => v.replace(/["']/g, "").replace("\n", ""))
882
886
  .filter((v) => v.length > 2);
883
887
  break;
884
- default:
885
- break;
886
888
  }
887
889
  return endpoints;
888
890
  };
@@ -910,6 +912,7 @@ export const createEvinseFile = (sliceArtefacts, options) => {
910
912
  const components = bomJson.components || [];
911
913
  let occEvidencePresent = false;
912
914
  let csEvidencePresent = false;
915
+ let servicesPresent = false;
913
916
  for (const comp of components) {
914
917
  if (!comp.purl) {
915
918
  continue;
@@ -957,6 +960,7 @@ export const createEvinseFile = (sliceArtefacts, options) => {
957
960
  }
958
961
  // Add to existing services
959
962
  bomJson.services = (bomJson.services || []).concat(services);
963
+ servicesPresent = true;
960
964
  }
961
965
  if (options.annotate) {
962
966
  if (!bomJson.annotations) {
@@ -993,7 +997,7 @@ export const createEvinseFile = (sliceArtefacts, options) => {
993
997
  bomJson.metadata.timestamp = new Date().toISOString();
994
998
  delete bomJson.signature;
995
999
  fs.writeFileSync(evinseOutFile, JSON.stringify(bomJson, null, 2));
996
- if (occEvidencePresent || csEvidencePresent) {
1000
+ if (occEvidencePresent || csEvidencePresent || servicesPresent) {
997
1001
  console.log(evinseOutFile, "created successfully.");
998
1002
  } else {
999
1003
  console.log(
package/index.js CHANGED
@@ -105,7 +105,8 @@ import {
105
105
  MAX_BUFFER,
106
106
  getNugetMetadata,
107
107
  frameworksList,
108
- parseContainerFile
108
+ parseContainerFile,
109
+ parseBitbucketPipelinesFile
109
110
  } from "./utils.js";
110
111
  import { spawnSync } from "node:child_process";
111
112
  import { fileURLToPath } from "node:url";
@@ -1860,7 +1861,7 @@ export const createNodejsBom = async (path, options) => {
1860
1861
  const parentSubComponents = [];
1861
1862
  let ppurl = "";
1862
1863
  // Docker mode requires special handling
1863
- if (["docker", "oci", "os"].includes(options.projectType)) {
1864
+ if (["docker", "oci", "container", "os"].includes(options.projectType)) {
1864
1865
  const pkgJsonFiles = getAllFiles(path, "**/package.json", options);
1865
1866
  // Are there any package.json files in the container?
1866
1867
  if (pkgJsonFiles.length) {
@@ -1880,7 +1881,7 @@ export const createNodejsBom = async (path, options) => {
1880
1881
  }
1881
1882
  let allImports = {};
1882
1883
  if (
1883
- !["docker", "oci", "os"].includes(options.projectType) &&
1884
+ !["docker", "oci", "container", "os"].includes(options.projectType) &&
1884
1885
  !options.noBabel
1885
1886
  ) {
1886
1887
  if (DEBUG_MODE) {
@@ -2753,7 +2754,7 @@ export const createGoBom = async (path, options) => {
2753
2754
  if (gomodFiles.length) {
2754
2755
  let shouldManuallyParse = false;
2755
2756
  // Use the go list -deps and go mod why commands to generate a good quality BOM for non-docker invocations
2756
- if (!["docker", "oci", "os"].includes(options.projectType)) {
2757
+ if (!["docker", "oci", "container", "os"].includes(options.projectType)) {
2757
2758
  for (const f of gomodFiles) {
2758
2759
  const basePath = dirname(f);
2759
2760
  // Ignore vendor packages
@@ -2865,7 +2866,7 @@ export const createGoBom = async (path, options) => {
2865
2866
  }
2866
2867
  }
2867
2868
  // Parse the gomod files manually. The resultant BOM would be incomplete
2868
- if (!["docker", "oci", "os"].includes(options.projectType)) {
2869
+ if (!["docker", "oci", "container", "os"].includes(options.projectType)) {
2869
2870
  console.log(
2870
2871
  "Manually parsing go.mod files. The resultant BOM would be incomplete."
2871
2872
  );
@@ -3148,13 +3149,25 @@ export const createCppBom = (path, options) => {
3148
3149
  retMap.parentComponent.type = "library";
3149
3150
  pkgList.push(retMap.parentComponent);
3150
3151
  }
3152
+ // Retain the dependency tree from cmake
3153
+ if (retMap.dependenciesList) {
3154
+ if (dependencies.length) {
3155
+ dependencies = mergeDependencies(
3156
+ dependencies,
3157
+ retMap.dependenciesList,
3158
+ parentComponent
3159
+ );
3160
+ } else {
3161
+ dependencies = retMap.dependenciesList;
3162
+ }
3163
+ }
3151
3164
  }
3152
3165
  }
3153
3166
  // The need for java >= 17 with atom is causing confusions since there could be C projects
3154
3167
  // inside of other project types. So we currently limit this analyis only when -t argument
3155
3168
  // is used.
3156
3169
  if (
3157
- !["docker", "oci", "os"].includes(options.projectType) &&
3170
+ !["docker", "oci", "container", "os"].includes(options.projectType) &&
3158
3171
  (!options.createMultiXBom || options.deep)
3159
3172
  ) {
3160
3173
  let osPkgsList = [];
@@ -3719,6 +3732,11 @@ export const createContainerSpecLikeBom = async (path, options) => {
3719
3732
  (options.multiProject ? "**/" : "") + "*Dockerfile*",
3720
3733
  options
3721
3734
  );
3735
+ const bbPipelineFiles = getAllFiles(
3736
+ path,
3737
+ (options.multiProject ? "**/" : "") + "bitbucket-pipelines.yml",
3738
+ options
3739
+ );
3722
3740
  const cfFiles = getAllFiles(
3723
3741
  path,
3724
3742
  (options.multiProject ? "**/" : "") + "*Containerfile*",
@@ -3747,27 +3765,35 @@ export const createContainerSpecLikeBom = async (path, options) => {
3747
3765
  }
3748
3766
  // Privado.ai json files
3749
3767
  const privadoFiles = getAllFiles(path, ".privado/" + "*.json", options);
3750
- // Parse yaml manifest files, dockerfiles or containerfiles
3751
- if (dcFiles.length || dfFiles.length || cfFiles.length) {
3752
- for (const f of [...dcFiles, ...dfFiles, ...cfFiles]) {
3768
+
3769
+ // Parse yaml manifest files, dockerfiles, containerfiles or bitbucket pipeline files
3770
+ if (
3771
+ dcFiles.length ||
3772
+ dfFiles.length ||
3773
+ cfFiles.length ||
3774
+ bbPipelineFiles.length
3775
+ ) {
3776
+ for (const f of [...dcFiles, ...dfFiles, ...cfFiles, ...bbPipelineFiles]) {
3753
3777
  if (DEBUG_MODE) {
3754
3778
  console.log(`Parsing ${f}`);
3755
3779
  }
3756
3780
 
3757
3781
  const dData = readFileSync(f, { encoding: "utf-8" });
3758
- let imglist = [];
3782
+ let imgList = [];
3759
3783
  // parse yaml manifest files
3760
- if (f.endsWith(".yml") || f.endsWith(".yaml")) {
3761
- imglist = parseContainerSpecData(dData);
3784
+ if (f.endsWith("bitbucket-pipelines.yml")) {
3785
+ imgList = parseBitbucketPipelinesFile(dData);
3786
+ } else if (f.endsWith(".yml") || f.endsWith(".yaml")) {
3787
+ imgList = parseContainerSpecData(dData);
3762
3788
  } else {
3763
- imglist = parseContainerFile(dData);
3789
+ imgList = parseContainerFile(dData);
3764
3790
  }
3765
3791
 
3766
- if (imglist && imglist.length) {
3792
+ if (imgList && imgList.length) {
3767
3793
  if (DEBUG_MODE) {
3768
- console.log("Images identified in", f, "are", imglist);
3794
+ console.log("Images identified in", f, "are", imgList);
3769
3795
  }
3770
- for (const img of imglist) {
3796
+ for (const img of imgList) {
3771
3797
  const commonProperties = [
3772
3798
  {
3773
3799
  name: "SrcFile",
@@ -3832,20 +3858,26 @@ export const createContainerSpecLikeBom = async (path, options) => {
3832
3858
  console.log(`Parsing image ${img.image}`);
3833
3859
  }
3834
3860
  const imageObj = parseImageName(img.image);
3861
+
3835
3862
  const pkg = {
3836
- name: imageObj.repo,
3863
+ name: imageObj.name,
3864
+ group: imageObj.group,
3837
3865
  version:
3838
3866
  imageObj.tag ||
3839
3867
  (imageObj.digest ? "sha256:" + imageObj.digest : "latest"),
3840
3868
  qualifiers: {},
3841
- properties: commonProperties
3869
+ properties: commonProperties,
3870
+ type: "container"
3842
3871
  };
3843
3872
  if (imageObj.registry) {
3844
- pkg["qualifiers"]["repository_url"] = imageObj.registry;
3873
+ pkg["qualifiers"]["repository_url"] = img.image;
3845
3874
  }
3846
3875
  if (imageObj.platform) {
3847
3876
  pkg["qualifiers"]["platform"] = imageObj.platform;
3848
3877
  }
3878
+ if (imageObj.tag) {
3879
+ pkg["qualifiers"]["tag"] = imageObj.tag;
3880
+ }
3849
3881
  // Create an entry for the oci image
3850
3882
  const imageBomData = buildBomNSData(options, [pkg], "oci", {
3851
3883
  src: img.image,
@@ -5296,6 +5328,7 @@ export const createBom = async (path, options) => {
5296
5328
  projectType === "docker" ||
5297
5329
  projectType === "podman" ||
5298
5330
  projectType === "oci" ||
5331
+ projectType === "container" ||
5299
5332
  path.startsWith("docker.io") ||
5300
5333
  path.startsWith("quay.io") ||
5301
5334
  path.startsWith("ghcr.io") ||
@@ -5544,19 +5577,36 @@ export async function submitBom(args, bomContents) {
5544
5577
  if (encodedBomContents.startsWith("77u/")) {
5545
5578
  encodedBomContents = encodedBomContents.substring(4);
5546
5579
  }
5547
- let projectVersion = args.projectVersion || "master";
5548
- if (projectVersion == true) {
5549
- projectVersion = "master";
5550
- }
5551
5580
  const bomPayload = {
5552
- project: args.projectId,
5553
- projectName: args.projectName,
5554
- projectVersion: projectVersion,
5555
5581
  autoCreate: "true",
5556
5582
  bom: encodedBomContents
5557
5583
  };
5558
- if (typeof args.parentProjectId !== "undefined") {
5559
- bomPayload.parentUUID = args.parentProjectId;
5584
+ let projectVersion = args.projectVersion || "master";
5585
+ if (
5586
+ typeof args.projectId !== "undefined" ||
5587
+ (typeof args.projectName !== "undefined" &&
5588
+ typeof projectVersion !== "undefined")
5589
+ ) {
5590
+ if (typeof args.projectId !== "undefined") {
5591
+ bomPayload.project = args.projectId;
5592
+ }
5593
+ if (typeof args.projectName !== "undefined") {
5594
+ bomPayload.projectName = args.projectName;
5595
+ }
5596
+ if (typeof projectVersion !== "undefined") {
5597
+ bomPayload.projectVersion = projectVersion;
5598
+ }
5599
+ } else {
5600
+ console.log(
5601
+ "projectId, projectName and projectVersion, or all three must be provided."
5602
+ );
5603
+ return;
5604
+ }
5605
+ if (
5606
+ typeof args.parentProjectId !== "undefined" ||
5607
+ typeof args.parentUUID !== "undefined"
5608
+ ) {
5609
+ bomPayload.parentUUID = args.parentProjectId || args.parentUUID;
5560
5610
  }
5561
5611
  if (DEBUG_MODE) {
5562
5612
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.9.4",
3
+ "version": "9.9.6",
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,13 +55,13 @@
55
55
  "url": "https://github.com/cyclonedx/cdxgen/issues"
56
56
  },
57
57
  "dependencies": {
58
- "@babel/parser": "^7.23.3",
59
- "@babel/traverse": "^7.23.3",
58
+ "@babel/parser": "^7.23.5",
59
+ "@babel/traverse": "^7.23.5",
60
60
  "@npmcli/arborist": "7.2.0",
61
61
  "ajv": "^8.12.0",
62
62
  "ajv-formats": "^2.1.1",
63
63
  "cheerio": "^1.0.0-rc.12",
64
- "edn-data": "^1.0.0",
64
+ "edn-data": "1.1.1",
65
65
  "find-up": "^6.3.0",
66
66
  "glob": "^10.3.10",
67
67
  "global-agent": "^3.0.0",
@@ -83,7 +83,7 @@
83
83
  "yargs": "^17.7.2"
84
84
  },
85
85
  "optionalDependencies": {
86
- "@appthreat/atom": "1.6.3",
86
+ "@appthreat/atom": "1.7.2",
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.35.0",
94
+ "sequelize": "^6.35.1",
95
95
  "sqlite3": "^5.1.6"
96
96
  },
97
97
  "files": [
@@ -102,7 +102,7 @@
102
102
  "devDependencies": {
103
103
  "caxa": "^3.0.1",
104
104
  "docsify-cli": "^4.4.4",
105
- "eslint": "^8.53.0",
105
+ "eslint": "^8.54.0",
106
106
  "eslint-config-prettier": "^9.0.0",
107
107
  "eslint-plugin-prettier": "^5.0.1",
108
108
  "jest": "^29.7.0",
package/server.js CHANGED
@@ -72,7 +72,7 @@ const parseQueryString = (q, body, options = {}) => {
72
72
  "requiredOnly",
73
73
  "noBabel",
74
74
  "installDeps",
75
- "project",
75
+ "projectId",
76
76
  "projectName",
77
77
  "projectGroup",
78
78
  "projectVersion",
@@ -83,7 +83,8 @@ const parseQueryString = (q, body, options = {}) => {
83
83
  "filter",
84
84
  "only",
85
85
  "autoCompositions",
86
- "gitBranch"
86
+ "gitBranch",
87
+ "active"
87
88
  ];
88
89
 
89
90
  for (const param of queryParams) {