@cyclonedx/cdxgen 12.3.0 → 12.3.2

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 (121) hide show
  1. package/README.md +15 -5
  2. package/bin/audit.js +7 -0
  3. package/bin/cdxgen.js +241 -81
  4. package/bin/repl.js +138 -0
  5. package/data/rules/ai-agent-governance.yaml +249 -0
  6. package/data/rules/dependency-sources.yaml +41 -0
  7. package/data/rules/mcp-servers.yaml +304 -0
  8. package/data/rules/package-integrity.yaml +123 -0
  9. package/lib/audit/index.js +353 -29
  10. package/lib/audit/index.poku.js +247 -7
  11. package/lib/audit/reporters.js +26 -0
  12. package/lib/audit/scoring.js +262 -13
  13. package/lib/audit/scoring.poku.js +179 -0
  14. package/lib/audit/targets.js +391 -2
  15. package/lib/audit/targets.poku.js +416 -3
  16. package/lib/cli/index.js +588 -45
  17. package/lib/cli/index.poku.js +735 -1
  18. package/lib/evinser/evinser.js +8 -5
  19. package/lib/helpers/agentFormulationParser.js +318 -0
  20. package/lib/helpers/aiInventory.js +262 -0
  21. package/lib/helpers/aiInventory.poku.js +111 -0
  22. package/lib/helpers/analyzer.js +1769 -0
  23. package/lib/helpers/analyzer.poku.js +284 -3
  24. package/lib/helpers/auditCategories.js +76 -0
  25. package/lib/helpers/ciParsers/githubActions.js +140 -16
  26. package/lib/helpers/ciParsers/githubActions.poku.js +110 -0
  27. package/lib/helpers/communityAiConfigParser.js +672 -0
  28. package/lib/helpers/communityAiConfigParser.poku.js +63 -0
  29. package/lib/helpers/depsUtils.js +108 -0
  30. package/lib/helpers/depsUtils.poku.js +72 -1
  31. package/lib/helpers/display.js +325 -3
  32. package/lib/helpers/display.poku.js +301 -0
  33. package/lib/helpers/formulationParsers.js +28 -0
  34. package/lib/helpers/formulationParsers.poku.js +504 -1
  35. package/lib/helpers/jsonLike.js +102 -0
  36. package/lib/helpers/jsonLike.poku.js +34 -0
  37. package/lib/helpers/mcp.js +248 -0
  38. package/lib/helpers/mcp.poku.js +101 -0
  39. package/lib/helpers/mcpConfigParser.js +656 -0
  40. package/lib/helpers/mcpConfigParser.poku.js +126 -0
  41. package/lib/helpers/mcpDiscovery.js +84 -0
  42. package/lib/helpers/mcpDiscovery.poku.js +21 -0
  43. package/lib/helpers/protobom.js +3 -3
  44. package/lib/helpers/provenanceUtils.js +29 -4
  45. package/lib/helpers/provenanceUtils.poku.js +29 -3
  46. package/lib/helpers/registryProvenance.js +210 -0
  47. package/lib/helpers/registryProvenance.poku.js +144 -0
  48. package/lib/helpers/rustFormulationParser.js +330 -0
  49. package/lib/helpers/source.js +21 -2
  50. package/lib/helpers/source.poku.js +38 -0
  51. package/lib/helpers/utils.js +1331 -83
  52. package/lib/helpers/utils.poku.js +599 -188
  53. package/lib/helpers/vsixutils.js +12 -4
  54. package/lib/helpers/vsixutils.poku.js +34 -0
  55. package/lib/managers/binary.js +36 -12
  56. package/lib/managers/binary.poku.js +68 -0
  57. package/lib/managers/docker.js +59 -9
  58. package/lib/managers/docker.poku.js +61 -0
  59. package/lib/managers/piptree.js +12 -7
  60. package/lib/managers/piptree.poku.js +44 -0
  61. package/lib/stages/postgen/annotator.js +2 -1
  62. package/lib/stages/postgen/annotator.poku.js +15 -0
  63. package/lib/stages/postgen/auditBom.js +20 -6
  64. package/lib/stages/postgen/auditBom.poku.js +694 -1
  65. package/lib/stages/postgen/postgen.js +262 -11
  66. package/lib/stages/postgen/postgen.poku.js +306 -2
  67. package/lib/stages/postgen/ruleEngine.js +49 -1
  68. package/lib/stages/postgen/spdxConverter.poku.js +70 -0
  69. package/lib/stages/pregen/pregen.js +6 -4
  70. package/package.json +1 -1
  71. package/types/bin/repl.d.ts.map +1 -1
  72. package/types/lib/audit/index.d.ts.map +1 -1
  73. package/types/lib/audit/reporters.d.ts.map +1 -1
  74. package/types/lib/audit/scoring.d.ts.map +1 -1
  75. package/types/lib/audit/targets.d.ts +12 -0
  76. package/types/lib/audit/targets.d.ts.map +1 -1
  77. package/types/lib/cli/index.d.ts +2 -8
  78. package/types/lib/cli/index.d.ts.map +1 -1
  79. package/types/lib/evinser/evinser.d.ts.map +1 -1
  80. package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
  81. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
  82. package/types/lib/helpers/aiInventory.d.ts +23 -0
  83. package/types/lib/helpers/aiInventory.d.ts.map +1 -0
  84. package/types/lib/helpers/analyzer.d.ts +10 -0
  85. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  86. package/types/lib/helpers/auditCategories.d.ts +12 -0
  87. package/types/lib/helpers/auditCategories.d.ts.map +1 -0
  88. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  89. package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
  90. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
  91. package/types/lib/helpers/depsUtils.d.ts +8 -0
  92. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  93. package/types/lib/helpers/display.d.ts +17 -1
  94. package/types/lib/helpers/display.d.ts.map +1 -1
  95. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  96. package/types/lib/helpers/jsonLike.d.ts +4 -0
  97. package/types/lib/helpers/jsonLike.d.ts.map +1 -0
  98. package/types/lib/helpers/mcp.d.ts +29 -0
  99. package/types/lib/helpers/mcp.d.ts.map +1 -0
  100. package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
  101. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
  102. package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
  103. package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
  104. package/types/lib/helpers/provenanceUtils.d.ts +5 -3
  105. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -1
  106. package/types/lib/helpers/registryProvenance.d.ts +9 -0
  107. package/types/lib/helpers/registryProvenance.d.ts.map +1 -1
  108. package/types/lib/helpers/rustFormulationParser.d.ts +17 -0
  109. package/types/lib/helpers/rustFormulationParser.d.ts.map +1 -0
  110. package/types/lib/helpers/source.d.ts.map +1 -1
  111. package/types/lib/helpers/utils.d.ts +31 -1
  112. package/types/lib/helpers/utils.d.ts.map +1 -1
  113. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  114. package/types/lib/managers/binary.d.ts.map +1 -1
  115. package/types/lib/managers/docker.d.ts.map +1 -1
  116. package/types/lib/managers/piptree.d.ts.map +1 -1
  117. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  118. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  119. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  120. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  121. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
