@cdxgen/cdxgen 13.0.0 → 13.1.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 (170) hide show
  1. package/README.md +26 -13
  2. package/bin/audit.js +6 -0
  3. package/bin/cdxgen.js +140 -8
  4. package/bin/convert.js +7 -0
  5. package/bin/evinse.js +1 -1
  6. package/bin/hbom.js +13 -4
  7. package/bin/tracebom.js +9 -1
  8. package/data/component-tags.json +8 -1
  9. package/data/frameworks-list.json +86 -1
  10. package/data/remediations.json +1144 -0
  11. package/data/rules/asar-archives.yaml +1 -1
  12. package/data/rules/build-fidelity.yaml +782 -0
  13. package/data/rules/package-integrity.yaml +13 -13
  14. package/lib/audit/index.js +48 -4
  15. package/lib/audit/scoring.js +2 -8
  16. package/lib/cli/bomAssembly.js +83 -15
  17. package/lib/cli/cliOptions.js +124 -7
  18. package/lib/cli/index.js +76 -48
  19. package/lib/cli/jsBom.js +32 -3
  20. package/lib/cli/jvmBom.js +1494 -1315
  21. package/lib/cli/managedBom.js +97 -28
  22. package/lib/cli/nativeBom.js +189 -10
  23. package/lib/core/buildLedger.js +799 -0
  24. package/lib/core/deferredExit.js +141 -0
  25. package/lib/core/env.js +69 -1
  26. package/lib/core/fs.js +60 -0
  27. package/lib/core/logger.js +76 -25
  28. package/lib/core/severity.js +16 -0
  29. package/lib/core/syncFileWriter.js +106 -0
  30. package/lib/core/ui.js +46 -2
  31. package/lib/ecosystems/ecosystems.js +9 -0
  32. package/lib/ecosystems/gradleutils.js +355 -15
  33. package/lib/ecosystems/parsers-js.js +26 -0
  34. package/lib/ecosystems/parsers-jvm.js +9 -0
  35. package/lib/ecosystems/parsers-misc.js +16 -2
  36. package/lib/ecosystems/parsers-python.js +171 -35
  37. package/lib/ecosystems/parsers-rust.js +12 -2
  38. package/lib/ecosystems/pypiClassifiers.js +326 -0
  39. package/lib/ecosystems/pythonutils.js +49 -1
  40. package/lib/ecosystems/rubyutils.js +13 -0
  41. package/lib/ecosystems/utils.js +9 -1
  42. package/lib/evinser/evinser.js +30 -12
  43. package/lib/helpers/remote/dependency-track.js +81 -14
  44. package/lib/inventory/auditCategories.js +9 -0
  45. package/lib/inventory/depsUtils.js +72 -0
  46. package/lib/inventory/dosai.js +431 -22
  47. package/lib/inventory/envcontext.js +211 -19
  48. package/lib/inventory/fetchBatch.js +57 -0
  49. package/lib/inventory/jvmToolEnv.js +593 -0
  50. package/lib/inventory/osinfo.js +123 -16
  51. package/lib/inventory/table.js +9 -5
  52. package/lib/inventory/toolRequirements.js +903 -0
  53. package/lib/managers/binary.js +216 -40
  54. package/lib/managers/docker.js +26 -5
  55. package/lib/server/server.js +1 -1
  56. package/lib/stages/postgen/annotationBuilder.js +97 -0
  57. package/lib/stages/postgen/auditBom.js +76 -79
  58. package/lib/stages/postgen/introspection/annotate.js +320 -0
  59. package/lib/stages/postgen/introspection/commandFacts.js +293 -0
  60. package/lib/stages/postgen/introspection/emit.js +192 -0
  61. package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
  62. package/lib/stages/postgen/introspection/markers.js +227 -0
  63. package/lib/stages/postgen/introspection/reflect.js +1214 -0
  64. package/lib/stages/postgen/introspection/report.js +1208 -0
  65. package/lib/stages/postgen/introspection/score.js +1067 -0
  66. package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
  67. package/lib/stages/postgen/parentEdge.js +156 -0
  68. package/lib/stages/postgen/postgen.js +115 -2
  69. package/lib/stages/postgen/ruleEngine.js +17 -0
  70. package/lib/stages/pregen/envAudit.js +42 -2
  71. package/lib/stages/pregen/pregen.js +1171 -6
  72. package/lib/validator/bomValidator.js +11 -2
  73. package/package.json +14 -14
  74. package/types/lib/audit/index.d.ts.map +1 -1
  75. package/types/lib/audit/scoring.d.ts +2 -8
  76. package/types/lib/audit/scoring.d.ts.map +1 -1
  77. package/types/lib/cli/bomAssembly.d.ts +14 -0
  78. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  79. package/types/lib/cli/cliOptions.d.ts +2 -1
  80. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  81. package/types/lib/cli/index.d.ts +8 -0
  82. package/types/lib/cli/index.d.ts.map +1 -1
  83. package/types/lib/cli/jsBom.d.ts.map +1 -1
  84. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  85. package/types/lib/cli/managedBom.d.ts.map +1 -1
  86. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  87. package/types/lib/core/buildLedger.d.ts +272 -0
  88. package/types/lib/core/buildLedger.d.ts.map +1 -0
  89. package/types/lib/core/deferredExit.d.ts +84 -0
  90. package/types/lib/core/deferredExit.d.ts.map +1 -0
  91. package/types/lib/core/env.d.ts +22 -1
  92. package/types/lib/core/env.d.ts.map +1 -1
  93. package/types/lib/core/fs.d.ts +21 -0
  94. package/types/lib/core/fs.d.ts.map +1 -1
  95. package/types/lib/core/logger.d.ts +14 -1
  96. package/types/lib/core/logger.d.ts.map +1 -1
  97. package/types/lib/core/severity.d.ts +11 -0
  98. package/types/lib/core/severity.d.ts.map +1 -0
  99. package/types/lib/core/syncFileWriter.d.ts +33 -0
  100. package/types/lib/core/syncFileWriter.d.ts.map +1 -0
  101. package/types/lib/core/ui.d.ts +1 -1
  102. package/types/lib/core/ui.d.ts.map +1 -1
  103. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  104. package/types/lib/ecosystems/gradleutils.d.ts +33 -0
  105. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  106. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  107. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  108. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  109. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  110. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  111. package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
  112. package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
  113. package/types/lib/ecosystems/pythonutils.d.ts +20 -0
  114. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  115. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  116. package/types/lib/ecosystems/utils.d.ts +4 -4
  117. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  118. package/types/lib/evinser/evinser.d.ts +5 -1
  119. package/types/lib/evinser/evinser.d.ts.map +1 -1
  120. package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
  121. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  122. package/types/lib/inventory/auditCategories.d.ts +8 -0
  123. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  124. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  125. package/types/lib/inventory/dosai.d.ts +20 -0
  126. package/types/lib/inventory/dosai.d.ts.map +1 -1
  127. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  128. package/types/lib/inventory/fetchBatch.d.ts +9 -0
  129. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  130. package/types/lib/inventory/jvmToolEnv.d.ts +189 -0
  131. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
  132. package/types/lib/inventory/osinfo.d.ts +83 -2
  133. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  134. package/types/lib/inventory/table.d.ts +4 -1
  135. package/types/lib/inventory/table.d.ts.map +1 -1
  136. package/types/lib/inventory/toolRequirements.d.ts +216 -0
  137. package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
  138. package/types/lib/managers/binary.d.ts +71 -0
  139. package/types/lib/managers/binary.d.ts.map +1 -1
  140. package/types/lib/managers/docker.d.ts.map +1 -1
  141. package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
  142. package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
  143. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  144. package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
  145. package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
  146. package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
  147. package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
  148. package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
  149. package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
  150. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
  151. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
  152. package/types/lib/stages/postgen/introspection/markers.d.ts +57 -0
  153. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
  154. package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
  155. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
  156. package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
  157. package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
  158. package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
  159. package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
  160. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
  161. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
  162. package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
  163. package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
  164. package/types/lib/stages/postgen/postgen.d.ts +2 -2
  165. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  166. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  167. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  168. package/types/lib/stages/pregen/pregen.d.ts +131 -0
  169. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  170. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from "node:buffer";
