@cyclonedx/cdxgen 11.2.0 → 11.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/lib/cli/index.js CHANGED
@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
3
3
  import {
4
4
  constants,
5
5
  accessSync,
6
+ existsSync,
6
7
  lstatSync,
7
8
  mkdtempSync,
8
9
  readFileSync,
@@ -16,6 +17,7 @@ import { basename, dirname, join, relative, resolve, sep } from "node:path";
16
17
  import process from "node:process";
17
18
  import { URL } from "node:url";
18
19
  import got from "got";
20
+ import { load as loadYaml } from "js-yaml";
19
21
  import { PackageURL } from "packageurl-js";
20
22
  import { gte, lte } from "semver";
21
23
  import { parse } from "ssri";
@@ -47,6 +49,7 @@ import {
47
49
  addEvidenceForImports,
48
50
  addPlugin,
49
51
  buildGradleCommandArguments,
52
+ buildObjectForCocoaPod,
50
53
  buildObjectForGradleModule,
51
54
  checksumFile,
52
55
  cleanupPlugin,
@@ -61,6 +64,7 @@ import {
61
64
  dirNameStr,
62
65
  encodeForPurl,
63
66
  executeParallelGradleProperties,
67
+ executePodCommand,
64
68
  extractJarArchive,
65
69
  frameworksList,
66
70
  generatePixiLockFile,
@@ -98,6 +102,7 @@ import {
98
102
  parseCljDep,
99
103
  parseCloudBuildData,
100
104
  parseCmakeLikeFile,
105
+ parseCocoaDependency,
101
106
  parseComposerJson,
102
107
  parseComposerLock,
103
108
  parseConanData,
@@ -139,6 +144,8 @@ import {
139
144
  parsePkgLock,
140
145
  parsePnpmLock,
141
146
  parsePnpmWorkspace,
147
+ parsePodfileLock,
148
+ parsePodfileTargets,
142
149
  parsePom,
143
150
  parsePrivadoFile,
144
151
  parsePubLockData,
@@ -5012,6 +5019,158 @@ export async function createSwiftBom(path, options) {
5012
5019
  });
5013
5020
  }
5014
5021
 
5022
+ /**
5023
+ * Function to create bom string for cocoa projects
5024
+ *
5025
+ * @param {string} path to the project
5026
+ * @param {Object} options Parse options from the cli
5027
+ */
5028
+ export async function createCocoaBom(path, options) {
5029
+ const cocoaFiles = getAllFiles(
5030
+ path,
5031
+ `${options.multiProject ? "**/" : ""}Podfile`,
5032
+ options,
5033
+ );
5034
+ if (cocoaFiles.length > 1) {
5035
+ thoughtLog(
5036
+ `There are ${cocoaFiles.length} pod files. I will carefully process each one.`,
5037
+ );
5038
+ }
5039
+ let excludeMessageShown = false;
5040
+ for (const podFile of cocoaFiles) {
5041
+ const projectPath = dirname(podFile);
5042
+ const lockFile = `${podFile}.lock`;
5043
+ if (!existsSync(lockFile) || options.deep) {
5044
+ if (options.installDeps) {
5045
+ executePodCommand(["install"], projectPath, options);
5046
+ } else {
5047
+ console.log(
5048
+ "No 'Podfile.lock' found and '--no-install-deps' is set -- A Podfile.lock is needed to parse dependencies!",
5049
+ );
5050
+ options.failOnError && process.exit(1);
5051
+ }
5052
+ }
5053
+ const parentComponent = await buildObjectForCocoaPod(
5054
+ {
5055
+ name: basename(projectPath),
5056
+ version: "latest",
5057
+ },
5058
+ undefined,
5059
+ "application",
5060
+ );
5061
+ const podfileLock = loadYaml(readFileSync(lockFile, "utf-8"));
5062
+ const pods = await parsePodfileLock(podfileLock, projectPath);
5063
+ const allObjects = new Map();
5064
+ for (const [name, pod] of pods) {
5065
+ allObjects.set(name, await buildObjectForCocoaPod(pod.metadata, options));
5066
+ }
5067
+ const allDependencies = new Map();
5068
+ for (const [name, pod] of pods) {
5069
+ const podDependencies = new Set();
5070
+ if (pod.dependencies) {
5071
+ for (const podDependency of pod.dependencies) {
5072
+ podDependencies.add(podDependency.name);
5073
+ }
5074
+ }
5075
+ allDependencies.set(name, podDependencies);
5076
+ }
5077
+ const targetDependencies = new Map();
5078
+ if (
5079
+ !process.env.COCOA_INCLUDED_TARGETS &&
5080
+ !process.env.COCOA_EXCLUDED_TARGETS
5081
+ ) {
5082
+ targetDependencies.set("Pods", podfileLock["DEPENDENCIES"]);
5083
+ } else {
5084
+ const result = executePodCommand(
5085
+ ["ipc", "podfile-json", "--silent", podFile],
5086
+ projectPath,
5087
+ options,
5088
+ );
5089
+ const resolvedPodFile = JSON.parse(result.stdout);
5090
+ parsePodfileTargets(
5091
+ resolvedPodFile["target_definitions"][0],
5092
+ targetDependencies,
5093
+ );
5094
+ }
5095
+ const usedTargets = new Set(
5096
+ process.env.COCOA_INCLUDED_TARGETS
5097
+ ? process.env.COCOA_INCLUDED_TARGETS.split(",")
5098
+ : targetDependencies.keys(),
5099
+ );
5100
+ if (process.env.COCOA_EXCLUDED_TARGETS) {
5101
+ process.env.COCOA_EXCLUDED_TARGETS.split(",").forEach((excludedTarget) =>
5102
+ usedTargets.delete(excludedTarget),
5103
+ );
5104
+ if (!excludeMessageShown) {
5105
+ thoughtLog(
5106
+ "Wait, the user wants me to exclude certain targets from this CocoaPods project. Perhaps they don't want dev and test projects included in the SBOM 🤔?",
5107
+ );
5108
+ excludeMessageShown = true;
5109
+ }
5110
+ }
5111
+ let addedObjects = new Set();
5112
+ for (const target of usedTargets) {
5113
+ if (targetDependencies.has(target)) {
5114
+ for (const dependency of targetDependencies.get(target)) {
5115
+ let dependencyName = parseCocoaDependency(dependency, false).name;
5116
+ if (!["false", "0"].includes(process.env.COCOA_MERGE_SUBSPECS)) {
5117
+ dependencyName = dependencyName.split("/")[0];
5118
+ }
5119
+ addedObjects.add(dependencyName);
5120
+ }
5121
+ }
5122
+ }
5123
+ let includedDependencies = [
5124
+ {
5125
+ ref: parentComponent["bom-ref"],
5126
+ dependsOn: [
5127
+ ...new Set(
5128
+ [...addedObjects].map((obj) => allObjects.get(obj)["bom-ref"]),
5129
+ ),
5130
+ ],
5131
+ },
5132
+ ];
5133
+ const includedObjects = new Set(addedObjects);
5134
+ while (addedObjects.size !== 0) {
5135
+ const newlyAddedObjects = new Set();
5136
+ for (const addedObject of addedObjects) {
5137
+ for (const dependency of allDependencies.get(addedObject)) {
5138
+ if (!includedObjects.has(dependency)) {
5139
+ includedObjects.add(dependency);
5140
+ newlyAddedObjects.add(dependency);
5141
+ }
5142
+ }
5143
+ }
5144
+ addedObjects = newlyAddedObjects;
5145
+ }
5146
+ for (const object of includedObjects) {
5147
+ includedDependencies = mergeDependencies(includedDependencies, [
5148
+ {
5149
+ ref: allObjects.get(object)["bom-ref"],
5150
+ dependsOn: [
5151
+ ...new Set(
5152
+ [...allDependencies.get(object)].map(
5153
+ (obj) => allObjects.get(obj)["bom-ref"],
5154
+ ),
5155
+ ),
5156
+ ],
5157
+ },
5158
+ ]);
5159
+ }
5160
+ return buildBomNSData(
5161
+ options,
5162
+ [...includedObjects].map((obj) => allObjects.get(obj)),
5163
+ "cocoapods",
5164
+ {
5165
+ src: path,
5166
+ filename: lockFile,
5167
+ dependencies: includedDependencies,
5168
+ parentComponent,
5169
+ },
5170
+ );
5171
+ }
5172
+ }
5173
+
5015
5174
  /**
5016
5175
  * Function to create bom string for docker compose
5017
5176
  *
@@ -5727,20 +5886,11 @@ export async function createCsharpBom(path, options) {
5727
5886
  `${options.multiProject ? "**/" : ""}*.sln`,
5728
5887
  options,
5729
5888
  );
5730
- let csProjFiles = getAllFiles(
5889
+ const csProjFiles = getAllFiles(
5731
5890
  path,
5732
- `${options.multiProject ? "**/" : ""}*.csproj`,
5891
+ `${options.multiProject ? "**/" : ""}*.{cs,vb,fs,ts,hmi,plc}proj`,
5733
5892
  options,
5734
5893
  );
5735
- csProjFiles = csProjFiles.concat(
5736
- getAllFiles(path, `${options.multiProject ? "**/" : ""}*.vbproj`, options),
5737
- );
5738
- csProjFiles = csProjFiles.concat(
5739
- getAllFiles(path, `${options.multiProject ? "**/" : ""}*.vcxproj`, options),
5740
- );
5741
- csProjFiles = csProjFiles.concat(
5742
- getAllFiles(path, `${options.multiProject ? "**/" : ""}*.fsproj`, options),
5743
- );
5744
5894
  const pkgConfigFiles = getAllFiles(
5745
5895
  path,
5746
5896
  `${options.multiProject ? "**/" : ""}packages.config`,
@@ -6095,6 +6245,9 @@ export async function createCsharpBom(path, options) {
6095
6245
  );
6096
6246
  // Create the slices file if it doesn't exist
6097
6247
  if (!safeExistsSync(slicesFile)) {
6248
+ thoughtLog(
6249
+ "Alright, the next step is to invoke the dosai command to identify evidence of occurrences for various components.",
6250
+ );
6098
6251
  const sliceResult = getDotnetSlices(resolve(path), resolve(slicesFile));
6099
6252
  if (!sliceResult && DEBUG_MODE) {
6100
6253
  console.log(
@@ -6523,7 +6676,7 @@ export async function createMultiXBom(pathList, options) {
6523
6676
  // and removing them from metadata.component.components -- the components are merged later
6524
6677
  if (bomData.parentComponent.components?.length) {
6525
6678
  let bomSubComponents = bomData.parentComponent.components;
6526
- if (["true", "1"].includes(process.env.GRADLE_RESOLVE_FROM_NODE)) {
6679
+ if (!["false", "0"].includes(process.env.GRADLE_RESOLVE_FROM_NODE)) {
6527
6680
  thoughtLog(
6528
6681
  "Wait, the user wants me to resolve gradle projects from npm.",
6529
6682
  );
@@ -6977,6 +7130,24 @@ export async function createMultiXBom(pathList, options) {
6977
7130
  }
6978
7131
  }
6979
7132
  }
7133
+ if (hasAnyProjectType(["oci", "cocoa"], options)) {
7134
+ bomData = await createCocoaBom(path, options);
7135
+ if (bomData?.bomJson?.components?.length) {
7136
+ if (DEBUG_MODE) {
7137
+ console.log(
7138
+ `Found ${bomData.bomJson.components.length} Cocoa packages at ${path}`,
7139
+ );
7140
+ }
7141
+ components = components.concat(bomData.bomJson.components);
7142
+ dependencies = dependencies.concat(bomData.bomJson.dependencies);
7143
+ if (
7144
+ bomData.parentComponent &&
7145
+ Object.keys(bomData.parentComponent).length
7146
+ ) {
7147
+ parentSubComponents.push(bomData.parentComponent);
7148
+ }
7149
+ }
7150
+ }
6980
7151
  // Collect any crypto keys
6981
7152
  if (options.specVersion >= 1.6 && options.includeCrypto) {
6982
7153
  thoughtLog(
@@ -7207,17 +7378,11 @@ export async function createXBom(path, options) {
7207
7378
  }
7208
7379
 
7209
7380
  // .Net
7210
- let csProjFiles = getAllFiles(
7381
+ const csProjFiles = getAllFiles(
7211
7382
  path,
7212
- `${options.multiProject ? "**/" : ""}*.csproj`,
7383
+ `${options.multiProject ? "**/" : ""}*.{cs,vb,fs,ts,hmi,plc}proj`,
7213
7384
  options,
7214
7385
  );
7215
- csProjFiles = csProjFiles.concat(
7216
- getAllFiles(path, `${options.multiProject ? "**/" : ""}*.vbproj`, options),
7217
- );
7218
- csProjFiles = csProjFiles.concat(
7219
- getAllFiles(path, `${options.multiProject ? "**/" : ""}*.fsproj`, options),
7220
- );
7221
7386
  if (csProjFiles.length) {
7222
7387
  return await createCsharpBom(path, options);
7223
7388
  }
@@ -7397,6 +7562,16 @@ export async function createXBom(path, options) {
7397
7562
  if (swiftFiles.length || pkgResolvedFiles.length) {
7398
7563
  return await createSwiftBom(path, options);
7399
7564
  }
7565
+
7566
+ // Cocoa
7567
+ const cocoaFiles = getAllFiles(
7568
+ path,
7569
+ `${options.multiProject ? "**/" : ""}Podfile`,
7570
+ options,
7571
+ );
7572
+ if (cocoaFiles.length) {
7573
+ return await createCocoaBom(path, options);
7574
+ }
7400
7575
  }
7401
7576
 
7402
7577
  /**
@@ -7550,7 +7725,7 @@ export async function createBom(path, options) {
7550
7725
  );
7551
7726
  } else {
7552
7727
  thoughtLog(
7553
- `The user wants me to focus on a single type, '${projectType}'. Could there be an issue with auto-detection, or might they use another tool like cyclonedx-cli to merge all the generated BOMs later?`,
7728
+ `The user wants me to focus on a single type, '${projectType}'.`,
7554
7729
  );
7555
7730
  }
7556
7731
  }
@@ -7636,6 +7811,9 @@ export async function createBom(path, options) {
7636
7811
  if (PROJECT_TYPE_ALIASES["binary"].includes(projectType[0])) {
7637
7812
  return createBinaryBom(path, options);
7638
7813
  }
7814
+ if (PROJECT_TYPE_ALIASES["cocoa"].includes(projectType[0])) {
7815
+ return await createCocoaBom(path, options);
7816
+ }
7639
7817
  switch (projectType[0]) {
7640
7818
  case "jar":
7641
7819
  return createJarBom(path, options);
@@ -619,6 +619,13 @@ export async function parseSliceUsages(
619
619
  const typesToLookup = new Set();
620
620
  const lKeyOverrides = {};
621
621
  const usages = slice.usages || [];
622
+ // What should be the line number to use. slice.lineNumber would be quite coarse and could lead to reports such as
623
+ // #1670. Line numbers under targetObj and definedBy is a safe bet for dynamic languages, but occassionally leads to
624
+ // confusion when inter-procedural tracking works better than expected.
625
+ let sliceLineNumber;
626
+ if (["java", "jar"].includes(language)) {
627
+ sliceLineNumber = slice.lineNumber;
628
+ }
622
629
  // Annotations from usages
623
630
  if (slice.signature?.startsWith("@") && !usages.length) {
624
631
  typesToLookup.add(slice.fullName);
@@ -631,7 +638,9 @@ export async function parseSliceUsages(
631
638
  }
632
639
  for (const ausage of usages) {
633
640
  const ausageLine =
634
- ausage?.targetObj?.lineNumber || ausage?.definedBy?.lineNumber;
641
+ sliceLineNumber ||
642
+ ausage?.targetObj?.lineNumber ||
643
+ ausage?.definedBy?.lineNumber;
635
644
  // First capture the types in the targetObj and definedBy
636
645
  for (const atype of [
637
646
  [ausage?.targetObj?.isExternal, ausage?.targetObj?.typeFullName],
@@ -585,6 +585,9 @@ export function createSemanticsSlices(basePath, options) {
585
585
  console.log(
586
586
  "TIP: Unable to detect the swift sdk needed to build this project. Try running the swift build command to check if this project builds successfully.",
587
587
  );
588
+ console.log(
589
+ "Check whether the project requires xcodebuild to build. Such projects are currently unsupported.",
590
+ );
588
591
  return;
589
592
  }
590
593
  }
@@ -31,6 +31,7 @@ export function thoughtLog(s, args) {
31
31
  if (!s?.endsWith(".") && !s?.endsWith("?") && !s?.endsWith("!")) {
32
32
  s = `${s}.`;
33
33
  }
34
+ s = s.replaceAll("'.'", "'<project dir>'");
34
35
  if (args) {
35
36
  tlogger.log(colorizeText(`${s}`), args);
36
37
  } else {