@@ -1,6 +1,7 @@
1
1
  import { assert, describe, it } from "poku";
2
2
 
3
3
  import {
4
+ collectCargoRegistryProvenanceProperties,
4
5
  collectNpmRegistryProvenanceProperties,
5
6
  collectPypiRegistryProvenanceProperties,
6
7
  } from "./registryProvenance.js";
@@ -450,3 +451,146 @@ describe("collectPypiRegistryProvenanceProperties()", () => {
450
451
  );
451
452
  });
452
453
  });
454
+
455
+ describe("collectCargoRegistryProvenanceProperties()", () => {
456
+ it("extracts Cargo publisher, release cadence, artifact, and trusted-publishing details", () => {
457
+ const properties = collectCargoRegistryProvenanceProperties(
458
+ {
459
+ crate: {
460
+ trustpub_only: true,
461
+ },
462
+ versions: [
463
+ {
464
+ num: "0.9.0",
465
+ created_at: "2025-01-01T10:00:00.000Z",
466
+ published_by: { login: "previous-publisher" },
467
+ },
468
+ {
469
+ num: "1.0.0",
470
+ created_at: "2025-03-01T10:00:00.000Z",
471
+ published_by: { login: "previous-publisher" },
472
+ },
473
+ {
474
+ num: "1.1.0",
475
+ created_at: "2025-05-15T10:00:00.000Z",
476
+ published_by: { login: "previous-publisher" },
477
+ },
478
+ {
479
+ num: "1.2.0",
480
+ created_at: "2025-06-05T10:00:00.000Z",
481
+ checksum: "cargo-sha256",
482
+ crate_size: 4242,
483
+ edition: "2021",
484
+ has_lib: true,
485
+ bin_names: ["cargo-demo"],
486
+ yanked: true,
487
+ published_by: {
488
+ login: "publisher",
489
+ name: "Publisher Name",
490
+ },
491
+ trustpub_data: {
492
+ predicateType: "https://slsa.dev/provenance/v1",
493
+ signatures: [
494
+ { keyid: "sigstore-cargo-key", sig: "cargo-signature" },
495
+ ],
496
+ subject: {
497
+ digest: {
498
+ sha256: "cargo-subject-digest",
499
+ },
500
+ },
501
+ url: "https://crates.io/provenance/cargo-demo/1.2.0",
502
+ },
503
+ },
504
+ ],
505
+ },
506
+ "1.2.0",
507
+ {
508
+ users: [
509
+ { login: "publisher", name: "Publisher Name" },
510
+ { login: "owner-two", name: "Owner Two" },
511
+ ],
512
+ },
513
+ );
514
+
515
+ assert.strictEqual(
516
+ getProperty(properties, "cdx:cargo:trustedPublishing"),
517
+ "true",
518
+ );
519
+ assert.strictEqual(
520
+ getProperty(properties, "cdx:cargo:publishTime"),
521
+ "2025-06-05T10:00:00.000Z",
522
+ );
523
+ assert.strictEqual(
524
+ getProperty(properties, "cdx:cargo:publisher"),
525
+ "publisher",
526
+ );
527
+ assert.strictEqual(
528
+ getProperty(properties, "cdx:cargo:priorPublisher"),
529
+ "previous-publisher",
530
+ );
531
+ assert.strictEqual(
532
+ getProperty(properties, "cdx:cargo:publisherDrift"),
533
+ "true",
534
+ );
535
+ assert.strictEqual(
536
+ getProperty(properties, "cdx:cargo:publisherSet"),
537
+ "publisher, publisher name",
538
+ );
539
+ assert.strictEqual(
540
+ getProperty(properties, "cdx:cargo:ownerSet"),
541
+ "publisher, publisher name, owner-two, owner two",
542
+ );
543
+ assert.strictEqual(
544
+ getProperty(properties, "cdx:cargo:artifactDigestSha256"),
545
+ "cargo-sha256",
546
+ );
547
+ assert.strictEqual(getProperty(properties, "cdx:cargo:yanked"), "true");
548
+ assert.strictEqual(getProperty(properties, "cdx:cargo:edition"), "2021");
549
+ assert.strictEqual(getProperty(properties, "cdx:cargo:hasLib"), "true");
550
+ assert.strictEqual(
551
+ getProperty(properties, "cdx:cargo:binNames"),
552
+ "cargo-demo",
553
+ );
554
+ assert.strictEqual(
555
+ getProperty(properties, "cdx:cargo:provenanceUrl"),
556
+ "https://crates.io/provenance/cargo-demo/1.2.0",
557
+ );
558
+ assert.strictEqual(
559
+ getProperty(properties, "cdx:cargo:provenanceDigest"),
560
+ "cargo-subject-digest",
561
+ );
562
+ assert.strictEqual(
563
+ getProperty(properties, "cdx:cargo:provenanceKeyId"),
564
+ "sigstore-cargo-key",
565
+ );
566
+ assert.strictEqual(
567
+ getProperty(properties, "cdx:cargo:provenanceSignature"),
568
+ "cargo-signature",
569
+ );
570
+ assert.strictEqual(
571
+ getProperty(properties, "cdx:cargo:provenancePredicateType"),
572
+ "https://slsa.dev/provenance/v1",
573
+ );
574
+ assert.strictEqual(getProperty(properties, "cdx:cargo:versionCount"), "4");
575
+ assert.strictEqual(
576
+ getProperty(properties, "cdx:cargo:priorVersion"),
577
+ "1.1.0",
578
+ );
579
+ assert.strictEqual(
580
+ getProperty(properties, "cdx:cargo:releaseGapDays"),
581
+ "21.00",
582
+ );
583
+ assert.strictEqual(
584
+ getProperty(properties, "cdx:cargo:releaseGapBaselineDays"),
585
+ "67.00",
586
+ );
587
+ assert.strictEqual(
588
+ getProperty(properties, "cdx:cargo:releaseGapSampleSize"),
589
+ "2",
590
+ );
591
+ assert.strictEqual(
592
+ getProperty(properties, "cdx:cargo:compressedCadence"),
593
+ undefined,
594
+ );
595
+ });
596
+ });
@@ -0,0 +1,330 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { basename, dirname, join } from "node:path";
3
+
4
+ import toml from "@iarna/toml";
5
+
6
+ import { safeExistsSync } from "./utils.js";
7
+
8
+ // Keep this list conservative and high-signal: it is meant to surface common
9
+ // Rust native build helpers for audit triage, not to exhaustively model every
10
+ // crate that may participate in a native build pipeline.
11
+ const NATIVE_BUILD_DEPENDENCIES = [
12
+ "autocfg",
13
+ "bindgen",
14
+ "cc",
15
+ "cbindgen",
16
+ "cmake",
17
+ "cxx-build",
18
+ "grpcio-compiler",
19
+ "nasm-rs",
20
+ "openssl-sys",
21
+ "pkg-config",
22
+ "prost-build",
23
+ "protobuf-src",
24
+ "pyo3-build-config",
25
+ "ring",
26
+ "tauri-build",
27
+ "tonic-build",
28
+ ];
29
+
30
+ const BUILD_SCRIPT_CAPABILITY_PATTERNS = [
31
+ ["process-execution", /\b(?:std::process::Command|Command::new|duct::cmd)\b/],
32
+ [
33
+ "native-tooling",
34
+ /\b(?:cc::Build|bindgen::Builder|cmake::Config|pkg_config::Config|autocfg::)/,
35
+ ],
36
+ [
37
+ "linker-directives",
38
+ /cargo:rustc-link-(?:lib|search|arg|arg-bins|arg-bin|arg-tests|arg-examples)/,
39
+ ],
40
+ [
41
+ "file-generation",
42
+ /\b(?:std::fs::(?:write|copy|create_dir_all)|include_bytes!|include_str!)\b/,
43
+ ],
44
+ ["network-access", /\b(?:reqwest|ureq|curl|git2)\b/],
45
+ ];
46
+
47
+ function appendProperty(properties, name, value) {
48
+ if (!name || value === undefined || value === null || value === "") {
49
+ return;
50
+ }
51
+ properties.push({
52
+ name,
53
+ value: typeof value === "string" ? value : String(value),
54
+ });
55
+ }
56
+
57
+ function createFormulationComponent(name, filePath, toolName) {
58
+ return {
59
+ type: "application",
60
+ name,
61
+ version: "config",
62
+ "bom-ref": `urn:cdxgen:formulation:${toolName}:${filePath}`,
63
+ properties: [{ name: "SrcFile", value: filePath }],
64
+ };
65
+ }
66
+
67
+ function parseCargoManifest(filePath) {
68
+ let cargoData;
69
+ try {
70
+ cargoData = toml.parse(readFileSync(filePath, { encoding: "utf-8" }));
71
+ } catch {
72
+ return undefined;
73
+ }
74
+ if (!cargoData || typeof cargoData !== "object") {
75
+ return undefined;
76
+ }
77
+ const packageName =
78
+ cargoData?.package?.name || basename(dirname(filePath)) || "cargo-project";
79
+ const component = createFormulationComponent(packageName, filePath, "cargo");
80
+ const properties = component.properties;
81
+ const buildDependencies = cargoData?.["build-dependencies"];
82
+ const devDependencies = cargoData?.["dev-dependencies"];
83
+ const targetBlocks =
84
+ cargoData?.target && typeof cargoData.target === "object"
85
+ ? Object.values(cargoData.target)
86
+ : [];
87
+ const targetDependencyBlocks = targetBlocks.filter(
88
+ (block) => block && typeof block === "object",
89
+ );
90
+ const packageNode = cargoData?.package || {};
91
+ const buildScriptPath =
92
+ typeof packageNode.build === "string"
93
+ ? join(dirname(filePath), packageNode.build)
94
+ : join(dirname(filePath), "build.rs");
95
+ const hasBuildScript =
96
+ typeof packageNode.build === "string" || safeExistsSync(buildScriptPath);
97
+ const buildDependencyNames = Object.keys(buildDependencies || {});
98
+ const targetBuildDependencyNames = targetDependencyBlocks.flatMap((block) =>
99
+ Object.keys(block?.["build-dependencies"] || {}),
100
+ );
101
+ const allBuildDependencyNames = [
102
+ ...new Set([...buildDependencyNames, ...targetBuildDependencyNames]),
103
+ ];
104
+ const nativeBuildIndicators = buildDependencyNames.filter((dependencyName) =>
105
+ NATIVE_BUILD_DEPENDENCIES.includes(dependencyName),
106
+ );
107
+ const targetNativeBuildIndicators = targetBuildDependencyNames.filter(
108
+ (dependencyName) =>
109
+ NATIVE_BUILD_DEPENDENCIES.includes(dependencyName) ||
110
+ dependencyName.endsWith("-sys"),
111
+ );
112
+ const expandedNativeBuildIndicators = [
113
+ ...new Set([
114
+ ...nativeBuildIndicators,
115
+ ...targetNativeBuildIndicators,
116
+ ...allBuildDependencyNames.filter((dependencyName) =>
117
+ dependencyName.endsWith("-sys"),
118
+ ),
119
+ ]),
120
+ ];
121
+ const releaseProfiles = Object.keys(cargoData?.profile || {});
122
+ let buildScriptCapabilities = [];
123
+ let buildScriptIndicators = [];
124
+ if (hasBuildScript) {
125
+ try {
126
+ const buildScriptContent = readFileSync(buildScriptPath, {
127
+ encoding: "utf-8",
128
+ });
129
+ buildScriptCapabilities = BUILD_SCRIPT_CAPABILITY_PATTERNS.filter(
130
+ ([, pattern]) => pattern.test(buildScriptContent),
131
+ ).map(([capability]) => capability);
132
+ if (/println!\s*\(\s*"cargo:/.test(buildScriptContent)) {
133
+ buildScriptIndicators.push("cargo-directives");
134
+ }
135
+ if (/include_bytes!|include_str!/.test(buildScriptContent)) {
136
+ buildScriptIndicators.push("embedded-assets");
137
+ }
138
+ } catch {
139
+ buildScriptCapabilities = [];
140
+ buildScriptIndicators = [];
141
+ }
142
+ }
143
+
144
+ appendProperty(properties, "cdx:rust:buildTool", "cargo");
145
+ appendProperty(
146
+ properties,
147
+ "cdx:cargo:manifestMode",
148
+ cargoData?.workspace ? "workspace" : "package",
149
+ );
150
+ appendProperty(
151
+ properties,
152
+ "cdx:cargo:hasWorkspaceMembers",
153
+ Array.isArray(cargoData?.workspace?.members) ? "true" : undefined,
154
+ );
155
+ appendProperty(
156
+ properties,
157
+ "cdx:cargo:workspaceMembers",
158
+ Array.isArray(cargoData?.workspace?.members)
159
+ ? cargoData.workspace.members.join(", ")
160
+ : undefined,
161
+ );
162
+ appendProperty(
163
+ properties,
164
+ "cdx:cargo:hasBuildDependencies",
165
+ allBuildDependencyNames.length ? "true" : undefined,
166
+ );
167
+ appendProperty(
168
+ properties,
169
+ "cdx:cargo:hasDevDependencies",
170
+ Object.keys(devDependencies || {}).length ? "true" : undefined,
171
+ );
172
+ appendProperty(
173
+ properties,
174
+ "cdx:cargo:hasTargetDependencies",
175
+ targetDependencyBlocks.some(
176
+ (block) =>
177
+ Object.keys(block?.dependencies || {}).length ||
178
+ Object.keys(block?.["build-dependencies"] || {}).length ||
179
+ Object.keys(block?.["dev-dependencies"] || {}).length,
180
+ )
181
+ ? "true"
182
+ : undefined,
183
+ );
184
+ appendProperty(
185
+ properties,
186
+ "cdx:cargo:hasBuildScript",
187
+ hasBuildScript ? "true" : undefined,
188
+ );
189
+ appendProperty(
190
+ properties,
191
+ "cdx:cargo:buildScript",
192
+ hasBuildScript ? buildScriptPath : undefined,
193
+ );
194
+ appendProperty(
195
+ properties,
196
+ "cdx:cargo:publish",
197
+ packageNode.publish === false ? "false" : undefined,
198
+ );
199
+ appendProperty(
200
+ properties,
201
+ "cdx:cargo:rustVersion",
202
+ packageNode["rust-version"],
203
+ );
204
+ appendProperty(
205
+ properties,
206
+ "cdx:cargo:releaseProfiles",
207
+ releaseProfiles.length ? releaseProfiles.join(", ") : undefined,
208
+ );
209
+ appendProperty(
210
+ properties,
211
+ "cdx:cargo:nativeBuildIndicators",
212
+ expandedNativeBuildIndicators.length
213
+ ? expandedNativeBuildIndicators.join(", ")
214
+ : undefined,
215
+ );
216
+ appendProperty(
217
+ properties,
218
+ "cdx:cargo:buildDependencyNames",
219
+ allBuildDependencyNames.length
220
+ ? allBuildDependencyNames.join(", ")
221
+ : undefined,
222
+ );
223
+ appendProperty(
224
+ properties,
225
+ "cdx:cargo:buildScriptCapabilities",
226
+ buildScriptCapabilities.length
227
+ ? buildScriptCapabilities.join(", ")
228
+ : undefined,
229
+ );
230
+ appendProperty(
231
+ properties,
232
+ "cdx:cargo:buildScriptIndicators",
233
+ buildScriptIndicators.length ? buildScriptIndicators.join(", ") : undefined,
234
+ );
235
+ appendProperty(
236
+ properties,
237
+ "cdx:cargo:hasNativeBuild",
238
+ hasBuildScript ||
239
+ expandedNativeBuildIndicators.length ||
240
+ buildScriptCapabilities.length
241
+ ? "true"
242
+ : undefined,
243
+ );
244
+ appendProperty(
245
+ properties,
246
+ "cdx:cargo:usesMaturin",
247
+ cargoData?.package?.metadata?.maturin ? "true" : undefined,
248
+ );
249
+ return component;
250
+ }
251
+
252
+ function parsePyProject(filePath) {
253
+ let pyprojectData;
254
+ try {
255
+ pyprojectData = toml.parse(readFileSync(filePath, { encoding: "utf-8" }));
256
+ } catch {
257
+ return undefined;
258
+ }
259
+ const buildBackend = pyprojectData?.["build-system"]?.["build-backend"];
260
+ const toolMaturin = pyprojectData?.tool?.maturin;
261
+ if (
262
+ !toolMaturin &&
263
+ (typeof buildBackend !== "string" || !buildBackend.includes("maturin"))
264
+ ) {
265
+ return undefined;
266
+ }
267
+ const component = createFormulationComponent(
268
+ pyprojectData?.project?.name ||
269
+ basename(dirname(filePath)) ||
270
+ "maturin-project",
271
+ filePath,
272
+ "maturin",
273
+ );
274
+ const properties = component.properties;
275
+ appendProperty(properties, "cdx:rust:buildTool", "maturin");
276
+ appendProperty(properties, "cdx:maturin:buildBackend", buildBackend);
277
+ appendProperty(
278
+ properties,
279
+ "cdx:maturin:moduleName",
280
+ toolMaturin?.["module-name"] || toolMaturin?.module_name,
281
+ );
282
+ appendProperty(properties, "cdx:maturin:bindings", toolMaturin?.bindings);
283
+ appendProperty(
284
+ properties,
285
+ "cdx:maturin:compatibility",
286
+ toolMaturin?.compatibility,
287
+ );
288
+ appendProperty(
289
+ properties,
290
+ "cdx:maturin:features",
291
+ Array.isArray(toolMaturin?.features)
292
+ ? toolMaturin.features.join(", ")
293
+ : undefined,
294
+ );
295
+ appendProperty(
296
+ properties,
297
+ "cdx:maturin:manifestPath",
298
+ toolMaturin?.manifest_path || toolMaturin?.["manifest-path"],
299
+ );
300
+ appendProperty(
301
+ properties,
302
+ "cdx:maturin:strip",
303
+ toolMaturin?.strip === true ? "true" : undefined,
304
+ );
305
+ return component;
306
+ }
307
+
308
+ export const rustFormulationParser = {
309
+ id: "rust-build",
310
+ patterns: ["**/Cargo.toml", "**/pyproject.toml"],
311
+ parse(files) {
312
+ const components = [];
313
+ for (const filePath of files || []) {
314
+ if (filePath.endsWith("Cargo.toml")) {
315
+ const cargoComponent = parseCargoManifest(filePath);
316
+ if (cargoComponent) {
317
+ components.push(cargoComponent);
318
+ }
319
+ continue;
320
+ }
321
+ if (filePath.endsWith("pyproject.toml")) {
322
+ const pyprojectComponent = parsePyProject(filePath);
323
+ if (pyprojectComponent) {
324
+ components.push(pyprojectComponent);
325
+ }
326
+ }
327
+ }
328
+ return { components };
329
+ },
330
+ };
@@ -9,13 +9,18 @@ import { coerce, diff, prerelease } from "semver";
9
9
  import { thoughtLog } from "./logger.js";
10
10
  import {
11
11
  cdxgenAgent,
12
+ createDryRunError,
12
13
  DEBUG_MODE,
13
14
  fetchPomXmlAsJson,
14
15
  getTmpDir,
15
16
  hasDangerousUnicode,
17
+ isDryRun,
16
18
  isSecureMode,
17
19
  isValidDriveRoot,
18
20
  isWin,
21
+ recordActivity,
22
+ safeMkdtempSync,
23
+ safeRmSync,
19
24
  safeSpawnSync,
20
25
  } from "./utils.js";
21
26
 
@@ -568,7 +573,21 @@ export function gitClone(repoUrl, branch = null) {
568
573
  if (!/^[a-zA-Z0-9_-]+$/.test(baseDirName)) {
569
574
  baseDirName = "repo-";
570
575
  }
571
- const tempDir = fs.mkdtempSync(path.join(getTmpDir(), baseDirName));
576
+ if (isDryRun) {
577
+ const error = createDryRunError(
578
+ "clone",
579
+ repoUrl,
580
+ "Dry run mode blocks repository cloning.",
581
+ );
582
+ recordActivity({
583
+ kind: "clone",
584
+ reason: error.message,
585
+ status: "blocked",
586
+ target: repoUrl,
587
+ });
588
+ return path.join(getTmpDir(), `${baseDirName}${path.sep}dry-run-clone`);
589
+ }
590
+ const tempDir = safeMkdtempSync(path.join(getTmpDir(), baseDirName));
572
591
 
573
592
  const gitArgs = [
574
593
  "-c",
@@ -1262,6 +1281,6 @@ export function cleanupSourceDir(srcDir) {
1262
1281
  fs.rmSync
1263
1282
  ) {
1264
1283
  thoughtLog(`Cleaning up ${srcDir}`);
1265
- fs.rmSync(srcDir, { recursive: true, force: true });
1284
+ safeRmSync(srcDir, { recursive: true, force: true });
1266
1285
  }
1267
1286
  }
@@ -7,6 +7,44 @@ import { assert, describe, it } from "poku";
7
7
  import sinon from "sinon";
8
8
 
9
9
  describe("source helper purl resolution", () => {
10
+ it("gitClone() records a blocked clone in dry-run mode", async () => {
11
+ const recordActivity = sinon.stub();
12
+ const safeSpawnSync = sinon.stub();
13
+ const { gitClone } = await esmock("./source.js", {
14
+ "./utils.js": {
15
+ cdxgenAgent: { get: sinon.stub() },
16
+ createDryRunError: (action, target, reason) => {
17
+ const error = new Error(reason);
18
+ error.action = action;
19
+ error.code = "CDXGEN_DRY_RUN";
20
+ error.target = target;
21
+ error.dryRun = true;
22
+ return error;
23
+ },
24
+ DEBUG_MODE: false,
25
+ fetchPomXmlAsJson: sinon.stub(),
26
+ getTmpDir: sinon.stub().returns(os.tmpdir()),
27
+ hasDangerousUnicode: sinon.stub().returns(false),
28
+ isDryRun: true,
29
+ isSecureMode: false,
30
+ isValidDriveRoot: sinon.stub().returns(true),
31
+ isWin: false,
32
+ recordActivity,
33
+ safeMkdtempSync: sinon.stub(),
34
+ safeRmSync: sinon.stub(),
35
+ safeSpawnSync,
36
+ },
37
+ });
38
+
39
+ const clonedPath = gitClone("https://github.com/cdxgen/cdxgen.git", "main");
40
+
41
+ assert.ok(clonedPath.includes("dry-run-clone"));
42
+ sinon.assert.notCalled(safeSpawnSync);
43
+ sinon.assert.calledOnce(recordActivity);
44
+ assert.strictEqual(recordActivity.firstCall.args[0].kind, "clone");
45
+ assert.strictEqual(recordActivity.firstCall.args[0].status, "blocked");
46
+ });
47
+
10
48
  it("resolves npm purl to repository URL", async () => {
11
49
  const getStub = sinon.stub().resolves({
12
50
  body: {