@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,452 @@
1
+ import { assert, describe, it } from "poku";
2
+
3
+ import {
4
+ collectNpmRegistryProvenanceProperties,
5
+ collectPypiRegistryProvenanceProperties,
6
+ } from "./registryProvenance.js";
7
+
8
+ function getProperty(properties, propertyName) {
9
+ return properties.find((property) => property.name === propertyName)?.value;
10
+ }
11
+
12
+ describe("collectNpmRegistryProvenanceProperties()", () => {
13
+ it("extracts trusted publishing and publisher details from npm metadata", () => {
14
+ const properties = collectNpmRegistryProvenanceProperties(
15
+ {
16
+ time: {
17
+ "1.2.0": "2025-01-01T10:00:00.000Z",
18
+ "1.2.1": "2025-01-15T10:00:00.000Z",
19
+ "1.2.2": "2026-03-01T10:00:00.000Z",
20
+ "1.2.3": "2026-04-01T10:00:00.000Z",
21
+ created: "2024-01-01T10:00:00.000Z",
22
+ modified: "2026-04-01T10:00:00.000Z",
23
+ },
24
+ versions: {
25
+ "1.2.0": {
26
+ _npmUser: {
27
+ name: "previous-publisher",
28
+ },
29
+ maintainers: [{ name: "alice" }, { email: "alice@example.com" }],
30
+ },
31
+ "1.2.1": {
32
+ _npmUser: {
33
+ name: "previous-publisher",
34
+ },
35
+ maintainers: [{ name: "alice" }, { email: "alice@example.com" }],
36
+ },
37
+ "1.2.2": {
38
+ _npmUser: {
39
+ name: "previous-publisher",
40
+ },
41
+ maintainers: [{ name: "alice" }, { email: "alice@example.com" }],
42
+ },
43
+ "1.2.3": {
44
+ _npmUser: {
45
+ email: "publisher@example.com",
46
+ name: "publisher",
47
+ },
48
+ maintainers: [{ name: "bob" }, { email: "bob@example.com" }],
49
+ dist: {
50
+ integrity: "sha512-artifact-integrity",
51
+ provenance: {
52
+ predicateType: "https://slsa.dev/provenance/v1",
53
+ signatures: [
54
+ {
55
+ keyid: "sigstore-npm-key",
56
+ sig: "MEUCIQDsig",
57
+ },
58
+ ],
59
+ subject: {
60
+ digest: {
61
+ sha256: "npm-subject-digest",
62
+ },
63
+ },
64
+ url: "https://registry.npmjs.org/-/npm/v1/attestations/example@1.2.3",
65
+ },
66
+ shasum: "deadbeefcafebabe",
67
+ },
68
+ },
69
+ },
70
+ },
71
+ "1.2.3",
72
+ );
73
+
74
+ assert.strictEqual(
75
+ getProperty(properties, "cdx:npm:trustedPublishing"),
76
+ "true",
77
+ );
78
+ assert.strictEqual(
79
+ getProperty(properties, "cdx:npm:provenanceUrl"),
80
+ "https://registry.npmjs.org/-/npm/v1/attestations/example@1.2.3",
81
+ );
82
+ assert.strictEqual(
83
+ getProperty(properties, "cdx:npm:publisher"),
84
+ "publisher",
85
+ );
86
+ assert.strictEqual(
87
+ getProperty(properties, "cdx:npm:publisherEmail"),
88
+ "publisher@example.com",
89
+ );
90
+ assert.strictEqual(
91
+ getProperty(properties, "cdx:npm:publishTime"),
92
+ "2026-04-01T10:00:00.000Z",
93
+ );
94
+ assert.strictEqual(
95
+ getProperty(properties, "cdx:npm:artifactIntegrity"),
96
+ "sha512-artifact-integrity",
97
+ );
98
+ assert.strictEqual(
99
+ getProperty(properties, "cdx:npm:artifactShasum"),
100
+ "deadbeefcafebabe",
101
+ );
102
+ assert.strictEqual(
103
+ getProperty(properties, "cdx:npm:provenanceDigest"),
104
+ "npm-subject-digest",
105
+ );
106
+ assert.strictEqual(
107
+ getProperty(properties, "cdx:npm:provenanceKeyId"),
108
+ "sigstore-npm-key",
109
+ );
110
+ assert.strictEqual(
111
+ getProperty(properties, "cdx:npm:provenancePredicateType"),
112
+ "https://slsa.dev/provenance/v1",
113
+ );
114
+ assert.strictEqual(
115
+ getProperty(properties, "cdx:npm:provenanceSignature"),
116
+ "MEUCIQDsig",
117
+ );
118
+ assert.strictEqual(getProperty(properties, "cdx:npm:versionCount"), "4");
119
+ assert.strictEqual(
120
+ getProperty(properties, "cdx:npm:priorVersion"),
121
+ "1.2.2",
122
+ );
123
+ assert.strictEqual(
124
+ getProperty(properties, "cdx:npm:priorPublisher"),
125
+ "previous-publisher",
126
+ );
127
+ assert.strictEqual(
128
+ getProperty(properties, "cdx:npm:publisherDrift"),
129
+ "true",
130
+ );
131
+ assert.strictEqual(
132
+ getProperty(properties, "cdx:npm:packageCreatedTime"),
133
+ "2024-01-01T10:00:00.000Z",
134
+ );
135
+ assert.strictEqual(
136
+ getProperty(properties, "cdx:npm:maintainerSet"),
137
+ "bob, bob@example.com, publisher, publisher@example.com",
138
+ );
139
+ assert.strictEqual(
140
+ getProperty(properties, "cdx:npm:priorMaintainerSet"),
141
+ "alice, alice@example.com, previous-publisher",
142
+ );
143
+ assert.strictEqual(
144
+ getProperty(properties, "cdx:npm:maintainerSetDrift"),
145
+ "true",
146
+ );
147
+ assert.strictEqual(
148
+ getProperty(properties, "cdx:npm:releaseGapDays"),
149
+ "31.00",
150
+ );
151
+ assert.strictEqual(
152
+ getProperty(properties, "cdx:npm:releaseGapBaselineDays"),
153
+ "212.00",
154
+ );
155
+ assert.strictEqual(
156
+ getProperty(properties, "cdx:npm:releaseGapSampleSize"),
157
+ "2",
158
+ );
159
+ });
160
+
161
+ it("extracts compressed cadence and partial maintainer overlap drift from npm metadata", () => {
162
+ const properties = collectNpmRegistryProvenanceProperties(
163
+ {
164
+ time: {
165
+ "0.9.0": "2024-11-01T10:00:00.000Z",
166
+ "1.0.0": "2025-01-01T10:00:00.000Z",
167
+ "1.1.0": "2025-03-15T10:00:00.000Z",
168
+ "1.2.0": "2025-05-27T10:00:00.000Z",
169
+ "1.2.1": "2025-06-05T10:00:00.000Z",
170
+ created: "2024-01-01T10:00:00.000Z",
171
+ modified: "2025-06-05T10:00:00.000Z",
172
+ },
173
+ versions: {
174
+ "0.9.0": {
175
+ _npmUser: {
176
+ name: "alice",
177
+ },
178
+ },
179
+ "1.0.0": {
180
+ _npmUser: {
181
+ name: "alice",
182
+ },
183
+ },
184
+ "1.1.0": {
185
+ _npmUser: {
186
+ name: "alice",
187
+ },
188
+ },
189
+ "1.2.0": {
190
+ _npmUser: {
191
+ name: "bob",
192
+ },
193
+ maintainers: [{ name: "alice" }],
194
+ },
195
+ "1.2.1": {
196
+ _npmUser: {
197
+ name: "bob",
198
+ },
199
+ maintainers: [{ name: "charlie" }],
200
+ },
201
+ },
202
+ },
203
+ "1.2.1",
204
+ );
205
+
206
+ assert.strictEqual(
207
+ getProperty(properties, "cdx:npm:maintainerSetPartialDrift"),
208
+ "true",
209
+ );
210
+ assert.strictEqual(
211
+ getProperty(properties, "cdx:npm:maintainerOverlapCount"),
212
+ "1",
213
+ );
214
+ assert.strictEqual(
215
+ getProperty(properties, "cdx:npm:maintainerOverlapRatio"),
216
+ "0.33",
217
+ );
218
+ assert.strictEqual(
219
+ getProperty(properties, "cdx:npm:compressedCadence"),
220
+ "true",
221
+ );
222
+ assert.strictEqual(
223
+ getProperty(properties, "cdx:npm:releaseCadenceCompressionRatio"),
224
+ "0.12",
225
+ );
226
+ assert.strictEqual(
227
+ getProperty(properties, "cdx:npm:maintainerSetDrift"),
228
+ undefined,
229
+ );
230
+ });
231
+ });
232
+
233
+ describe("collectPypiRegistryProvenanceProperties()", () => {
234
+ it("extracts trusted publishing and uploader details from PyPI metadata", () => {
235
+ const properties = collectPypiRegistryProvenanceProperties(
236
+ {
237
+ releases: {
238
+ "1.7.0": [
239
+ {
240
+ upload_time_iso_8601: "2025-11-20T08:15:30.000Z",
241
+ uploader: "previous-uploader",
242
+ },
243
+ ],
244
+ "1.8.0": [
245
+ {
246
+ upload_time_iso_8601: "2025-12-05T08:15:30.000Z",
247
+ uploader: "previous-uploader",
248
+ },
249
+ ],
250
+ "1.9.0": [
251
+ {
252
+ upload_time_iso_8601: "2025-12-20T08:15:30.000Z",
253
+ uploader: "previous-uploader",
254
+ },
255
+ ],
256
+ "2.0.0": [
257
+ {
258
+ digests: {
259
+ blake2b_256: "pypi-blake",
260
+ sha256: "pypi-sha256",
261
+ },
262
+ md5_digest: "pypi-md5",
263
+ provenance: {
264
+ predicateType: "https://docs.pypi.org/attestations/publish/v1",
265
+ signatures: [
266
+ {
267
+ keyid: "sigstore-pypi-key",
268
+ sig: "c2lnbmF0dXJl",
269
+ },
270
+ ],
271
+ subject: {
272
+ digest: {
273
+ sha256: "pypi-provenance-digest",
274
+ },
275
+ },
276
+ },
277
+ provenance_url:
278
+ "https://pypi.org/integrity/example/2.0.0/example-2.0.0.tar.gz/provenance",
279
+ upload_time_iso_8601: "2026-03-20T08:15:30.000Z",
280
+ uploader: "trusted-publisher",
281
+ uploader_verified: true,
282
+ },
283
+ ],
284
+ },
285
+ },
286
+ "2.0.0",
287
+ );
288
+
289
+ assert.strictEqual(
290
+ getProperty(properties, "cdx:pypi:trustedPublishing"),
291
+ "true",
292
+ );
293
+ assert.strictEqual(
294
+ getProperty(properties, "cdx:pypi:provenanceUrl"),
295
+ "https://pypi.org/integrity/example/2.0.0/example-2.0.0.tar.gz/provenance",
296
+ );
297
+ assert.strictEqual(
298
+ getProperty(properties, "cdx:pypi:publishTime"),
299
+ "2026-03-20T08:15:30.000Z",
300
+ );
301
+ assert.strictEqual(
302
+ getProperty(properties, "cdx:pypi:publisher"),
303
+ "trusted-publisher",
304
+ );
305
+ assert.strictEqual(
306
+ getProperty(properties, "cdx:pypi:uploaderVerified"),
307
+ "true",
308
+ );
309
+ assert.strictEqual(
310
+ getProperty(properties, "cdx:pypi:artifactDigestSha256"),
311
+ "pypi-sha256",
312
+ );
313
+ assert.strictEqual(
314
+ getProperty(properties, "cdx:pypi:artifactDigestBlake2b256"),
315
+ "pypi-blake",
316
+ );
317
+ assert.strictEqual(
318
+ getProperty(properties, "cdx:pypi:artifactDigestMd5"),
319
+ "pypi-md5",
320
+ );
321
+ assert.strictEqual(
322
+ getProperty(properties, "cdx:pypi:provenanceDigest"),
323
+ "pypi-provenance-digest",
324
+ );
325
+ assert.strictEqual(
326
+ getProperty(properties, "cdx:pypi:provenanceKeyId"),
327
+ "sigstore-pypi-key",
328
+ );
329
+ assert.strictEqual(
330
+ getProperty(properties, "cdx:pypi:provenancePredicateType"),
331
+ "https://docs.pypi.org/attestations/publish/v1",
332
+ );
333
+ assert.strictEqual(
334
+ getProperty(properties, "cdx:pypi:provenanceSignature"),
335
+ "c2lnbmF0dXJl",
336
+ );
337
+ assert.strictEqual(getProperty(properties, "cdx:pypi:versionCount"), "4");
338
+ assert.strictEqual(
339
+ getProperty(properties, "cdx:pypi:priorVersion"),
340
+ "1.9.0",
341
+ );
342
+ assert.strictEqual(
343
+ getProperty(properties, "cdx:pypi:priorPublisher"),
344
+ "previous-uploader",
345
+ );
346
+ assert.strictEqual(
347
+ getProperty(properties, "cdx:pypi:publisherDrift"),
348
+ "true",
349
+ );
350
+ assert.strictEqual(
351
+ getProperty(properties, "cdx:pypi:packageCreatedTime"),
352
+ "2025-11-20T08:15:30.000Z",
353
+ );
354
+ assert.strictEqual(
355
+ getProperty(properties, "cdx:pypi:uploaderSet"),
356
+ "trusted-publisher",
357
+ );
358
+ assert.strictEqual(
359
+ getProperty(properties, "cdx:pypi:priorUploaderSet"),
360
+ "previous-uploader",
361
+ );
362
+ assert.strictEqual(
363
+ getProperty(properties, "cdx:pypi:uploaderSetDrift"),
364
+ "true",
365
+ );
366
+ assert.strictEqual(
367
+ getProperty(properties, "cdx:pypi:releaseGapDays"),
368
+ "90.00",
369
+ );
370
+ assert.strictEqual(
371
+ getProperty(properties, "cdx:pypi:releaseGapBaselineDays"),
372
+ "15.00",
373
+ );
374
+ assert.strictEqual(
375
+ getProperty(properties, "cdx:pypi:releaseGapSampleSize"),
376
+ "2",
377
+ );
378
+ });
379
+
380
+ it("extracts compressed cadence and partial uploader overlap drift from PyPI metadata", () => {
381
+ const properties = collectPypiRegistryProvenanceProperties(
382
+ {
383
+ releases: {
384
+ "0.9.0": [
385
+ {
386
+ upload_time_iso_8601: "2024-11-01T08:15:30.000Z",
387
+ uploader: "alice",
388
+ },
389
+ ],
390
+ "1.0.0": [
391
+ {
392
+ upload_time_iso_8601: "2025-01-01T08:15:30.000Z",
393
+ uploader: "alice",
394
+ },
395
+ ],
396
+ "1.1.0": [
397
+ {
398
+ upload_time_iso_8601: "2025-03-15T08:15:30.000Z",
399
+ uploader: "alice",
400
+ },
401
+ ],
402
+ "1.2.0": [
403
+ {
404
+ upload_time_iso_8601: "2025-05-27T08:15:30.000Z",
405
+ uploader: "alice",
406
+ },
407
+ {
408
+ upload_time_iso_8601: "2025-05-27T08:16:30.000Z",
409
+ uploader: "bob",
410
+ },
411
+ ],
412
+ "1.2.1": [
413
+ {
414
+ upload_time_iso_8601: "2025-06-05T08:15:30.000Z",
415
+ uploader: "bob",
416
+ },
417
+ {
418
+ upload_time_iso_8601: "2025-06-05T08:16:30.000Z",
419
+ uploader: "charlie",
420
+ },
421
+ ],
422
+ },
423
+ },
424
+ "1.2.1",
425
+ );
426
+
427
+ assert.strictEqual(
428
+ getProperty(properties, "cdx:pypi:uploaderSetPartialDrift"),
429
+ "true",
430
+ );
431
+ assert.strictEqual(
432
+ getProperty(properties, "cdx:pypi:uploaderOverlapCount"),
433
+ "1",
434
+ );
435
+ assert.strictEqual(
436
+ getProperty(properties, "cdx:pypi:uploaderOverlapRatio"),
437
+ "0.33",
438
+ );
439
+ assert.strictEqual(
440
+ getProperty(properties, "cdx:pypi:compressedCadence"),
441
+ "true",
442
+ );
443
+ assert.strictEqual(
444
+ getProperty(properties, "cdx:pypi:releaseCadenceCompressionRatio"),
445
+ "0.12",
446
+ );
447
+ assert.strictEqual(
448
+ getProperty(properties, "cdx:pypi:uploaderSetDrift"),
449
+ undefined,
450
+ );
451
+ });
452
+ });
@@ -0,0 +1,84 @@
1
+ import { Buffer } from "node:buffer";
2
+
3
+ /**
4
+ * Returns the Dependency-Track BOM API URL.
5
+ *
6
+ * @param {string} serverUrl Dependency-Track server URL
7
+ * @returns {string} API URL to submit BOM payload
8
+ */
9
+ export function getDependencyTrackBomUrl(serverUrl) {
10
+ return `${serverUrl.replace(/\/$/, "")}/api/v1/bom`;
11
+ }
12
+
13
+ /**
14
+ * Build the payload for Dependency-Track BOM submission.
15
+ *
16
+ * @param {Object} args CLI/server arguments
17
+ * @param {Object} bomContents BOM Json
18
+ * @returns {Object | undefined} payload object if project coordinates are valid
19
+ */
20
+ export function buildDependencyTrackBomPayload(args, bomContents) {
21
+ let encodedBomContents = Buffer.from(JSON.stringify(bomContents)).toString(
22
+ "base64",
23
+ );
24
+ if (encodedBomContents.startsWith("77u/")) {
25
+ encodedBomContents = encodedBomContents.substring(4);
26
+ }
27
+ const autoCreate =
28
+ typeof args.autoCreate === "boolean"
29
+ ? args.autoCreate
30
+ : args.autoCreate !== "false";
31
+ const bomPayload = {
32
+ autoCreate: String(autoCreate),
33
+ bom: encodedBomContents,
34
+ };
35
+ if (
36
+ typeof args.projectId !== "undefined" ||
37
+ typeof args.projectName !== "undefined"
38
+ ) {
39
+ if (typeof args.projectId !== "undefined") {
40
+ bomPayload.project = args.projectId;
41
+ }
42
+ if (typeof args.projectName !== "undefined") {
43
+ bomPayload.projectName = args.projectName;
44
+ }
45
+ // Dependency-Track submissions use "main" as fallback when no version is provided.
46
+ bomPayload.projectVersion = args.projectVersion || "main";
47
+ } else {
48
+ return undefined;
49
+ }
50
+ const parentProjectId = args.parentProjectId || args.parentUUID;
51
+ const hasParentUuidMode = typeof parentProjectId !== "undefined";
52
+ const hasParentName = typeof args.parentProjectName !== "undefined";
53
+ const hasParentVersion = typeof args.parentProjectVersion !== "undefined";
54
+ const hasParentCoordsMode = hasParentName || hasParentVersion;
55
+ if (hasParentUuidMode && hasParentCoordsMode) {
56
+ return undefined;
57
+ }
58
+ if (!hasParentUuidMode && hasParentName !== hasParentVersion) {
59
+ return undefined;
60
+ }
61
+ if (hasParentUuidMode) {
62
+ bomPayload.parentUUID = parentProjectId;
63
+ }
64
+ if (hasParentName && hasParentVersion) {
65
+ bomPayload.parentName = args.parentProjectName;
66
+ bomPayload.parentVersion = args.parentProjectVersion;
67
+ }
68
+ if (
69
+ typeof args.isLatest === "boolean" ||
70
+ args.isLatest === "true" ||
71
+ args.isLatest === "false"
72
+ ) {
73
+ bomPayload.isLatest =
74
+ typeof args.isLatest === "boolean"
75
+ ? args.isLatest
76
+ : args.isLatest === "true";
77
+ }
78
+ if (typeof args.projectTag !== "undefined") {
79
+ bomPayload.projectTags = (
80
+ Array.isArray(args.projectTag) ? args.projectTag : [args.projectTag]
81
+ ).map((tag) => ({ name: tag }));
82
+ }
83
+ return bomPayload;
84
+ }
@@ -0,0 +1,119 @@
1
+ import { assert, describe, it } from "poku";
2
+
3
+ import {
4
+ buildDependencyTrackBomPayload,
5
+ getDependencyTrackBomUrl,
6
+ } from "./dependency-track.js";
7
+
8
+ describe("Dependency-Track helper tests", () => {
9
+ it("returns submission URL without trailing slash duplication", () => {
10
+ assert.strictEqual(
11
+ getDependencyTrackBomUrl("https://dtrack.example.com/"),
12
+ "https://dtrack.example.com/api/v1/bom",
13
+ );
14
+ assert.strictEqual(
15
+ getDependencyTrackBomUrl("https://dtrack.example.com"),
16
+ "https://dtrack.example.com/api/v1/bom",
17
+ );
18
+ });
19
+
20
+ it("builds payload with parentUUID and tags", () => {
21
+ const payload = buildDependencyTrackBomPayload(
22
+ {
23
+ projectName: "child",
24
+ projectVersion: "1.0.0",
25
+ parentProjectId: "d9628844-5f04-4ca7-88a2-64eb6bc64db0",
26
+ projectTag: ["tag1", "tag2"],
27
+ },
28
+ { bom: "test" },
29
+ );
30
+ assert.deepStrictEqual(payload, {
31
+ autoCreate: "true",
32
+ bom: "eyJib20iOiJ0ZXN0In0=",
33
+ parentUUID: "d9628844-5f04-4ca7-88a2-64eb6bc64db0",
34
+ projectName: "child",
35
+ projectTags: [{ name: "tag1" }, { name: "tag2" }],
36
+ projectVersion: "1.0.0",
37
+ });
38
+ });
39
+
40
+ it("builds payload with parentName and parentVersion", () => {
41
+ const payload = buildDependencyTrackBomPayload(
42
+ {
43
+ projectName: "child",
44
+ projectVersion: "1.0.0",
45
+ parentProjectName: "parent",
46
+ parentProjectVersion: "2.0.0",
47
+ },
48
+ { bom: "test2" },
49
+ );
50
+ assert.deepStrictEqual(payload, {
51
+ autoCreate: "true",
52
+ bom: "eyJib20iOiJ0ZXN0MiJ9",
53
+ parentName: "parent",
54
+ parentVersion: "2.0.0",
55
+ projectName: "child",
56
+ projectVersion: "1.0.0",
57
+ });
58
+ });
59
+
60
+ it("returns undefined when project identity is missing", () => {
61
+ const payload = buildDependencyTrackBomPayload({}, { bom: "test3" });
62
+ assert.strictEqual(payload, undefined);
63
+ });
64
+
65
+ it("supports configurable autoCreate and isLatest", () => {
66
+ const payload = buildDependencyTrackBomPayload(
67
+ {
68
+ autoCreate: false,
69
+ isLatest: true,
70
+ projectName: "child",
71
+ },
72
+ { bom: "test4" },
73
+ );
74
+ assert.deepStrictEqual(payload, {
75
+ autoCreate: "false",
76
+ bom: "eyJib20iOiJ0ZXN0NCJ9",
77
+ isLatest: true,
78
+ projectName: "child",
79
+ projectVersion: "main",
80
+ });
81
+ });
82
+
83
+ it("defaults projectVersion to main when only projectName is provided", () => {
84
+ const payload = buildDependencyTrackBomPayload(
85
+ { projectName: "child" },
86
+ { bom: "test5" },
87
+ );
88
+ assert.deepStrictEqual(payload, {
89
+ autoCreate: "true",
90
+ bom: "eyJib20iOiJ0ZXN0NSJ9",
91
+ projectName: "child",
92
+ projectVersion: "main",
93
+ });
94
+ });
95
+
96
+ it("returns undefined when parent UUID and parent name/version are both provided", () => {
97
+ const payload = buildDependencyTrackBomPayload(
98
+ {
99
+ parentProjectId: "d9628844-5f04-4ca7-88a2-64eb6bc64db0",
100
+ parentProjectName: "parent",
101
+ parentProjectVersion: "1.0.0",
102
+ projectName: "child",
103
+ },
104
+ { bom: "test6" },
105
+ );
106
+ assert.strictEqual(payload, undefined);
107
+ });
108
+
109
+ it("returns undefined when parent name/version mode is incomplete", () => {
110
+ const payload = buildDependencyTrackBomPayload(
111
+ {
112
+ parentProjectName: "parent",
113
+ projectName: "child",
114
+ },
115
+ { bom: "test7" },
116
+ );
117
+ assert.strictEqual(payload, undefined);
118
+ });
119
+ });