@cyclonedx/cdxgen 12.2.0 → 12.3.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.
Files changed (181) hide show
  1. package/README.md +242 -90
  2. package/bin/audit.js +191 -0
  3. package/bin/cdxgen.js +532 -168
  4. package/bin/convert.js +99 -0
  5. package/bin/evinse.js +23 -0
  6. package/bin/repl.js +339 -8
  7. package/bin/sign.js +8 -0
  8. package/bin/validate.js +8 -0
  9. package/bin/verify.js +8 -0
  10. package/data/container-knowledge-index.json +125 -0
  11. package/data/gtfobins-index.json +6296 -0
  12. package/data/lolbas-index.json +150 -0
  13. package/data/queries-darwin.json +63 -3
  14. package/data/queries-win.json +45 -3
  15. package/data/queries.json +74 -2
  16. package/data/rules/chrome-extensions.yaml +240 -0
  17. package/data/rules/ci-permissions.yaml +478 -18
  18. package/data/rules/container-risk.yaml +270 -0
  19. package/data/rules/obom-runtime.yaml +891 -0
  20. package/data/rules/package-integrity.yaml +49 -0
  21. package/data/spdx-export.schema.json +6794 -0
  22. package/data/spdx-model-v3.0.1.jsonld +15999 -0
  23. package/lib/audit/index.js +1924 -0
  24. package/lib/audit/index.poku.js +1488 -0
  25. package/lib/audit/progress.js +137 -0
  26. package/lib/audit/progress.poku.js +188 -0
  27. package/lib/audit/reporters.js +618 -0
  28. package/lib/audit/scoring.js +310 -0
  29. package/lib/audit/scoring.poku.js +341 -0
  30. package/lib/audit/targets.js +260 -0
  31. package/lib/audit/targets.poku.js +331 -0
  32. package/lib/cli/index.js +276 -68
  33. package/lib/cli/index.poku.js +368 -0
  34. package/lib/helpers/analyzer.js +1052 -5
  35. package/lib/helpers/analyzer.poku.js +301 -0
  36. package/lib/helpers/annotationFormatter.js +49 -0
  37. package/lib/helpers/annotationFormatter.poku.js +44 -0
  38. package/lib/helpers/bomUtils.js +36 -0
  39. package/lib/helpers/bomUtils.poku.js +51 -0
  40. package/lib/helpers/caxa.js +2 -2
  41. package/lib/helpers/chromextutils.js +1153 -0
  42. package/lib/helpers/chromextutils.poku.js +493 -0
  43. package/lib/helpers/ciParsers/githubActions.js +1632 -45
  44. package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
  45. package/lib/helpers/containerRisk.js +186 -0
  46. package/lib/helpers/containerRisk.poku.js +52 -0
  47. package/lib/helpers/depsUtils.js +16 -0
  48. package/lib/helpers/depsUtils.poku.js +58 -1
  49. package/lib/helpers/display.js +245 -61
  50. package/lib/helpers/display.poku.js +162 -2
  51. package/lib/helpers/exportUtils.js +123 -0
  52. package/lib/helpers/exportUtils.poku.js +60 -0
  53. package/lib/helpers/formulationParsers.js +69 -0
  54. package/lib/helpers/formulationParsers.poku.js +44 -0
  55. package/lib/helpers/gtfobins.js +189 -0
  56. package/lib/helpers/gtfobins.poku.js +49 -0
  57. package/lib/helpers/lolbas.js +267 -0
  58. package/lib/helpers/lolbas.poku.js +39 -0
  59. package/lib/helpers/osqueryTransform.js +84 -0
  60. package/lib/helpers/osqueryTransform.poku.js +49 -0
  61. package/lib/helpers/provenanceUtils.js +193 -0
  62. package/lib/helpers/provenanceUtils.poku.js +145 -0
  63. package/lib/helpers/pylockutils.js +281 -0
  64. package/lib/helpers/pylockutils.poku.js +48 -0
  65. package/lib/helpers/registryProvenance.js +793 -0
  66. package/lib/helpers/registryProvenance.poku.js +452 -0
  67. package/lib/helpers/remote/dependency-track.js +84 -0
  68. package/lib/helpers/remote/dependency-track.poku.js +119 -0
  69. package/lib/helpers/source.js +1267 -0
  70. package/lib/helpers/source.poku.js +771 -0
  71. package/lib/helpers/spdxUtils.js +97 -0
  72. package/lib/helpers/spdxUtils.poku.js +70 -0
  73. package/lib/helpers/table.js +384 -0
  74. package/lib/helpers/table.poku.js +186 -0
  75. package/lib/helpers/unicodeScan.js +147 -0
  76. package/lib/helpers/unicodeScan.poku.js +45 -0
  77. package/lib/helpers/utils.js +882 -136
  78. package/lib/helpers/utils.poku.js +995 -91
  79. package/lib/managers/binary.js +29 -5
  80. package/lib/managers/docker.js +179 -52
  81. package/lib/managers/docker.poku.js +327 -28
  82. package/lib/managers/oci.js +107 -23
  83. package/lib/managers/oci.poku.js +132 -0
  84. package/lib/server/openapi.yaml +50 -0
  85. package/lib/server/server.js +228 -331
  86. package/lib/server/server.poku.js +220 -5
  87. package/lib/stages/postgen/annotator.js +7 -0
  88. package/lib/stages/postgen/annotator.poku.js +40 -0
  89. package/lib/stages/postgen/auditBom.js +20 -5
  90. package/lib/stages/postgen/auditBom.poku.js +1729 -67
  91. package/lib/stages/postgen/postgen.js +40 -0
  92. package/lib/stages/postgen/postgen.poku.js +47 -0
  93. package/lib/stages/postgen/ruleEngine.js +80 -2
  94. package/lib/stages/postgen/spdxConverter.js +796 -0
  95. package/lib/stages/postgen/spdxConverter.poku.js +341 -0
  96. package/lib/validator/bomValidator.js +232 -0
  97. package/lib/validator/bomValidator.poku.js +70 -0
  98. package/lib/validator/complianceRules.js +70 -7
  99. package/lib/validator/complianceRules.poku.js +30 -0
  100. package/lib/validator/reporters/annotations.js +2 -2
  101. package/lib/validator/reporters/console.js +13 -2
  102. package/lib/validator/reporters.poku.js +13 -0
  103. package/package.json +10 -8
  104. package/types/bin/audit.d.ts +3 -0
  105. package/types/bin/audit.d.ts.map +1 -0
  106. package/types/bin/convert.d.ts +3 -0
  107. package/types/bin/convert.d.ts.map +1 -0
  108. package/types/bin/repl.d.ts.map +1 -1
  109. package/types/lib/audit/index.d.ts +115 -0
  110. package/types/lib/audit/index.d.ts.map +1 -0
  111. package/types/lib/audit/progress.d.ts +27 -0
  112. package/types/lib/audit/progress.d.ts.map +1 -0
  113. package/types/lib/audit/reporters.d.ts +35 -0
  114. package/types/lib/audit/reporters.d.ts.map +1 -0
  115. package/types/lib/audit/scoring.d.ts +35 -0
  116. package/types/lib/audit/scoring.d.ts.map +1 -0
  117. package/types/lib/audit/targets.d.ts +63 -0
  118. package/types/lib/audit/targets.d.ts.map +1 -0
  119. package/types/lib/cli/index.d.ts +8 -0
  120. package/types/lib/cli/index.d.ts.map +1 -1
  121. package/types/lib/helpers/analyzer.d.ts +13 -0
  122. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  123. package/types/lib/helpers/annotationFormatter.d.ts +23 -0
  124. package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
  125. package/types/lib/helpers/bomUtils.d.ts +5 -0
  126. package/types/lib/helpers/bomUtils.d.ts.map +1 -0
  127. package/types/lib/helpers/chromextutils.d.ts +97 -0
  128. package/types/lib/helpers/chromextutils.d.ts.map +1 -0
  129. package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
  130. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  131. package/types/lib/helpers/containerRisk.d.ts +17 -0
  132. package/types/lib/helpers/containerRisk.d.ts.map +1 -0
  133. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  134. package/types/lib/helpers/display.d.ts +4 -1
  135. package/types/lib/helpers/display.d.ts.map +1 -1
  136. package/types/lib/helpers/exportUtils.d.ts +40 -0
  137. package/types/lib/helpers/exportUtils.d.ts.map +1 -0
  138. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  139. package/types/lib/helpers/gtfobins.d.ts +17 -0
  140. package/types/lib/helpers/gtfobins.d.ts.map +1 -0
  141. package/types/lib/helpers/lolbas.d.ts +16 -0
  142. package/types/lib/helpers/lolbas.d.ts.map +1 -0
  143. package/types/lib/helpers/osqueryTransform.d.ts +7 -0
  144. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
  145. package/types/lib/helpers/provenanceUtils.d.ts +90 -0
  146. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
  147. package/types/lib/helpers/pylockutils.d.ts +51 -0
  148. package/types/lib/helpers/pylockutils.d.ts.map +1 -0
  149. package/types/lib/helpers/registryProvenance.d.ts +17 -0
  150. package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
  151. package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
  152. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  153. package/types/lib/helpers/source.d.ts +141 -0
  154. package/types/lib/helpers/source.d.ts.map +1 -0
  155. package/types/lib/helpers/spdxUtils.d.ts +2 -0
  156. package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
  157. package/types/lib/helpers/table.d.ts +6 -0
  158. package/types/lib/helpers/table.d.ts.map +1 -0
  159. package/types/lib/helpers/unicodeScan.d.ts +46 -0
  160. package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
  161. package/types/lib/helpers/utils.d.ts +30 -11
  162. package/types/lib/helpers/utils.d.ts.map +1 -1
  163. package/types/lib/managers/binary.d.ts.map +1 -1
  164. package/types/lib/managers/docker.d.ts.map +1 -1
  165. package/types/lib/managers/oci.d.ts.map +1 -1
  166. package/types/lib/server/server.d.ts +0 -35
  167. package/types/lib/server/server.d.ts.map +1 -1
  168. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  169. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  170. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  171. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  172. package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
  173. package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
  174. package/types/lib/validator/bomValidator.d.ts +1 -0
  175. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  176. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  177. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  178. package/types/bin/dependencies.d.ts +0 -3
  179. package/types/bin/dependencies.d.ts.map +0 -1
  180. package/types/bin/licenses.d.ts +0 -3
  181. package/types/bin/licenses.d.ts.map +0 -1