2
+ import { randomBytes } from "node:crypto";
2
3
 
3
4
  import { hasDangerousUnicode } from "../../core/activity.js";
4
5
 
@@ -44,26 +45,23 @@ export function getDependencyTrackBomUrl(serverUrl) {
44
45
  }
45
46
 
46
47
  /**
47
- * Build the payload for Dependency-Track BOM submission.
48
+ * Build the form fields for a Dependency-Track BOM submission.
49
+ *
50
+ * Field names follow the `multipart/form-data` variant of `/api/v1/bom`, which differs
51
+ * from the JSON variant: the JSON body calls the flag `isLatestProjectVersion` and takes
52
+ * `projectTags` as an array of objects, while the form takes `isLatest` and a
53
+ * comma-separated tag list.
48
54
  *
49
55
  * @param {Object} args CLI/server arguments
50
- * @param {Object} bomContents BOM Json
51
- * @returns {Object | undefined} payload object if project coordinates are valid
56
+ * @returns {Object | undefined} field map if the project coordinates are valid
52
57
  */
53
- export function buildDependencyTrackBomPayload(args, bomContents) {
54
- let encodedBomContents = Buffer.from(JSON.stringify(bomContents)).toString(
55
- "base64",
56
- );
57
- if (encodedBomContents.startsWith("77u/")) {
58
- encodedBomContents = encodedBomContents.substring(4);
59
- }
58
+ export function buildDependencyTrackBomPayload(args) {
60
59
  const autoCreate =
61
60
  typeof args.autoCreate === "boolean"
62
61
  ? args.autoCreate
63
62
  : args.autoCreate !== "false";
64
63
  const bomPayload = {
65
64
  autoCreate: String(autoCreate),
66
- bom: encodedBomContents,
67
65
  };
68
66
  if (
69
67
  typeof args.projectId !== "undefined" ||
@@ -103,15 +101,84 @@ export function buildDependencyTrackBomPayload(args, bomContents) {
103
101
  args.isLatest === "true" ||
104
102
  args.isLatest === "false"
105
103
  ) {
106
- bomPayload.isLatest =
104
+ bomPayload.isLatest = String(
107
105
  typeof args.isLatest === "boolean"
108
106
  ? args.isLatest
109
- : args.isLatest === "true";
107
+ : args.isLatest === "true",
108
+ );
110
109
  }
111
110
  if (typeof args.projectTag !== "undefined") {
111
+ // The server splits this field on commas, so a tag cannot itself contain one.
112
112
  bomPayload.projectTags = (
113
113
  Array.isArray(args.projectTag) ? args.projectTag : [args.projectTag]
114
- ).map((tag) => ({ name: tag }));
114
+ ).join(",");
115
115
  }
116
116
  return bomPayload;
117
117
  }
118
+
119
+ /**
120
+ * Encode text fields and a single file part as a `multipart/form-data` body.
121
+ *
122
+ * @param {Object} fields Text fields keyed by field name
123
+ * @param {Object} file File part
124
+ * @param {string} file.name Field name of the file part
125
+ * @param {string} file.filename File name reported to the server
126
+ * @param {string} file.contentType Content type of the file part
127
+ * @param {Buffer} file.content File content
128
+ * @returns {{ body: Buffer, contentType: string }} encoded body and its content type
129
+ */
130
+ export function encodeMultipartFormData(fields, file) {
131
+ // A random boundary keeps the delimiter from colliding with the BOM content.
132
+ const boundary = `--------------------------cdxgen${randomBytes(16).toString("hex")}`;
133
+ const chunks = [];
134
+ const pushPart = (header, content) => {
135
+ chunks.push(
136
+ Buffer.from(`--${boundary}\r\n${header}\r\n\r\n`),
137
+ Buffer.isBuffer(content) ? content : Buffer.from(String(content)),
138
+ Buffer.from("\r\n"),
139
+ );
140
+ };
141
+ for (const [name, value] of Object.entries(fields)) {
142
+ pushPart(`Content-Disposition: form-data; name="${name}"`, value);
143
+ }
144
+ pushPart(
145
+ `Content-Disposition: form-data; name="${file.name}"; filename="${file.filename}"\r\nContent-Type: ${file.contentType}`,
146
+ file.content,
147
+ );
148
+ chunks.push(Buffer.from(`--${boundary}--\r\n`));
149
+ return {
150
+ body: Buffer.concat(chunks),
151
+ contentType: `multipart/form-data; boundary=${boundary}`,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Build the `multipart/form-data` request for Dependency-Track BOM submission.
157
+ *
158
+ * The JSON variant of the endpoint carries the BOM as a base64 string, which Jackson
159
+ * refuses to read beyond 20,000,000 characters - roughly a 15 MB BOM, which container
160
+ * image scans exceed routinely. The form variant carries the BOM as a file part and has
161
+ * no such limit, and is what the server recommends for large uploads.
162
+ *
163
+ * @param {Object} args CLI/server arguments
164
+ * @param {Object | string} bomContents BOM Json
165
+ * @returns {{ body: Buffer, contentType: string } | undefined} request body and content type if the arguments are valid
166
+ */
167
+ export function buildDependencyTrackBomRequest(args, bomContents) {
168
+ if (!bomContents) {
169
+ return undefined;
170
+ }
171
+ const fields = buildDependencyTrackBomPayload(args);
172
+ if (!fields) {
173
+ return undefined;
174
+ }
175
+ const bomString =
176
+ typeof bomContents === "string" ? bomContents : JSON.stringify(bomContents);
177
+ return encodeMultipartFormData(fields, {
178
+ name: "bom",
179
+ filename: "bom.json",
180
+ contentType: "application/vnd.cyclonedx+json",
181
+ // The server rejects a BOM that still carries a byte order mark.
182
+ content: Buffer.from(bomString.replace(/^\ufeff/, "")),
183
+ });
184
+ }
@@ -23,6 +23,15 @@ export const GOLEM_AUDIT_CATEGORIES = Object.freeze([
23
23
  "golem-compliance",
24
24
  ]);
25
25
 
26
+ /**
27
+ * Frozen list of audit categories that never activate on their own. These
28
+ * rule packs serve a specific feature (`--introspect` for build-fidelity) and
29
+ * stay inactive unless the caller explicitly requests the category, so a
30
+ * default `--bom-audit` run - which evaluates every loaded rule - cannot pick
31
+ * them up.
32
+ */
33
+ export const OPT_IN_BOM_AUDIT_CATEGORIES = Object.freeze(["build-fidelity"]);
34
+
26
35
  /** Frozen list of AI-bill-of-materials audit categories. */
27
36
  export const AI_BOM_AUDIT_CATEGORIES = Object.freeze([
28
37
  "ai-governance",
@@ -222,6 +222,28 @@ function serviceIdentityKey(service) {
222
222
  return `${service?.group || ""}:${service?.name || ""}:${service?.version || ""}`.toLowerCase();
223
223
  }
224
224
 
225
+ /**
226
+ * Union two services[].data[] lists, keyed by flow + classification + name so that repeated
227
+ * classifications of the same payload do not accumulate duplicates.
228
+ *
229
+ * @param {Array} existingData Data entries already on the service
230
+ * @param {Array} newData Data entries being merged in
231
+ * @returns {Array} Merged data entries
232
+ */
233
+ function mergeServiceData(existingData = [], newData = []) {
234
+ const seen = new Map();
235
+ for (const entry of [...existingData, ...newData]) {
236
+ if (!entry?.flow || !entry?.classification) {
237
+ continue;
238
+ }
239
+ const key = `${entry.flow}|${entry.classification}|${entry.name || ""}`;
240
+ if (!seen.has(key)) {
241
+ seen.set(key, entry);
242
+ }
243
+ }
244
+ return Array.from(seen.values());
245
+ }
246
+
225
247
  function mergeServiceProperties(existingProps = [], newProps = []) {
226
248
  const merged = [...existingProps];
227
249
  for (const newProp of newProps) {
@@ -249,6 +271,38 @@ function normalizeServiceEndpoints(endpoints) {
249
271
  return [];
250
272
  }
251
273
 
274
+ function evidenceOccurrenceKey(occurrence) {
275
+ const location = occurrence?.location || {};
276
+ return `${location.path || ""}#${location.line ?? ""}#${location.column ?? ""}`;
277
+ }
278
+
279
+ /**
280
+ * Union two service evidence objects by their occurrences so a collision keeps
281
+ * every call site instead of whichever side arrived first.
282
+ *
283
+ * @param {Object} [existingEvidence] Evidence already on the service
284
+ * @param {Object} [newEvidence] Evidence being merged in
285
+ * @returns {Object} Evidence covering both inputs
286
+ */
287
+ function mergeServiceEvidence(existingEvidence, newEvidence) {
288
+ if (!existingEvidence) {
289
+ return newEvidence;
290
+ }
291
+ const occurrences = [...(existingEvidence.occurrences || [])];
292
+ const seen = new Set(occurrences.map(evidenceOccurrenceKey));
293
+ for (const occurrence of newEvidence?.occurrences || []) {
294
+ const key = evidenceOccurrenceKey(occurrence);
295
+ if (!seen.has(key)) {
296
+ seen.add(key);
297
+ occurrences.push(occurrence);
298
+ }
299
+ }
300
+ if (!occurrences.length) {
301
+ return existingEvidence;
302
+ }
303
+ return { ...existingEvidence, occurrences };
304
+ }
305
+
252
306
  /**
253
307
  * Merge CycloneDX services using bom-ref or group/name/version identity.
254
308
  *
@@ -313,6 +367,24 @@ export function mergeServices(services, newServices) {
313
367
  existing.properties || [],
314
368
  service.properties || [],
315
369
  );
370
+ // Carry the richer fields through a collision too. Without these, a dosai service that collided
371
+ // with one derived from endpoints lost precisely what schema 4.0.0 adds: its bom-ref, its data
372
+ // classifications, and its evidence.
373
+ existing["bom-ref"] = existing["bom-ref"] || service["bom-ref"];
374
+ if (Array.isArray(service.data) && service.data.length) {
375
+ existing.data = mergeServiceData(existing.data || [], service.data);
376
+ }
377
+ if (service.evidence) {
378
+ existing.evidence = mergeServiceEvidence(
379
+ existing.evidence,
380
+ service.evidence,
381
+ );
382
+ }
383
+ if (Array.isArray(service.tags) && service.tags.length) {
384
+ existing.tags = Array.from(
385
+ new Set([...(existing.tags || []), ...service.tags]),
386
+ );
387
+ }
316
388
  if (Array.isArray(service.services) && service.services.length) {
317
389
  existing.services = mergeServices(
318
390
  existing.services || [],