@@ -0,0 +1,796 @@
1
+ import { CDXGEN_SPDX_CREATED_BY, getTimestamp } from "../../helpers/utils.js";
2
+
3
+ export const SPDX_JSONLD_CONTEXT =
4
+ "https://spdx.org/rdf/3.0.1/spdx-context.jsonld";
5
+ export const SPDX_SPEC_VERSION = "3.0.1";
6
+
7
+ const SPDX_DOCUMENT_PROFILES = ["core", "software"];
8
+ const SPDX_RELATIONSHIP_DEPENDS_ON = "dependsOn";
9
+ const SPDX_EXTENSION_PROFILE = "extension";
10
+ const SPDX_EXTENSION_KEY = "extension";
11
+ const SPDX_CDX_PROPERTIES_EXTENSION_TYPE = "extension_CdxPropertiesExtension";
12
+ const SPDX_CDX_PROPERTY_ENTRY_TYPE = "extension_CdxPropertyEntry";
13
+ const SPDX_CDX_PROPERTY_KEY = "extension_cdxProperty";
14
+ const SPDX_CDX_PROPERTY_NAME_KEY = "extension_cdxPropName";
15
+ const SPDX_CDX_PROPERTY_VALUE_KEY = "extension_cdxPropValue";
16
+ const SPDX_EXTERNAL_REF_TYPE_MAP = Object.freeze({
17
+ website: "altWebPage",
18
+ documentation: "documentation",
19
+ distribution: "altDownloadLocation",
20
+ download: "altDownloadLocation",
21
+ "issue-tracker": "issueTracker",
22
+ "mailing-list": "mailingList",
23
+ vcs: "vcs",
24
+ "build-meta": "buildMeta",
25
+ "build-system": "buildSystem",
26
+ "release-notes": "releaseNotes",
27
+ chat: "chat",
28
+ social: "socialMedia",
29
+ "social-media": "socialMedia",
30
+ support: "support",
31
+ license: "license",
32
+ cwe: "cwe",
33
+ });
34
+ const SPDX_HASH_ALGORITHMS = new Set([
35
+ "sha1",
36
+ "sha224",
37
+ "sha256",
38
+ "sha384",
39
+ "sha512",
40
+ "sha3_256",
41
+ "sha3_384",
42
+ "sha3_512",
43
+ "md2",
44
+ "md4",
45
+ "md5",
46
+ "md6",
47
+ "adler32",
48
+ "blake2b_256",
49
+ "blake2b_384",
50
+ "blake2b_512",
51
+ "blake3",
52
+ "gost3411",
53
+ "ripemd_160",
54
+ "shake_256",
55
+ "sm3",
56
+ "streebog_256",
57
+ "streebog_512",
58
+ ]);
59
+
60
+ /**
61
+ * Cache normalized SPDX hash algorithm names across conversions.
62
+ *
63
+ * This module-level cache intentionally lives for the process lifetime so
64
+ * repeated convertCycloneDxToSpdx() calls avoid repeated normalization work.
65
+ */
66
+ const normalizedHashAlgorithmCache = new Map();
67
+
68
+ const toArray = (value) => {
69
+ if (Array.isArray(value)) {
70
+ return value;
71
+ }
72
+ if (value) {
73
+ return [value];
74
+ }
75
+ return [];
76
+ };
77
+
78
+ const normalizeHashAlgorithm = (algorithm) => {
79
+ const cacheKey = `${algorithm || ""}`;
80
+ if (normalizedHashAlgorithmCache.has(cacheKey)) {
81
+ return normalizedHashAlgorithmCache.get(cacheKey);
82
+ }
83
+ const normalized = `${algorithm || ""}`
84
+ .trim()
85
+ .toLowerCase()
86
+ .replace(/-/gu, "")
87
+ .replace(/\//gu, "_");
88
+ const normalizedAlgorithm = SPDX_HASH_ALGORITHMS.has(normalized)
89
+ ? normalized
90
+ : undefined;
91
+ normalizedHashAlgorithmCache.set(cacheKey, normalizedAlgorithm);
92
+ return normalizedAlgorithm;
93
+ };
94
+
95
+ const toSerializableValue = (value) => {
96
+ if (value === undefined) {
97
+ return undefined;
98
+ }
99
+ if (value === null) {
100
+ return null;
101
+ }
102
+ if (
103
+ typeof value === "string" ||
104
+ typeof value === "number" ||
105
+ typeof value === "boolean"
106
+ ) {
107
+ return value;
108
+ }
109
+ if (Array.isArray(value)) {
110
+ const output = [];
111
+ for (const item of value) {
112
+ const mappedValue = toSerializableValue(item);
113
+ if (mappedValue !== undefined) {
114
+ output.push(mappedValue);
115
+ }
116
+ }
117
+ return output;
118
+ }
119
+ if (typeof value === "object") {
120
+ const output = {};
121
+ for (const [key, item] of Object.entries(value)) {
122
+ const mappedValue = toSerializableValue(item);
123
+ if (mappedValue !== undefined) {
124
+ output[key] = mappedValue;
125
+ }
126
+ }
127
+ return output;
128
+ }
129
+ return `${value}`;
130
+ };
131
+
132
+ const addIfDefined = (obj, key, value) => {
133
+ if (value !== undefined && value !== null) {
134
+ obj[key] = value;
135
+ }
136
+ };
137
+
138
+ const hasEntries = (value) => {
139
+ if (!value) {
140
+ return false;
141
+ }
142
+ if (Array.isArray(value)) {
143
+ return value.length > 0;
144
+ }
145
+ if (typeof value === "object") {
146
+ return Object.keys(value).length > 0;
147
+ }
148
+ return true;
149
+ };
150
+
151
+ const isSimpleValue = (value) =>
152
+ value === null ||
153
+ typeof value === "string" ||
154
+ typeof value === "number" ||
155
+ typeof value === "boolean";
156
+
157
+ const stringifyExtensionPropertyValue = (value) =>
158
+ typeof value === "string"
159
+ ? value
160
+ : JSON.stringify(toSerializableValue(value));
161
+
162
+ const encodeSpdxFragment = (value) =>
163
+ `${value || "unknown"}`
164
+ .replace(/[^A-Za-z0-9._-]+/gu, "-")
165
+ .replace(/^-+/u, "")
166
+ .replace(/-+$/u, "") || "unknown";
167
+
168
+ const createNamespace = (bomJson) => {
169
+ const serial = `${bomJson?.serialNumber || ""}`.replace(/^urn:uuid:/u, "");
170
+ const componentName = `${bomJson?.metadata?.component?.name || ""}`.trim();
171
+ const serialFragment = serial ? encodeSpdxFragment(serial) : "";
172
+ const componentNameFragment = componentName
173
+ ? encodeSpdxFragment(componentName)
174
+ : "";
175
+ const base = serialFragment || componentNameFragment || `${Date.now()}`;
176
+ return `urn:cdxgen:spdx:${base}#`;
177
+ };
178
+
179
+ const buildElementKey = (component) =>
180
+ component?.["bom-ref"] ||
181
+ component?.purl ||
182
+ `${component?.name || "component"}@${component?.version || "0"}`;
183
+
184
+ const buildSpdxId = (namespace, prefix, value) =>
185
+ `${namespace}${prefix}-${encodeSpdxFragment(value)}`;
186
+
187
+ const selectRootComponent = (bomJson) => {
188
+ if (bomJson?.metadata?.component) {
189
+ return bomJson.metadata.component;
190
+ }
191
+ return bomJson?.components?.[0];
192
+ };
193
+
194
+ const toSpdxHashes = (hashInput) => {
195
+ const hashes = [];
196
+ const originalHashes = [];
197
+ for (const hash of toArray(hashInput)) {
198
+ if (!hash?.content) {
199
+ continue;
200
+ }
201
+ const algorithm = normalizeHashAlgorithm(hash?.alg);
202
+ const originalHash = {
203
+ algorithm: hash?.alg || "unknown",
204
+ hashValue: hash.content,
205
+ };
206
+ if (algorithm) {
207
+ originalHash.normalizedAlgorithm = algorithm;
208
+ }
209
+ originalHashes.push(originalHash);
210
+ if (!algorithm) {
211
+ continue;
212
+ }
213
+ hashes.push({
214
+ type: "Hash",
215
+ algorithm,
216
+ hashValue: hash.content,
217
+ });
218
+ }
219
+ return { hashes, originalHashes };
220
+ };
221
+
222
+ const toPropertyList = (propertyInput) => {
223
+ const properties = [];
224
+ for (const property of toArray(propertyInput)) {
225
+ if (!property?.name) {
226
+ continue;
227
+ }
228
+ properties.push({
229
+ name: `${property.name}`,
230
+ value: `${property.value ?? ""}`,
231
+ });
232
+ }
233
+ return properties;
234
+ };
235
+
236
+ const toReferenceList = (referenceInput) => {
237
+ const references = [];
238
+ for (const reference of toArray(referenceInput)) {
239
+ if (!reference?.url) {
240
+ continue;
241
+ }
242
+ const serializedReference = {
243
+ type: `${reference?.type || "other"}`,
244
+ url: reference.url,
245
+ };
246
+ const refHashes = toSpdxHashes(reference?.hashes);
247
+ if (reference?.comment) {
248
+ serializedReference.comment = reference.comment;
249
+ }
250
+ if (refHashes.hashes.length) {
251
+ serializedReference.verifiedUsing = refHashes.hashes;
252
+ }
253
+ if (refHashes.originalHashes.length) {
254
+ serializedReference.originalHashes = refHashes.originalHashes;
255
+ }
256
+ references.push(serializedReference);
257
+ }
258
+ return references;
259
+ };
260
+
261
+ const toSpdxExternalRefList = (referenceInput) => {
262
+ const references = [];
263
+ for (const reference of toArray(referenceInput)) {
264
+ if (!reference?.url) {
265
+ continue;
266
+ }
267
+ const spdxReference = {
268
+ type: "ExternalRef",
269
+ externalRefType: SPDX_EXTERNAL_REF_TYPE_MAP[reference?.type] || "other",
270
+ locator: [reference.url],
271
+ };
272
+ if (reference?.comment) {
273
+ spdxReference.comment = reference.comment;
274
+ }
275
+ references.push(spdxReference);
276
+ }
277
+ return references;
278
+ };
279
+
280
+ const toSpdxExternalReferences = (component) => {
281
+ const references = toSpdxExternalRefList(component?.externalReferences);
282
+ const homepageReference = references.find((reference) =>
283
+ [
284
+ "altWebPage",
285
+ "documentation",
286
+ "altDownloadLocation",
287
+ "releaseNotes",
288
+ ].includes(reference?.externalRefType),
289
+ );
290
+ const downloadReference = references.find(
291
+ (reference) => reference?.externalRefType === "altDownloadLocation",
292
+ );
293
+ return {
294
+ references,
295
+ homepageReference,
296
+ downloadReference,
297
+ };
298
+ };
299
+
300
+ const buildCycloneDxExtensionData = (component, additionalData = {}) => {
301
+ const extensionData = {};
302
+ const properties = toPropertyList(component?.properties);
303
+ const externalReferences = toReferenceList(component?.externalReferences);
304
+ const hashMappings = toSpdxHashes(component?.hashes);
305
+
306
+ addIfDefined(extensionData, "bomRef", component?.["bom-ref"]);
307
+ addIfDefined(extensionData, "group", component?.group);
308
+ addIfDefined(extensionData, "scope", component?.scope);
309
+ if (properties.length) {
310
+ extensionData.properties = properties;
311
+ }
312
+ if (externalReferences.length) {
313
+ extensionData.externalReferences = externalReferences;
314
+ }
315
+ if (hashMappings.originalHashes.length) {
316
+ extensionData.hashes = hashMappings.originalHashes;
317
+ }
318
+ addIfDefined(
319
+ extensionData,
320
+ "licenses",
321
+ toSerializableValue(component?.licenses),
322
+ );
323
+ addIfDefined(
324
+ extensionData,
325
+ "supplier",
326
+ toSerializableValue(component?.supplier),
327
+ );
328
+ addIfDefined(
329
+ extensionData,
330
+ "manufacturer",
331
+ toSerializableValue(component?.manufacturer),
332
+ );
333
+ addIfDefined(extensionData, "author", toSerializableValue(component?.author));
334
+ addIfDefined(
335
+ extensionData,
336
+ "authors",
337
+ toSerializableValue(component?.authors),
338
+ );
339
+ addIfDefined(
340
+ extensionData,
341
+ "publisher",
342
+ toSerializableValue(component?.publisher),
343
+ );
344
+ addIfDefined(
345
+ extensionData,
346
+ "maintainer",
347
+ toSerializableValue(component?.maintainer),
348
+ );
349
+ addIfDefined(
350
+ extensionData,
351
+ "maintainers",
352
+ toSerializableValue(component?.maintainers),
353
+ );
354
+ addIfDefined(extensionData, "tags", toSerializableValue(component?.tags));
355
+ addIfDefined(
356
+ extensionData,
357
+ "releaseNotes",
358
+ toSerializableValue(component?.releaseNotes),
359
+ );
360
+ addIfDefined(
361
+ extensionData,
362
+ "evidence",
363
+ toSerializableValue(component?.evidence),
364
+ );
365
+ addIfDefined(
366
+ extensionData,
367
+ "pedigree",
368
+ toSerializableValue(component?.pedigree),
369
+ );
370
+ addIfDefined(extensionData, "cpe", toSerializableValue(component?.cpe));
371
+ addIfDefined(extensionData, "swid", toSerializableValue(component?.swid));
372
+ addIfDefined(
373
+ extensionData,
374
+ "omniborId",
375
+ toSerializableValue(component?.omniborId),
376
+ );
377
+ addIfDefined(extensionData, "swhid", toSerializableValue(component?.swhid));
378
+ for (const [key, value] of Object.entries(additionalData)) {
379
+ addIfDefined(extensionData, key, toSerializableValue(value));
380
+ }
381
+ return hasEntries(extensionData) ? extensionData : undefined;
382
+ };
383
+
384
+ const maybeAppendExtensionPropertyEntries = (propertyEntries, key, value) => {
385
+ if (value === undefined || value === null) {
386
+ return;
387
+ }
388
+ if (
389
+ Array.isArray(value) &&
390
+ value.every((entry) => entry?.name && isSimpleValue(entry?.value))
391
+ ) {
392
+ for (const entry of value) {
393
+ propertyEntries.push({
394
+ type: SPDX_CDX_PROPERTY_ENTRY_TYPE,
395
+ [SPDX_CDX_PROPERTY_NAME_KEY]: `${key}.${entry.name}`,
396
+ [SPDX_CDX_PROPERTY_VALUE_KEY]: `${entry.value}`,
397
+ });
398
+ }
399
+ return;
400
+ }
401
+ propertyEntries.push({
402
+ type: SPDX_CDX_PROPERTY_ENTRY_TYPE,
403
+ [SPDX_CDX_PROPERTY_NAME_KEY]: key,
404
+ [SPDX_CDX_PROPERTY_VALUE_KEY]: stringifyExtensionPropertyValue(value),
405
+ });
406
+ };
407
+
408
+ const toSpdxExtensions = (extensionData) => {
409
+ if (!hasEntries(extensionData)) {
410
+ return undefined;
411
+ }
412
+ const propertyEntries = [];
413
+ for (const [key, value] of Object.entries(extensionData)) {
414
+ maybeAppendExtensionPropertyEntries(propertyEntries, key, value);
415
+ }
416
+ if (!propertyEntries.length) {
417
+ return undefined;
418
+ }
419
+ return [
420
+ {
421
+ type: SPDX_CDX_PROPERTIES_EXTENSION_TYPE,
422
+ [SPDX_CDX_PROPERTY_KEY]: propertyEntries,
423
+ },
424
+ ];
425
+ };
426
+
427
+ const createSyntheticElement = (
428
+ namespace,
429
+ source,
430
+ entryType,
431
+ index,
432
+ formulationIndex,
433
+ ) => {
434
+ const name =
435
+ source?.name || source?.["bom-ref"] || `${entryType}-${index + 1}`;
436
+ const bomRef =
437
+ source?.["bom-ref"] ||
438
+ `urn:cdxgen:${entryType}:${formulationIndex ?? "root"}:${index}`;
439
+ const synthetic = {
440
+ type: "library",
441
+ name,
442
+ version: source?.version,
443
+ description: source?.description,
444
+ "bom-ref": bomRef,
445
+ properties: source?.properties,
446
+ externalReferences: source?.externalReferences,
447
+ hashes: source?.hashes,
448
+ cdxgenSyntheticSource: {
449
+ entryType,
450
+ source: toSerializableValue(source),
451
+ },
452
+ };
453
+ const syntheticKey = buildElementKey(synthetic);
454
+ const syntheticSpdxId = buildSpdxId(
455
+ namespace,
456
+ "SPDXRef",
457
+ `${entryType}-${syntheticKey}`,
458
+ );
459
+ return { synthetic, syntheticSpdxId };
460
+ };
461
+
462
+ const collectSyntheticComponents = (bomJson, namespace) => {
463
+ const syntheticComponents = [];
464
+ const syntheticRefs = [];
465
+ for (const [index, service] of toArray(bomJson?.services).entries()) {
466
+ const syntheticEntry = createSyntheticElement(
467
+ namespace,
468
+ service,
469
+ "service",
470
+ index,
471
+ );
472
+ syntheticComponents.push(syntheticEntry.synthetic);
473
+ syntheticRefs.push(syntheticEntry.syntheticSpdxId);
474
+ }
475
+ for (const [formulationIndex, formulation] of toArray(
476
+ bomJson?.formulation,
477
+ ).entries()) {
478
+ for (const [serviceIndex, service] of toArray(
479
+ formulation?.services,
480
+ ).entries()) {
481
+ const syntheticEntry = createSyntheticElement(
482
+ namespace,
483
+ service,
484
+ "formulation-service",
485
+ serviceIndex,
486
+ formulationIndex,
487
+ );
488
+ syntheticComponents.push(syntheticEntry.synthetic);
489
+ syntheticRefs.push(syntheticEntry.syntheticSpdxId);
490
+ }
491
+ for (const [workflowIndex, workflow] of toArray(
492
+ formulation?.workflows,
493
+ ).entries()) {
494
+ const workflowEntry = createSyntheticElement(
495
+ namespace,
496
+ workflow,
497
+ "workflow",
498
+ workflowIndex,
499
+ formulationIndex,
500
+ );
501
+ syntheticComponents.push(workflowEntry.synthetic);
502
+ syntheticRefs.push(workflowEntry.syntheticSpdxId);
503
+ for (const [taskIndex, task] of toArray(workflow?.tasks).entries()) {
504
+ const taskEntry = createSyntheticElement(
505
+ namespace,
506
+ task,
507
+ "task",
508
+ taskIndex,
509
+ `${formulationIndex}-${workflowIndex}`,
510
+ );
511
+ syntheticComponents.push(taskEntry.synthetic);
512
+ syntheticRefs.push(taskEntry.syntheticSpdxId);
513
+ }
514
+ }
515
+ for (const [componentIndex, component] of toArray(
516
+ formulation?.components,
517
+ ).entries()) {
518
+ const syntheticEntry = createSyntheticElement(
519
+ namespace,
520
+ component,
521
+ "formulation-component",
522
+ componentIndex,
523
+ formulationIndex,
524
+ );
525
+ syntheticComponents.push(syntheticEntry.synthetic);
526
+ syntheticRefs.push(syntheticEntry.syntheticSpdxId);
527
+ }
528
+ }
529
+ return { syntheticComponents, syntheticRefs };
530
+ };
531
+
532
+ const buildDocumentExtensionData = (bomJson) => {
533
+ const documentExtension = {};
534
+ const metadataProperties = toPropertyList(bomJson?.metadata?.properties);
535
+ const bomProperties = toPropertyList(bomJson?.properties);
536
+ if (metadataProperties.length) {
537
+ documentExtension.metadataProperties = metadataProperties;
538
+ }
539
+ if (bomProperties.length) {
540
+ documentExtension.bomProperties = bomProperties;
541
+ }
542
+ addIfDefined(
543
+ documentExtension,
544
+ "metadataTools",
545
+ toSerializableValue(bomJson?.metadata?.tools),
546
+ );
547
+ addIfDefined(
548
+ documentExtension,
549
+ "metadataAuthors",
550
+ toSerializableValue(bomJson?.metadata?.authors),
551
+ );
552
+ addIfDefined(
553
+ documentExtension,
554
+ "metadataAuthor",
555
+ toSerializableValue(bomJson?.metadata?.author),
556
+ );
557
+ addIfDefined(
558
+ documentExtension,
559
+ "metadataPublisher",
560
+ toSerializableValue(bomJson?.metadata?.publisher),
561
+ );
562
+ addIfDefined(
563
+ documentExtension,
564
+ "metadataMaintainer",
565
+ toSerializableValue(bomJson?.metadata?.maintainer),
566
+ );
567
+ addIfDefined(
568
+ documentExtension,
569
+ "metadataMaintainers",
570
+ toSerializableValue(bomJson?.metadata?.maintainers),
571
+ );
572
+ addIfDefined(
573
+ documentExtension,
574
+ "metadataTags",
575
+ toSerializableValue(bomJson?.metadata?.tags),
576
+ );
577
+ addIfDefined(
578
+ documentExtension,
579
+ "metadataSupplier",
580
+ toSerializableValue(bomJson?.metadata?.supplier),
581
+ );
582
+ addIfDefined(
583
+ documentExtension,
584
+ "metadataManufacturer",
585
+ toSerializableValue(bomJson?.metadata?.manufacturer),
586
+ );
587
+ addIfDefined(
588
+ documentExtension,
589
+ "metadataLicenses",
590
+ toSerializableValue(bomJson?.metadata?.licenses),
591
+ );
592
+ addIfDefined(
593
+ documentExtension,
594
+ "services",
595
+ toSerializableValue(bomJson?.services),
596
+ );
597
+ addIfDefined(
598
+ documentExtension,
599
+ "formulation",
600
+ toSerializableValue(bomJson?.formulation),
601
+ );
602
+ return hasEntries(documentExtension) ? documentExtension : undefined;
603
+ };
604
+
605
+ const toSpdxPackage = (component, creationInfoId, spdxId) => {
606
+ const spdxPackage = {
607
+ type: component?.type === "file" ? "software_File" : "software_Package",
608
+ spdxId,
609
+ creationInfo: creationInfoId,
610
+ name: component?.name || "unnamed-component",
611
+ };
612
+ if (component?.description) {
613
+ spdxPackage.description = component.description;
614
+ }
615
+ if (component?.version && component?.type !== "file") {
616
+ spdxPackage.software_packageVersion = component.version;
617
+ }
618
+ if (component?.purl && component?.type !== "file") {
619
+ spdxPackage.software_packageUrl = component.purl;
620
+ }
621
+ const hashMappings = toSpdxHashes(component?.hashes);
622
+ if (hashMappings.hashes.length) {
623
+ spdxPackage.verifiedUsing = hashMappings.hashes;
624
+ }
625
+ const externalReferenceData = toSpdxExternalReferences(component);
626
+ if (
627
+ externalReferenceData.homepageReference?.locator?.[0] &&
628
+ component?.type !== "file"
629
+ ) {
630
+ spdxPackage.software_homePage =
631
+ externalReferenceData.homepageReference.locator[0];
632
+ }
633
+ if (
634
+ externalReferenceData.downloadReference?.locator?.[0] &&
635
+ component?.type !== "file"
636
+ ) {
637
+ spdxPackage.software_downloadLocation =
638
+ externalReferenceData.downloadReference.locator[0];
639
+ }
640
+ if (externalReferenceData.references.length) {
641
+ spdxPackage.externalRef = externalReferenceData.references;
642
+ }
643
+ const additionalExtensionData = {};
644
+ if (component?.cdxgenSyntheticSource) {
645
+ additionalExtensionData.syntheticSource = component.cdxgenSyntheticSource;
646
+ }
647
+ const extensionData = buildCycloneDxExtensionData(
648
+ component,
649
+ additionalExtensionData,
650
+ );
651
+ const extensions = toSpdxExtensions(extensionData);
652
+ if (extensions) {
653
+ spdxPackage[SPDX_EXTENSION_KEY] = extensions;
654
+ }
655
+ return spdxPackage;
656
+ };
657
+
658
+ const buildRelationship = (creationInfoId, from, to, relationshipId) => ({
659
+ type: "Relationship",
660
+ spdxId: relationshipId,
661
+ creationInfo: creationInfoId,
662
+ from,
663
+ to,
664
+ relationshipType: SPDX_RELATIONSHIP_DEPENDS_ON,
665
+ });
666
+
667
+ /**
668
+ * Convert a CycloneDX BOM JSON document into an SPDX 3.0.1 JSON-LD document.
669
+ *
670
+ * @param {object|string} bomJson CycloneDX BOM JSON
671
+ * @param {object} [options] CLI options
672
+ * @returns {object|undefined} SPDX 3.0.1 JSON-LD document
673
+ */
674
+ export function convertCycloneDxToSpdx(bomJson, options = {}) {
675
+ if (!bomJson) {
676
+ return undefined;
677
+ }
678
+ if (typeof bomJson === "string" || bomJson instanceof String) {
679
+ bomJson = JSON.parse(bomJson);
680
+ }
681
+ const namespace = createNamespace(bomJson);
682
+ const creationInfoId = buildSpdxId(namespace, "CreationInfo", "main");
683
+ const createdBy = [
684
+ CDXGEN_SPDX_CREATED_BY || "https://github.com/cdxgen/cdxgen",
685
+ ];
686
+ const creationInfo = {
687
+ type: "CreationInfo",
688
+ "@id": creationInfoId,
689
+ specVersion: SPDX_SPEC_VERSION,
690
+ created: bomJson?.metadata?.timestamp || getTimestamp(),
691
+ createdBy,
692
+ };
693
+ const rootComponent = selectRootComponent(bomJson);
694
+ const syntheticComponentData = collectSyntheticComponents(bomJson, namespace);
695
+ const allComponents = [];
696
+ if (rootComponent) {
697
+ allComponents.push(rootComponent);
698
+ }
699
+ for (const component of toArray(bomJson?.components)) {
700
+ allComponents.push(component);
701
+ }
702
+ for (const syntheticComponent of syntheticComponentData.syntheticComponents) {
703
+ allComponents.push(syntheticComponent);
704
+ }
705
+ const dedupedComponents = new Map();
706
+ const refToSpdxId = new Map();
707
+ const graphElements = [];
708
+ for (const component of allComponents) {
709
+ const elementKey = buildElementKey(component);
710
+ if (dedupedComponents.has(elementKey)) {
711
+ continue;
712
+ }
713
+ const spdxId = buildSpdxId(namespace, "SPDXRef", elementKey);
714
+ dedupedComponents.set(elementKey, component);
715
+ refToSpdxId.set(elementKey, spdxId);
716
+ graphElements.push(toSpdxPackage(component, creationInfoId, spdxId));
717
+ }
718
+ const relationshipElements = [];
719
+ let relationshipIndex = 0;
720
+ for (const dependency of toArray(bomJson?.dependencies)) {
721
+ const sourceSpdxId = refToSpdxId.get(dependency?.ref);
722
+ if (
723
+ !sourceSpdxId ||
724
+ !Array.isArray(dependency?.dependsOn) ||
725
+ !dependency.dependsOn.length
726
+ ) {
727
+ continue;
728
+ }
729
+ const toIds = dependency.dependsOn
730
+ .map((dependsOn) => refToSpdxId.get(dependsOn))
731
+ .filter(Boolean);
732
+ if (!toIds.length) {
733
+ continue;
734
+ }
735
+ relationshipIndex += 1;
736
+ relationshipElements.push(
737
+ buildRelationship(
738
+ creationInfoId,
739
+ sourceSpdxId,
740
+ toIds,
741
+ buildSpdxId(
742
+ namespace,
743
+ "Relationship",
744
+ `${dependency.ref}-${relationshipIndex}`,
745
+ ),
746
+ ),
747
+ );
748
+ }
749
+ const rootElementId = rootComponent
750
+ ? refToSpdxId.get(buildElementKey(rootComponent))
751
+ : undefined;
752
+ const documentId = buildSpdxId(namespace, "SPDXRef", "DOCUMENT");
753
+ const spdxDocument = {
754
+ type: "SpdxDocument",
755
+ spdxId: documentId,
756
+ creationInfo: creationInfoId,
757
+ name:
758
+ options?.projectName ||
759
+ bomJson?.metadata?.component?.name ||
760
+ bomJson?.metadata?.component?.["bom-ref"] ||
761
+ "cdxgen SPDX export",
762
+ profileConformance: [...SPDX_DOCUMENT_PROFILES],
763
+ element: [
764
+ ...graphElements.map((element) => element.spdxId),
765
+ ...relationshipElements.map((element) => element.spdxId),
766
+ ],
767
+ };
768
+ if (rootElementId) {
769
+ spdxDocument.rootElement = [rootElementId];
770
+ }
771
+ if (bomJson?.metadata?.component?.description) {
772
+ spdxDocument.description = bomJson.metadata.component.description;
773
+ }
774
+ const documentExtensionData = buildDocumentExtensionData(bomJson);
775
+ const documentExtensions = toSpdxExtensions(documentExtensionData);
776
+ if (documentExtensions) {
777
+ spdxDocument[SPDX_EXTENSION_KEY] = documentExtensions;
778
+ }
779
+ if (
780
+ documentExtensions ||
781
+ graphElements.some((element) =>
782
+ Array.isArray(element?.[SPDX_EXTENSION_KEY]),
783
+ )
784
+ ) {
785
+ spdxDocument.profileConformance.push(SPDX_EXTENSION_PROFILE);
786
+ }
787
+ return {
788
+ "@context": SPDX_JSONLD_CONTEXT,
789
+ "@graph": [
790
+ creationInfo,
791
+ spdxDocument,
792
+ ...graphElements,
793
+ ...relationshipElements,
794
+ ],
795
+ };
796
+ }