@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
@@ -77,6 +77,7 @@ import {
77
77
  } from "./pylockutils.js";
78
78
  import { get_python_command_from_env, getVenvMetadata } from "./pythonutils.js";
79
79
  import {
80
+ collectCargoRegistryProvenanceProperties,
80
81
  collectNpmRegistryProvenanceProperties,
81
82
  collectPypiRegistryProvenanceProperties,
82
83
  } from "./registryProvenance.js";
@@ -95,6 +96,15 @@ export const isSecureMode =
95
96
  ["true", "1"].includes(process.env?.CDXGEN_SECURE_MODE) ||
96
97
  process.env?.NODE_OPTIONS?.includes("--permission");
97
98
 
99
+ // CLI dry-run must be detected during module initialization because some probes
100
+ // execute while modules are imported, before bin/cdxgen.js can thread options.
101
+ const hasDryRunArg = process.argv?.some(
102
+ (arg) =>
103
+ arg === "--dry-run" || arg === "--dry-run=true" || arg === "--dry-run=1",
104
+ );
105
+ export let isDryRun =
106
+ ["true", "1"].includes(process.env?.CDXGEN_DRY_RUN) || hasDryRunArg;
107
+
98
108
  export const isNode = globalThis.process?.versions?.node !== undefined;
99
109
  export const isBun = globalThis.Bun?.version !== undefined;
100
110
  export const isDeno = globalThis.Deno?.version?.deno !== undefined;
@@ -102,6 +112,104 @@ export const isDeno = globalThis.Deno?.version?.deno !== undefined;
102
112
  export const isWin = platform() === "win32";
103
113
  export const isMac = platform() === "darwin";
104
114
 
115
+ export const DRY_RUN_ERROR_CODE = "CDXGEN_DRY_RUN";
116
+ const activityLedger = [];
117
+ let activityCounter = 0;
118
+ let currentActivityContext = {};
119
+
120
+ export function setDryRunMode(enabled) {
121
+ isDryRun = !!enabled;
122
+ if (enabled) {
123
+ process.env.CDXGEN_DRY_RUN = "true";
124
+ return;
125
+ }
126
+ delete process.env.CDXGEN_DRY_RUN;
127
+ }
128
+
129
+ export function createDryRunError(action, target, reason) {
130
+ const message =
131
+ reason || `Dry run mode blocked the attempted ${action} operation.`;
132
+ const error = new Error(message);
133
+ error.code = DRY_RUN_ERROR_CODE;
134
+ error.name = "DryRunError";
135
+ error.action = action;
136
+ error.target = target;
137
+ error.dryRun = true;
138
+ return error;
139
+ }
140
+
141
+ export function isDryRunError(error) {
142
+ return !!(error?.dryRun || error?.code === DRY_RUN_ERROR_CODE);
143
+ }
144
+
145
+ export function setActivityContext(context = {}) {
146
+ currentActivityContext = {
147
+ ...currentActivityContext,
148
+ ...context,
149
+ };
150
+ }
151
+
152
+ export function resetActivityContext() {
153
+ currentActivityContext = {};
154
+ }
155
+
156
+ export function recordActivity(activity) {
157
+ if (!(isDryRun || DEBUG_MODE)) {
158
+ return undefined;
159
+ }
160
+ const identifier = `ACT-${String(++activityCounter).padStart(4, "0")}`;
161
+ const entry = {
162
+ identifier,
163
+ timestamp: new Date().toISOString(),
164
+ projectType: currentActivityContext.projectType,
165
+ packageType: currentActivityContext.packageType,
166
+ sourcePath: currentActivityContext.sourcePath,
167
+ ...activity,
168
+ };
169
+ activityLedger.push(entry);
170
+ traceLog("activity", entry);
171
+ return entry;
172
+ }
173
+
174
+ export function getRecordedActivities() {
175
+ return [...activityLedger];
176
+ }
177
+
178
+ export function resetRecordedActivities() {
179
+ activityLedger.length = 0;
180
+ activityCounter = 0;
181
+ }
182
+
183
+ function recordFilesystemActivity(kind, target, status, reason = undefined) {
184
+ return recordActivity({
185
+ kind,
186
+ reason,
187
+ status,
188
+ target,
189
+ });
190
+ }
191
+
192
+ function hasReadPermission(filePath) {
193
+ if (!(isSecureMode && process.permission)) {
194
+ return true;
195
+ }
196
+ return process.permission.has("fs.read", join(filePath, "", "*"));
197
+ }
198
+
199
+ function hasWritePermission(filePath) {
200
+ if (!(isSecureMode && process.permission)) {
201
+ return true;
202
+ }
203
+ const candidatePaths = [
204
+ filePath,
205
+ join(filePath, "", "*"),
206
+ join(dirname(filePath), "*"),
207
+ ];
208
+ return candidatePaths.some((candidatePath) =>
209
+ process.permission.has("fs.write", candidatePath),
210
+ );
211
+ }
212
+
105
213
  /**
106
214
  * Safely check if a file path exists without crashing due to a lack of permissions
107
215
  *
@@ -109,17 +217,42 @@ export const isMac = platform() === "darwin";
109
217
  * @Boolean True if the path exists. False otherwise
110
218
  */
111
219
  export function safeExistsSync(filePath) {
112
- if (isSecureMode && process.permission) {
113
- if (!process.permission.has("fs.read", join(filePath, "", "*"))) {
114
- if (DEBUG_MODE) {
115
- console.log(`cdxgen lacks read permission for: ${filePath}`);
116
- }
117
- return false;
220
+ if (!hasReadPermission(filePath)) {
221
+ if (DEBUG_MODE) {
222
+ console.log("cdxgen lacks read permission for a requested path.");
118
223
  }
224
+ return false;
119
225
  }
120
226
  return existsSync(filePath);
121
227
  }
122
228
 
229
+ export function safeWriteSync(filePath, data, options) {
230
+ if (isDryRun) {
231
+ recordFilesystemActivity(
232
+ "write",
233
+ filePath,
234
+ "blocked",
235
+ "Dry run mode blocks filesystem writes.",
236
+ );
237
+ return undefined;
238
+ }
239
+ if (!hasWritePermission(filePath)) {
240
+ if (DEBUG_MODE) {
241
+ console.log("cdxgen lacks write permission for a requested path.");
242
+ }
243
+ recordFilesystemActivity(
244
+ "write",
245
+ filePath,
246
+ "blocked",
247
+ "cdxgen lacks write permission for this path.",
248
+ );
249
+ return undefined;
250
+ }
251
+ const result = writeFileSync(filePath, data, options);
252
+ recordFilesystemActivity("write", filePath, "completed");
253
+ return result;
254
+ }
255
+
123
256
  /**
124
257
  * Safely create a directory without crashing due to a lack of permissions
125
258
  *
@@ -128,15 +261,118 @@ export function safeExistsSync(filePath) {
128
261
  * @Boolean True if the path exists. False otherwise
129
262
  */
130
263
  export function safeMkdirSync(filePath, options) {
131
- if (isSecureMode && process.permission) {
132
- if (!process.permission.has("fs.write", join(filePath, "", "*"))) {
133
- if (DEBUG_MODE) {
134
- console.log(`cdxgen lacks write permission for: ${filePath}`);
135
- }
136
- return undefined;
264
+ if (isDryRun) {
265
+ recordFilesystemActivity(
266
+ "mkdir",
267
+ filePath,
268
+ "blocked",
269
+ "Dry run mode blocks directory creation.",
270
+ );
271
+ return undefined;
272
+ }
273
+ if (!hasWritePermission(filePath)) {
274
+ if (DEBUG_MODE) {
275
+ console.log("cdxgen lacks write permission for a requested path.");
137
276
  }
277
+ recordFilesystemActivity(
278
+ "mkdir",
279
+ filePath,
280
+ "blocked",
281
+ "cdxgen lacks write permission for this path.",
282
+ );
283
+ return undefined;
284
+ }
285
+ const result = mkdirSync(filePath, options);
286
+ recordFilesystemActivity("mkdir", filePath, "completed");
287
+ return result;
288
+ }
289
+
290
+ export function safeMkdtempSync(prefix, options = undefined) {
291
+ if (isDryRun) {
292
+ const tempPath = `${prefix}${randomUUID().replaceAll("-", "").slice(0, 6)}`;
293
+ recordFilesystemActivity(
294
+ "temp-dir",
295
+ tempPath,
296
+ "blocked",
297
+ "Dry run mode blocks temporary directory creation.",
298
+ );
299
+ return tempPath;
300
+ }
301
+ const tempPath = mkdtempSync(prefix, options);
302
+ recordFilesystemActivity("temp-dir", tempPath, "completed");
303
+ return tempPath;
304
+ }
305
+
306
+ export function safeRmSync(filePath, options = undefined) {
307
+ if (isDryRun) {
308
+ recordFilesystemActivity(
309
+ "cleanup",
310
+ filePath,
311
+ "blocked",
312
+ "Dry run mode blocks filesystem deletions.",
313
+ );
314
+ return undefined;
315
+ }
316
+ const result = rmSync(filePath, options);
317
+ recordFilesystemActivity("cleanup", filePath, "completed");
318
+ return result;
319
+ }
320
+
321
+ export function safeUnlinkSync(filePath) {
322
+ if (isDryRun) {
323
+ recordFilesystemActivity(
324
+ "cleanup",
325
+ filePath,
326
+ "blocked",
327
+ "Dry run mode blocks file deletions.",
328
+ );
329
+ return undefined;
330
+ }
331
+ const result = unlinkSync(filePath);
332
+ recordFilesystemActivity("cleanup", filePath, "completed");
333
+ return result;
334
+ }
335
+
336
+ export function safeCopyFileSync(src, dest, mode = undefined) {
337
+ if (isDryRun) {
338
+ recordFilesystemActivity(
339
+ "write",
340
+ dest,
341
+ "blocked",
342
+ `Dry run mode blocks copying files from ${src}.`,
343
+ );
344
+ return undefined;
345
+ }
346
+ const result =
347
+ mode === undefined
348
+ ? copyFileSync(src, dest)
349
+ : copyFileSync(src, dest, mode);
350
+ recordFilesystemActivity("write", dest, "completed", `Copied from ${src}.`);
351
+ return result;
352
+ }
353
+
354
+ export async function safeExtractArchive(
355
+ sourcePath,
356
+ targetPath,
357
+ extractor,
358
+ kind = "unzip",
359
+ ) {
360
+ if (isDryRun) {
361
+ recordActivity({
362
+ kind,
363
+ reason: "Dry run mode blocks archive extraction and decompression.",
364
+ status: "blocked",
365
+ target: `${sourcePath} -> ${targetPath}`,
366
+ });
367
+ return false;
138
368
  }
139
- return mkdirSync(filePath, options);
369
+ await extractor();
370
+ recordActivity({
371
+ kind,
372
+ status: "completed",
373
+ target: `${sourcePath} -> ${targetPath}`,
374
+ });
375
+ return true;
140
376
  }
141
377
 
142
378
  export const commandsExecuted = new Set();
@@ -203,6 +439,25 @@ function isWindowsShellHijackRisk(command, options) {
203
439
  * @returns {Object} spawnSync result object with status, stdout, stderr, and error fields
204
440
  */
205
441
  export function safeSpawnSync(command, args, options) {
442
+ if (isDryRun) {
443
+ const error = createDryRunError(
444
+ "execute",
445
+ command,
446
+ "Dry run mode blocks child process execution.",
447
+ );
448
+ recordActivity({
449
+ kind: "execute",
450
+ reason: error.message,
451
+ status: "blocked",
452
+ target: `${command}${args?.length ? ` ${args.join(" ")}` : ""}`,
453
+ });
454
+ return {
455
+ status: 1,
456
+ stdout: undefined,
457
+ stderr: undefined,
458
+ error,
459
+ };
460
+ }
206
461
  if (
207
462
  (isSecureMode && process.permission && !process.permission.has("child")) ||
208
463
  !isAllowedCommand(command)
@@ -210,6 +465,12 @@ export function safeSpawnSync(command, args, options) {
210
465
  if (DEBUG_MODE) {
211
466
  console.log(`cdxgen lacks execute permission for ${command}`);
212
467
  }
468
+ recordActivity({
469
+ kind: "execute",
470
+ reason: "cdxgen lacks execute permission for this command.",
471
+ status: "blocked",
472
+ target: `${command}${args?.length ? ` ${args.join(" ")}` : ""}`,
473
+ });
213
474
  return {
214
475
  status: 1,
215
476
  stdout: undefined,
@@ -221,6 +482,12 @@ export function safeSpawnSync(command, args, options) {
221
482
  if (isWindowsShellHijackRisk(command, options)) {
222
483
  const blockedReason = `${command} matches local file in cwd (Windows shell hijack risk)`;
223
484
  console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
485
+ recordActivity({
486
+ kind: "execute",
487
+ reason: blockedReason,
488
+ status: "blocked",
489
+ target: `${command}${args?.length ? ` ${args.join(" ")}` : ""}`,
490
+ });
224
491
  return {
225
492
  status: 1,
226
493
  stdout: undefined,
@@ -335,7 +602,14 @@ export function safeSpawnSync(command, args, options) {
335
602
  args = undefined;
336
603
  }
337
604
  }
338
- return spawnSync(command, args, options);
605
+ const result = spawnSync(command, args, options);
606
+ recordActivity({
607
+ kind: "execute",
608
+ reason: result.error?.message,
609
+ status: result.status === 0 && !result.error ? "completed" : "failed",
610
+ target: `${command}${args?.length ? ` ${args.join(" ")}` : ""}`,
611
+ });
612
+ return result;
339
613
  }
340
614
 
341
615
  const licenseMapping = JSON.parse(
@@ -413,7 +687,7 @@ const temporaryFiles = new Set();
413
687
  process.on("exit", () =>
414
688
  temporaryFiles.forEach((tempFile) => {
415
689
  if (safeExistsSync(tempFile)) {
416
- unlinkSync(tempFile);
690
+ safeUnlinkSync(tempFile);
417
691
  }
418
692
  }),
419
693
  );
@@ -620,6 +894,7 @@ export const PROJECT_TYPE_ALIASES = {
620
894
  "gradle-index": ["gradle-index", "gradle-cache"],
621
895
  "sbt-index": ["sbt-index", "sbt-cache"],
622
896
  "maven-index": ["maven-index", "maven-cache", "maven-core"],
897
+ "cargo-cache": ["cargo-cache", "cargo-index"],
623
898
  js: [
624
899
  "npm",
625
900
  "pnpm",
@@ -651,6 +926,8 @@ export const PROJECT_TYPE_ALIASES = {
651
926
  "yarn",
652
927
  "rush",
653
928
  ],
929
+ mcp: ["mcp"],
930
+ "ai-skill": ["ai-skill", "skill", "skills"],
654
931
  py: [
655
932
  "py",
656
933
  "python",
@@ -950,10 +1227,36 @@ export const cdxgenAgent = got.extend({
950
1227
  hooks: {
951
1228
  beforeRequest: [
952
1229
  (options) => {
1230
+ options.context = {
1231
+ ...options.context,
1232
+ activityTarget: options.url.toString(),
1233
+ };
1234
+ if (isDryRun) {
1235
+ const error = createDryRunError(
1236
+ "network",
1237
+ options.url.toString(),
1238
+ "Dry run mode blocks outbound network access.",
1239
+ );
1240
+ recordActivity({
1241
+ kind: "network",
1242
+ reason: error.message,
1243
+ status: "blocked",
1244
+ target: options.url.toString(),
1245
+ });
1246
+ options.context.activityBlocked = true;
1247
+ throw error;
1248
+ }
953
1249
  if (!isAllowedHost(options.url.hostname)) {
954
1250
  console.log(
955
1251
  `Access to the remote host '${options.url.hostname}' is not permitted.`,
956
1252
  );
1253
+ recordActivity({
1254
+ kind: "network",
1255
+ reason: "The remote host is not permitted.",
1256
+ status: "blocked",
1257
+ target: options.url.toString(),
1258
+ });
1259
+ options.context.activityBlocked = true;
957
1260
  return new AbortController().abort();
958
1261
  }
959
1262
  // Only allow https protocol in secure mode
@@ -961,6 +1264,13 @@ export const cdxgenAgent = got.extend({
961
1264
  console.log(
962
1265
  `Access to the remote host '${options.url.hostname}' is not permitted via the '${options.url.protocol}' protocol.`,
963
1266
  );
1267
+ recordActivity({
1268
+ kind: "network",
1269
+ reason: `The '${options.url.protocol}' protocol is not permitted in secure mode.`,
1270
+ status: "blocked",
1271
+ target: options.url.toString(),
1272
+ });
1273
+ options.context.activityBlocked = true;
964
1274
  return new AbortController().abort();
965
1275
  }
966
1276
  remoteHostsAccessed.add(options.url.hostname);
@@ -971,6 +1281,39 @@ export const cdxgenAgent = got.extend({
971
1281
  });
972
1282
  },
973
1283
  ],
1284
+ afterResponse: [
1285
+ (response) => {
1286
+ if (response.statusCode < 200 || response.statusCode >= 300) {
1287
+ return response;
1288
+ }
1289
+ const activityTarget =
1290
+ response.request.options.context?.activityTarget ||
1291
+ response.request.options.url?.toString() ||
1292
+ response.url;
1293
+ recordActivity({
1294
+ kind: "network",
1295
+ status: "completed",
1296
+ target: activityTarget,
1297
+ });
1298
+ return response;
1299
+ },
1300
+ ],
1301
+ beforeError: [
1302
+ (error) => {
1303
+ if (error.options?.context?.activityBlocked) {
1304
+ return error;
1305
+ }
1306
+ recordActivity({
1307
+ kind: "network",
1308
+ reason: error.message,
1309
+ status: "failed",
1310
+ target:
1311
+ error.options?.context?.activityTarget ||
1312
+ error.options?.url?.toString(),
1313
+ });
1314
+ return error;
1315
+ },
1316
+ ],
974
1317
  },
975
1318
  });
976
1319
 
@@ -9751,12 +10094,24 @@ export async function getCratesMetadata(pkgList) {
9751
10094
  const cdepList = [];
9752
10095
  for (const p of pkgList) {
9753
10096
  try {
10097
+ if (!p?.name || !p?.version || p.version === "workspace") {
10098
+ cdepList.push(p);
10099
+ continue;
10100
+ }
9754
10101
  if (DEBUG_MODE) {
9755
10102
  console.log(`Querying crates.io for ${p.name}@${p.version}`);
9756
10103
  }
9757
10104
  const res = await cdxgenAgent.get(CRATES_URL + p.name, {
9758
10105
  responseType: "json",
9759
10106
  });
10107
+ let ownersRes;
10108
+ try {
10109
+ ownersRes = await cdxgenAgent.get(`${CRATES_URL + p.name}/owners`, {
10110
+ responseType: "json",
10111
+ });
10112
+ } catch (_err) {
10113
+ ownersRes = undefined;
10114
+ }
9760
10115
  let versionToUse = res?.body?.versions[0];
9761
10116
  if (p.version) {
9762
10117
  for (const aversion of res.body.versions) {
@@ -9782,7 +10137,7 @@ export async function getCratesMetadata(pkgList) {
9782
10137
  p.version = body.newest_version;
9783
10138
  }
9784
10139
  if (!p._integrity && versionToUse.checksum) {
9785
- p._integrity = `sha384-${versionToUse.checksum}`;
10140
+ p._integrity = normalizeCargoIntegrity(versionToUse.checksum);
9786
10141
  }
9787
10142
  if (!p.properties) {
9788
10143
  p.properties = [];
@@ -9808,6 +10163,13 @@ export async function getCratesMetadata(pkgList) {
9808
10163
  value: JSON.stringify(versionToUse.features),
9809
10164
  });
9810
10165
  }
10166
+ p.properties = p.properties.concat(
10167
+ collectCargoRegistryProvenanceProperties(
10168
+ res?.body,
10169
+ versionToUse?.num || p.version,
10170
+ ownersRes?.body,
10171
+ ),
10172
+ );
9811
10173
  cdepList.push(p);
9812
10174
  } catch (_err) {
9813
10175
  cdepList.push(p);
@@ -9926,7 +10288,10 @@ function parseCargoDependencyFromPackageNode(packageNode) {
9926
10288
  }
9927
10289
 
9928
10290
  if (!isExtendFromWorkspace(pkgChecksum) && pkgChecksum) {
9929
- pkg._integrity = `${pkgChecksum}`;
10291
+ const normalizedCargoIntegrity = normalizeCargoIntegrity(pkgChecksum);
10292
+ if (normalizedCargoIntegrity) {
10293
+ pkg._integrity = normalizedCargoIntegrity;
10294
+ }
9930
10295
  }
9931
10296
  if (!isExtendFromWorkspace(pkgName) && pkgName) {
9932
10297
  pkg.group = group;
@@ -9957,6 +10322,555 @@ function parseCargoDependencyFromPackageNode(packageNode) {
9957
10322
  return pkg;
9958
10323
  }
9959
10324
 
10325
+ function normalizeCargoIntegrity(integrity) {
10326
+ if (typeof integrity !== "string") {
10327
+ return undefined;
10328
+ }
10329
+ const normalizedIntegrity = integrity.trim().toLowerCase();
10330
+ const prefixedMatch = /^(sha256|sha384)-(?<digest>[a-f0-9]+)$/i.exec(
10331
+ normalizedIntegrity,
10332
+ );
10333
+ if (prefixedMatch?.groups?.digest) {
10334
+ const algorithm = prefixedMatch[1].toLowerCase();
10335
+ const digest = prefixedMatch.groups.digest;
10336
+ const expectedDigestLength = algorithm === "sha384" ? 96 : 64;
10337
+ if (digest.length === expectedDigestLength) {
10338
+ return `${algorithm}-${digest}`;
10339
+ }
10340
+ return undefined;
10341
+ }
10342
+ if (!/^[a-f0-9]+$/i.test(normalizedIntegrity)) {
10343
+ return undefined;
10344
+ }
10345
+ if (normalizedIntegrity.length === 64) {
10346
+ return `sha256-${normalizedIntegrity}`;
10347
+ }
10348
+ if (normalizedIntegrity.length === 96) {
10349
+ return `sha384-${normalizedIntegrity}`;
10350
+ }
10351
+ return undefined;
10352
+ }
10353
+
10354
+ function cargoIntegrityToComponentHash(integrity) {
10355
+ const normalizedIntegrity = normalizeCargoIntegrity(integrity);
10356
+ if (!normalizedIntegrity) {
10357
+ return undefined;
10358
+ }
10359
+ const [, algorithm, digest] = /^(sha256|sha384)-([a-f0-9]+)$/i.exec(
10360
+ normalizedIntegrity,
10361
+ );
10362
+ return {
10363
+ alg: algorithm.toLowerCase() === "sha384" ? "SHA-384" : "SHA-256",
10364
+ content: digest,
10365
+ };
10366
+ }
10367
+
10368
+ function readCargoTomlData(cargoTomlFile) {
10369
+ if (!cargoTomlFile || !safeExistsSync(cargoTomlFile)) {
10370
+ return undefined;
10371
+ }
10372
+ try {
10373
+ return toml.parse(readFileSync(cargoTomlFile, { encoding: "utf-8" }));
10374
+ } catch (error) {
10375
+ traceLog("cargo", {
10376
+ cargoTomlFile,
10377
+ error: error.message,
10378
+ });
10379
+ if (DEBUG_MODE) {
10380
+ console.warn(`Failed to parse Cargo manifest ${cargoTomlFile}:`, error);
10381
+ }
10382
+ return undefined;
10383
+ }
10384
+ }
10385
+
10386
+ function isCargoWorkspaceReference(value) {
10387
+ return Boolean(value?.workspace);
10388
+ }
10389
+
10390
+ function cargoPackageInfoToPurl(pkg) {
10391
+ return decodeURIComponent(
10392
+ new PackageURL(
10393
+ "cargo",
10394
+ pkg?.group,
10395
+ pkg?.name,
10396
+ pkg?.version,
10397
+ null,
10398
+ null,
10399
+ ).toString(),
10400
+ );
10401
+ }
10402
+
10403
+ function resolveCargoDependencyAliasName(dependencyName, dependencyNode) {
10404
+ if (
10405
+ dependencyNode &&
10406
+ typeof dependencyNode === "object" &&
10407
+ typeof dependencyNode.package === "string" &&
10408
+ dependencyNode.package
10409
+ ) {
10410
+ return dependencyNode.package;
10411
+ }
10412
+ return dependencyName;
10413
+ }
10414
+
10415
+ function resolveCargoWorkspaceContext(cargoTomlFile, cargoData, context = {}) {
10416
+ if (
10417
+ context?.workspaceRootFile &&
10418
+ context?.workspaceRootData &&
10419
+ safeExistsSync(context.workspaceRootFile)
10420
+ ) {
10421
+ return {
10422
+ isVirtualWorkspace: !context.workspaceRootData?.package,
10423
+ isWorkspaceRoot: context.workspaceRootFile === cargoTomlFile,
10424
+ workspaceData: context.workspaceRootData.workspace,
10425
+ workspaceRootData: context.workspaceRootData,
10426
+ workspaceRootFile: context.workspaceRootFile,
10427
+ };
10428
+ }
10429
+ let currentDir = dirname(cargoTomlFile);
10430
+ while (currentDir && currentDir !== dirname(currentDir)) {
10431
+ const candidateFile = join(currentDir, "Cargo.toml");
10432
+ if (safeExistsSync(candidateFile)) {
10433
+ const candidateData =
10434
+ candidateFile === cargoTomlFile
10435
+ ? cargoData
10436
+ : readCargoTomlData(candidateFile);
10437
+ if (candidateData?.workspace) {
10438
+ return {
10439
+ isVirtualWorkspace: !candidateData?.package,
10440
+ isWorkspaceRoot: candidateFile === cargoTomlFile,
10441
+ workspaceData: candidateData.workspace,
10442
+ workspaceRootData: candidateData,
10443
+ workspaceRootFile: candidateFile,
10444
+ };
10445
+ }
10446
+ }
10447
+ const parentDir = dirname(currentDir);
10448
+ if (parentDir === currentDir) {
10449
+ break;
10450
+ }
10451
+ currentDir = parentDir;
10452
+ }
10453
+ return {};
10454
+ }
10455
+
10456
+ function resolveCargoWorkspaceMembers(workspaceRootFile, workspaceData) {
10457
+ const workspaceRootDir = dirname(workspaceRootFile);
10458
+ const members = [];
10459
+ const excludedRoots = new Set();
10460
+ for (const excludedPattern of workspaceData?.exclude || []) {
10461
+ excludedRoots.add(resolve(workspaceRootDir, excludedPattern));
10462
+ }
10463
+ for (const memberPattern of workspaceData?.members || []) {
10464
+ const directMemberFile = resolve(
10465
+ workspaceRootDir,
10466
+ memberPattern,
10467
+ "Cargo.toml",
10468
+ );
10469
+ if (safeExistsSync(directMemberFile)) {
10470
+ members.push(directMemberFile);
10471
+ continue;
10472
+ }
10473
+ const matchedMemberFiles = globSync(
10474
+ join(memberPattern, "Cargo.toml").replaceAll("\\", "/"),
10475
+ {
10476
+ absolute: true,
10477
+ cwd: workspaceRootDir,
10478
+ nodir: true,
10479
+ windowsPathsNoEscape: true,
10480
+ },
10481
+ );
10482
+ if (matchedMemberFiles?.length) {
10483
+ members.push(...matchedMemberFiles);
10484
+ }
10485
+ }
10486
+ return [...new Set(members)]
10487
+ .filter((memberFile) => {
10488
+ const memberDir = resolve(dirname(memberFile));
10489
+ for (const excludedRoot of excludedRoots) {
10490
+ if (
10491
+ memberDir === excludedRoot ||
10492
+ memberDir.startsWith(`${excludedRoot}${_sep}`)
10493
+ ) {
10494
+ return false;
10495
+ }
10496
+ }
10497
+ return true;
10498
+ })
10499
+ .sort();
10500
+ }
10501
+
10502
+ function resolveCargoWorkspacePackageNode(packageNode, workspacePackageNode) {
10503
+ if (!packageNode || typeof packageNode !== "object") {
10504
+ return packageNode;
10505
+ }
10506
+ const mergedNode = { ...packageNode };
10507
+ for (const fieldName of [
10508
+ "authors",
10509
+ "description",
10510
+ "documentation",
10511
+ "edition",
10512
+ "homepage",
10513
+ "keywords",
10514
+ "license",
10515
+ "name",
10516
+ "readme",
10517
+ "repository",
10518
+ "rust-version",
10519
+ "version",
10520
+ ]) {
10521
+ if (
10522
+ isCargoWorkspaceReference(packageNode[fieldName]) &&
10523
+ workspacePackageNode?.[fieldName] !== undefined
10524
+ ) {
10525
+ mergedNode[fieldName] = workspacePackageNode[fieldName];
10526
+ }
10527
+ }
10528
+ return mergedNode;
10529
+ }
10530
+
10531
+ function resolveCargoManifestPackageIdentity(
10532
+ cargoTomlFile,
10533
+ cargoData,
10534
+ context = {},
10535
+ ) {
10536
+ const workspaceContext = resolveCargoWorkspaceContext(
10537
+ cargoTomlFile,
10538
+ cargoData,
10539
+ context,
10540
+ );
10541
+ const resolvedPackageNode = resolveCargoWorkspacePackageNode(
10542
+ cargoData?.package,
10543
+ workspaceContext?.workspaceData?.package,
10544
+ );
10545
+ if (
10546
+ resolvedPackageNode &&
10547
+ typeof resolvedPackageNode === "object" &&
10548
+ !Array.isArray(resolvedPackageNode)
10549
+ ) {
10550
+ try {
10551
+ return parseCargoDependencyFromPackageNode(resolvedPackageNode);
10552
+ } catch {
10553
+ return undefined;
10554
+ }
10555
+ }
10556
+ if (
10557
+ cargoData?.workspace &&
10558
+ workspaceContext?.isWorkspaceRoot &&
10559
+ workspaceContext?.isVirtualWorkspace
10560
+ ) {
10561
+ return {
10562
+ group: "",
10563
+ name: basename(dirname(cargoTomlFile)),
10564
+ version: "workspace",
10565
+ };
10566
+ }
10567
+ return undefined;
10568
+ }
10569
+
10570
+ function normalizeCargoDependencySpec(dependencySpec) {
10571
+ if (typeof dependencySpec === "string") {
10572
+ return { version: dependencySpec };
10573
+ }
10574
+ if (!dependencySpec || typeof dependencySpec !== "object") {
10575
+ return {};
10576
+ }
10577
+ return { ...dependencySpec };
10578
+ }
10579
+
10580
+ function mergeCargoWorkspaceDependencySpec(
10581
+ dependencyName,
10582
+ dependencyNode,
10583
+ workspaceDependencies,
10584
+ ) {
10585
+ if (
10586
+ !dependencyNode ||
10587
+ typeof dependencyNode !== "object" ||
10588
+ dependencyNode.workspace !== true
10589
+ ) {
10590
+ return dependencyNode;
10591
+ }
10592
+ const workspaceDependencyNode = workspaceDependencies?.[dependencyName];
10593
+ if (workspaceDependencyNode === undefined) {
10594
+ return dependencyNode;
10595
+ }
10596
+ const mergedSpec = {
10597
+ ...normalizeCargoDependencySpec(workspaceDependencyNode),
10598
+ ...normalizeCargoDependencySpec(dependencyNode),
10599
+ };
10600
+ mergedSpec.workspace = true;
10601
+ if (
10602
+ Array.isArray(workspaceDependencyNode?.features) ||
10603
+ Array.isArray(dependencyNode?.features)
10604
+ ) {
10605
+ mergedSpec.features = [
10606
+ ...new Set([
10607
+ ...(workspaceDependencyNode?.features || []),
10608
+ ...(dependencyNode?.features || []),
10609
+ ]),
10610
+ ];
10611
+ }
10612
+ return mergedSpec;
10613
+ }
10614
+
10615
+ function resolveCargoWorkspaceMemberMap(
10616
+ workspaceRootFile,
10617
+ workspaceRootData,
10618
+ workspaceMemberCache,
10619
+ ) {
10620
+ if (!workspaceRootFile || !workspaceRootData?.workspace) {
10621
+ return new Map();
10622
+ }
10623
+ const cacheKey = resolve(workspaceRootFile);
10624
+ if (workspaceMemberCache?.has(cacheKey)) {
10625
+ return workspaceMemberCache.get(cacheKey);
10626
+ }
10627
+ const memberMap = new Map();
10628
+ const workspaceMemberFiles = resolveCargoWorkspaceMembers(
10629
+ workspaceRootFile,
10630
+ workspaceRootData.workspace,
10631
+ );
10632
+ for (const workspaceMemberFile of workspaceMemberFiles) {
10633
+ const memberCargoData = readCargoTomlData(workspaceMemberFile);
10634
+ if (!memberCargoData) {
10635
+ continue;
10636
+ }
10637
+ const memberIdentity = resolveCargoManifestPackageIdentity(
10638
+ workspaceMemberFile,
10639
+ memberCargoData,
10640
+ {
10641
+ workspaceRootData,
10642
+ workspaceRootFile,
10643
+ },
10644
+ );
10645
+ if (!memberIdentity?.name || !memberIdentity?.version) {
10646
+ continue;
10647
+ }
10648
+ memberMap.set(memberIdentity.name, {
10649
+ ...memberIdentity,
10650
+ filePath: workspaceMemberFile,
10651
+ ref: cargoPackageInfoToPurl(memberIdentity),
10652
+ });
10653
+ }
10654
+ workspaceMemberCache?.set(cacheKey, memberMap);
10655
+ return memberMap;
10656
+ }
10657
+
10658
+ function resolveCargoWorkspaceDependencyTarget(
10659
+ cargoTomlFile,
10660
+ dependencyName,
10661
+ dependencyNode,
10662
+ workspaceContext,
10663
+ workspaceMemberMap,
10664
+ ) {
10665
+ const resolvedDependencyName = resolveCargoDependencyAliasName(
10666
+ dependencyName,
10667
+ dependencyNode,
10668
+ );
10669
+ if (
10670
+ dependencyNode &&
10671
+ typeof dependencyNode === "object" &&
10672
+ dependencyNode.workspace === true &&
10673
+ workspaceMemberMap?.has(resolvedDependencyName)
10674
+ ) {
10675
+ return workspaceMemberMap.get(resolvedDependencyName);
10676
+ }
10677
+ const dependencyPath =
10678
+ dependencyNode &&
10679
+ typeof dependencyNode === "object" &&
10680
+ typeof dependencyNode.path === "string" &&
10681
+ dependencyNode.path
10682
+ ? resolve(dirname(cargoTomlFile), dependencyNode.path, "Cargo.toml")
10683
+ : undefined;
10684
+ if (!dependencyPath || !safeExistsSync(dependencyPath)) {
10685
+ return undefined;
10686
+ }
10687
+ const dependencyCargoData = readCargoTomlData(dependencyPath);
10688
+ if (!dependencyCargoData) {
10689
+ return undefined;
10690
+ }
10691
+ const dependencyIdentity = resolveCargoManifestPackageIdentity(
10692
+ dependencyPath,
10693
+ dependencyCargoData,
10694
+ {
10695
+ workspaceRootData: workspaceContext?.workspaceRootData,
10696
+ workspaceRootFile: workspaceContext?.workspaceRootFile,
10697
+ },
10698
+ );
10699
+ if (!dependencyIdentity?.name || !dependencyIdentity?.version) {
10700
+ return undefined;
10701
+ }
10702
+ return {
10703
+ ...dependencyIdentity,
10704
+ filePath: dependencyPath,
10705
+ ref: cargoPackageInfoToPurl(dependencyIdentity),
10706
+ };
10707
+ }
10708
+
10709
+ function ensurePropertiesArray(pkg) {
10710
+ if (!pkg.properties) {
10711
+ pkg.properties = [];
10712
+ }
10713
+ return pkg.properties;
10714
+ }
10715
+
10716
+ function appendCargoProperty(pkg, name, value) {
10717
+ if (!name || value === undefined || value === null || value === "") {
10718
+ return;
10719
+ }
10720
+ const properties = ensurePropertiesArray(pkg);
10721
+ const stringValue = typeof value === "string" ? value : String(value);
10722
+ if (
10723
+ properties.some(
10724
+ (property) => property.name === name && property.value === stringValue,
10725
+ )
10726
+ ) {
10727
+ return;
10728
+ }
10729
+ properties.push({
10730
+ name,
10731
+ value: stringValue,
10732
+ });
10733
+ }
10734
+
10735
+ function normalizeCargoDependencyVersion(dependencyNode) {
10736
+ if (typeof dependencyNode === "string" || dependencyNode instanceof String) {
10737
+ return dependencyNode.trim();
10738
+ }
10739
+ if (!dependencyNode || typeof dependencyNode !== "object") {
10740
+ return "";
10741
+ }
10742
+ if (typeof dependencyNode.version === "string" && dependencyNode.version) {
10743
+ return dependencyNode.version;
10744
+ }
10745
+ if (typeof dependencyNode.git === "string" && dependencyNode.git) {
10746
+ return `git+${dependencyNode.git}`;
10747
+ }
10748
+ if (typeof dependencyNode.path === "string" && dependencyNode.path) {
10749
+ return `path+${dependencyNode.path}`;
10750
+ }
10751
+ if (dependencyNode.workspace === true) {
10752
+ return "workspace";
10753
+ }
10754
+ return "";
10755
+ }
10756
+
10757
+ function applyCargoDependencySpecMetadata(
10758
+ pkg,
10759
+ dependencyNode,
10760
+ dependencyKind,
10761
+ targetSelector,
10762
+ resolvedWorkspaceTarget,
10763
+ ) {
10764
+ appendCargoProperty(pkg, "cdx:cargo:dependencyKind", dependencyKind);
10765
+ appendCargoProperty(pkg, "cdx:cargo:scope", dependencyKind);
10766
+ if (targetSelector) {
10767
+ appendCargoProperty(pkg, "cdx:cargo:target", targetSelector);
10768
+ }
10769
+ if (!dependencyNode || typeof dependencyNode !== "object") {
10770
+ if (dependencyKind === "dev") {
10771
+ pkg.scope = "excluded";
10772
+ }
10773
+ return;
10774
+ }
10775
+ if (dependencyKind === "dev") {
10776
+ pkg.scope = "excluded";
10777
+ }
10778
+ if (dependencyNode.optional === true) {
10779
+ pkg.scope = "optional";
10780
+ appendCargoProperty(pkg, "cdx:cargo:optional", "true");
10781
+ }
10782
+ if (dependencyNode.default_features === false) {
10783
+ appendCargoProperty(pkg, "cdx:cargo:defaultFeatures", "false");
10784
+ }
10785
+ if (dependencyNode["default-features"] === false) {
10786
+ appendCargoProperty(pkg, "cdx:cargo:defaultFeatures", "false");
10787
+ }
10788
+ if (
10789
+ Array.isArray(dependencyNode.features) &&
10790
+ dependencyNode.features.length
10791
+ ) {
10792
+ appendCargoProperty(
10793
+ pkg,
10794
+ "cdx:cargo:dependencyFeatures",
10795
+ JSON.stringify(dependencyNode.features),
10796
+ );
10797
+ }
10798
+ appendCargoProperty(pkg, "cdx:cargo:path", dependencyNode.path);
10799
+ appendCargoProperty(pkg, "cdx:cargo:git", dependencyNode.git);
10800
+ appendCargoProperty(pkg, "cdx:cargo:gitBranch", dependencyNode.branch);
10801
+ appendCargoProperty(pkg, "cdx:cargo:gitTag", dependencyNode.tag);
10802
+ appendCargoProperty(pkg, "cdx:cargo:gitRev", dependencyNode.rev);
10803
+ appendCargoProperty(pkg, "cdx:cargo:registry", dependencyNode.registry);
10804
+ appendCargoProperty(pkg, "cdx:cargo:package", dependencyNode.package);
10805
+ appendCargoProperty(
10806
+ pkg,
10807
+ "cdx:cargo:workspaceDependency",
10808
+ dependencyNode.workspace === true ? "true" : undefined,
10809
+ );
10810
+ appendCargoProperty(
10811
+ pkg,
10812
+ "cdx:cargo:workspaceDependencyResolved",
10813
+ resolvedWorkspaceTarget ? "true" : undefined,
10814
+ );
10815
+ appendCargoProperty(
10816
+ pkg,
10817
+ "cdx:cargo:resolvedWorkspaceMember",
10818
+ resolvedWorkspaceTarget?.name,
10819
+ );
10820
+ appendCargoProperty(
10821
+ pkg,
10822
+ "cdx:cargo:resolvedMemberPath",
10823
+ resolvedWorkspaceTarget?.filePath,
10824
+ );
10825
+ }
10826
+
10827
+ function collectCargoManifestDependencyComponents(
10828
+ dependencyEntries,
10829
+ addPackageToList,
10830
+ pkgList,
10831
+ simple,
10832
+ dependencyKind,
10833
+ targetSelector,
10834
+ workspaceDependencies,
10835
+ cargoTomlFile,
10836
+ workspaceContext,
10837
+ workspaceMemberMap,
10838
+ ) {
10839
+ if (!dependencyEntries || typeof dependencyEntries !== "object") {
10840
+ return;
10841
+ }
10842
+ for (const dependencyName of Object.keys(dependencyEntries)) {
10843
+ const dependencyNode = mergeCargoWorkspaceDependencySpec(
10844
+ dependencyName,
10845
+ dependencyEntries[dependencyName],
10846
+ workspaceDependencies,
10847
+ );
10848
+ const resolvedWorkspaceTarget = resolveCargoWorkspaceDependencyTarget(
10849
+ cargoTomlFile,
10850
+ dependencyName,
10851
+ dependencyNode,
10852
+ workspaceContext,
10853
+ workspaceMemberMap,
10854
+ );
10855
+ const version = normalizeCargoDependencyVersion(dependencyNode);
10856
+ if (!dependencyName || !version) {
10857
+ continue;
10858
+ }
10859
+ const pkg = {
10860
+ name: dependencyName,
10861
+ version,
10862
+ };
10863
+ applyCargoDependencySpecMetadata(
10864
+ pkg,
10865
+ dependencyNode,
10866
+ dependencyKind,
10867
+ targetSelector,
10868
+ resolvedWorkspaceTarget,
10869
+ );
10870
+ addPackageToList(pkgList, pkg, { packageMode: false, simple });
10871
+ }
10872
+ }
10873
+
9960
10874
  /**
9961
10875
  * Method to parse cargo.toml data
9962
10876
  *
@@ -9979,6 +10893,7 @@ export async function parseCargoTomlData(
9979
10893
  cargoTomlFile,
9980
10894
  simple = false,
9981
10895
  pkgFilesMap = {},
10896
+ context = {},
9982
10897
  ) {
9983
10898
  const pkgList = [];
9984
10899
 
@@ -9995,6 +10910,7 @@ export async function parseCargoTomlData(
9995
10910
  name: "SrcFile",
9996
10911
  value: cargoTomlFile,
9997
10912
  },
10913
+ ...(pkg.properties || []),
9998
10914
  ];
9999
10915
  if (pkgFilesMap?.[pkg.name]) {
10000
10916
  pkg.components = fileListToComponents(pkgFilesMap[pkg.name]);
@@ -10038,16 +10954,39 @@ export async function parseCargoTomlData(
10038
10954
  if (!cargoTomlFile || !safeExistsSync(cargoTomlFile)) {
10039
10955
  return pkgList;
10040
10956
  }
10041
- let cargoData;
10042
- try {
10043
- cargoData = toml.parse(readFileSync(cargoTomlFile, { encoding: "utf-8" }));
10044
- } catch (e) {
10045
- console.log(e);
10957
+ const normalizedCargoTomlFile = resolve(cargoTomlFile);
10958
+ cargoTomlFile = normalizedCargoTomlFile;
10959
+ const visitedCargoTomlFiles = context?.visitedCargoTomlFiles || new Set();
10960
+ for (const visitedCargoTomlFile of visitedCargoTomlFiles) {
10961
+ if (typeof visitedCargoTomlFile === "string") {
10962
+ visitedCargoTomlFiles.add(resolve(visitedCargoTomlFile));
10963
+ }
10046
10964
  }
10965
+ if (visitedCargoTomlFiles.has(normalizedCargoTomlFile)) {
10966
+ return pkgList;
10967
+ }
10968
+ visitedCargoTomlFiles.add(normalizedCargoTomlFile);
10969
+ const cargoData = readCargoTomlData(normalizedCargoTomlFile);
10047
10970
  if (!cargoData) {
10048
10971
  return pkgList;
10049
10972
  }
10050
- const packageNode = cargoData["package"];
10973
+ const workspaceContext = resolveCargoWorkspaceContext(
10974
+ normalizedCargoTomlFile,
10975
+ cargoData,
10976
+ context,
10977
+ );
10978
+ const workspacePackageNode = workspaceContext?.workspaceData?.package;
10979
+ const workspaceDependencies = workspaceContext?.workspaceData?.dependencies;
10980
+ const workspaceMemberCache = context?.workspaceMemberCache || new Map();
10981
+ const workspaceMemberMap = resolveCargoWorkspaceMemberMap(
10982
+ workspaceContext?.workspaceRootFile,
10983
+ workspaceContext?.workspaceRootData,
10984
+ workspaceMemberCache,
10985
+ );
10986
+ const packageNode = resolveCargoWorkspacePackageNode(
10987
+ cargoData["package"],
10988
+ workspacePackageNode,
10989
+ );
10051
10990
  // parse `[package]`
10052
10991
  if (packageNode instanceof Object && !Array.isArray(packageNode)) {
10053
10992
  /** @type {Object} */
@@ -10060,45 +10999,159 @@ export async function parseCargoTomlData(
10060
10999
  `Failed to parse package: ${packageObjNode?.name}@${packageObjNode?.version},fail with:${e.message}`,
10061
11000
  );
10062
11001
  }
11002
+ } else if (
11003
+ cargoData.workspace &&
11004
+ workspaceContext?.isWorkspaceRoot &&
11005
+ workspaceContext?.isVirtualWorkspace
11006
+ ) {
11007
+ const workspaceComponent = {
11008
+ name: basename(dirname(cargoTomlFile)),
11009
+ properties: [],
11010
+ version: "workspace",
11011
+ };
11012
+ appendCargoProperty(
11013
+ workspaceComponent,
11014
+ "cdx:cargo:manifestMode",
11015
+ "virtual-workspace",
11016
+ );
11017
+ addPackageToList(pkgList, workspaceComponent, {
11018
+ packageMode: true,
11019
+ simple,
11020
+ });
10063
11021
  }
10064
- // parse `[dependencies]`
10065
- const dependenciesNode = cargoData["dependencies"];
10066
- if (dependenciesNode) {
10067
- for (const dependencyName in dependenciesNode) {
10068
- const dependencyNode = dependenciesNode[dependencyName];
10069
- let version = "";
10070
- if (
10071
- typeof dependencyNode === "string" ||
10072
- dependencyNode instanceof String
10073
- ) {
10074
- // like `libc = 0.2.79`
10075
- version = dependencyNode;
10076
- } else if (Object.keys(dependencyNode).length > 0) {
10077
- // like `libc = { version = "0.2.79", features = ['rustc-dep-of-std'], default-features = false }`
10078
- version = dependencyNode?.version;
10079
- const git = dependencyNode?.git;
10080
- if (!version && git) {
10081
- version = `git+${git}`;
10082
- }
10083
- }
10084
- if (dependencyName && version) {
10085
- const pkg = {};
10086
- pkg.name = dependencyName;
10087
- pkg.version = version;
10088
- addPackageToList(pkgList, pkg, { packageMode: false, simple });
11022
+ if (pkgList[0] && workspaceContext?.workspaceRootFile) {
11023
+ appendCargoProperty(
11024
+ pkgList[0],
11025
+ "cdx:cargo:workspaceRoot",
11026
+ workspaceContext.workspaceRootFile,
11027
+ );
11028
+ appendCargoProperty(
11029
+ pkgList[0],
11030
+ "cdx:cargo:manifestMode",
11031
+ cargoData?.workspace
11032
+ ? workspaceContext?.isVirtualWorkspace
11033
+ ? "virtual-workspace"
11034
+ : "workspace"
11035
+ : "package",
11036
+ );
11037
+ }
11038
+ if (Array.isArray(cargoData?.workspace?.members) && pkgList[0]) {
11039
+ appendCargoProperty(pkgList[0], "cdx:cargo:hasWorkspaceMembers", "true");
11040
+ appendCargoProperty(
11041
+ pkgList[0],
11042
+ "cdx:cargo:workspaceMembers",
11043
+ cargoData.workspace.members.join(", "),
11044
+ );
11045
+ }
11046
+ collectCargoManifestDependencyComponents(
11047
+ cargoData["dependencies"],
11048
+ addPackageToList,
11049
+ pkgList,
11050
+ simple,
11051
+ "runtime",
11052
+ undefined,
11053
+ workspaceDependencies,
11054
+ cargoTomlFile,
11055
+ workspaceContext,
11056
+ workspaceMemberMap,
11057
+ );
11058
+ collectCargoManifestDependencyComponents(
11059
+ cargoData["build-dependencies"],
11060
+ addPackageToList,
11061
+ pkgList,
11062
+ simple,
11063
+ "build",
11064
+ undefined,
11065
+ workspaceDependencies,
11066
+ cargoTomlFile,
11067
+ workspaceContext,
11068
+ workspaceMemberMap,
11069
+ );
11070
+ collectCargoManifestDependencyComponents(
11071
+ cargoData["dev-dependencies"],
11072
+ addPackageToList,
11073
+ pkgList,
11074
+ simple,
11075
+ "dev",
11076
+ undefined,
11077
+ workspaceDependencies,
11078
+ cargoTomlFile,
11079
+ workspaceContext,
11080
+ workspaceMemberMap,
11081
+ );
11082
+ if (cargoData.target && typeof cargoData.target === "object") {
11083
+ for (const targetSelector of Object.keys(cargoData.target)) {
11084
+ const targetBlock = cargoData.target[targetSelector];
11085
+ if (!targetBlock || typeof targetBlock !== "object") {
11086
+ continue;
10089
11087
  }
11088
+ collectCargoManifestDependencyComponents(
11089
+ targetBlock["dependencies"],
11090
+ addPackageToList,
11091
+ pkgList,
11092
+ simple,
11093
+ "runtime",
11094
+ targetSelector,
11095
+ workspaceDependencies,
11096
+ cargoTomlFile,
11097
+ workspaceContext,
11098
+ workspaceMemberMap,
11099
+ );
11100
+ collectCargoManifestDependencyComponents(
11101
+ targetBlock["build-dependencies"],
11102
+ addPackageToList,
11103
+ pkgList,
11104
+ simple,
11105
+ "build",
11106
+ targetSelector,
11107
+ workspaceDependencies,
11108
+ cargoTomlFile,
11109
+ workspaceContext,
11110
+ workspaceMemberMap,
11111
+ );
11112
+ collectCargoManifestDependencyComponents(
11113
+ targetBlock["dev-dependencies"],
11114
+ addPackageToList,
11115
+ pkgList,
11116
+ simple,
11117
+ "dev",
11118
+ targetSelector,
11119
+ workspaceDependencies,
11120
+ cargoTomlFile,
11121
+ workspaceContext,
11122
+ workspaceMemberMap,
11123
+ );
10090
11124
  }
10091
11125
  }
10092
-
10093
- // Properly parsing project with workspaces is currently unsupported. Some
10094
- // projects may have a top-level Cargo.toml file containing only
10095
- // workspace definitions and no package name. That will make the parent
10096
- // component unreliable.
10097
- // See: https://doc.rust-lang.org/cargo/reference/workspaces.html#virtual-workspace
10098
- if (cargoData.workspace && DEBUG_MODE) {
10099
- console.log(
10100
- `Found [workspace] section in ${cargoTomlFile}. Workspaces are currently not fully supported. Verify that the parent component is correct.`,
11126
+ if (
11127
+ context?.includeWorkspaceMembers !== false &&
11128
+ workspaceContext?.isWorkspaceRoot &&
11129
+ Array.isArray(cargoData?.workspace?.members)
11130
+ ) {
11131
+ const workspaceMemberFiles = resolveCargoWorkspaceMembers(
11132
+ cargoTomlFile,
11133
+ cargoData.workspace,
10101
11134
  );
11135
+ for (const workspaceMemberFile of workspaceMemberFiles) {
11136
+ if (workspaceMemberFile === cargoTomlFile) {
11137
+ continue;
11138
+ }
11139
+ const workspaceMemberPackages = await parseCargoTomlData(
11140
+ workspaceMemberFile,
11141
+ simple,
11142
+ pkgFilesMap,
11143
+ {
11144
+ includeWorkspaceMembers: false,
11145
+ visitedCargoTomlFiles,
11146
+ workspaceMemberCache,
11147
+ workspaceRootData: cargoData,
11148
+ workspaceRootFile: cargoTomlFile,
11149
+ },
11150
+ );
11151
+ if (workspaceMemberPackages?.length) {
11152
+ pkgList.push(...workspaceMemberPackages);
11153
+ }
11154
+ }
10102
11155
  }
10103
11156
 
10104
11157
  if (!simple && shouldFetchLicense()) {
@@ -10143,13 +11196,9 @@ export async function parseCargoData(
10143
11196
  version: newPackage.version,
10144
11197
  };
10145
11198
 
10146
- if (newPackage._integrity) {
10147
- component.hashes = [
10148
- {
10149
- alg: "SHA-384",
10150
- content: newPackage._integrity,
10151
- },
10152
- ];
11199
+ const integrityHash = cargoIntegrityToComponentHash(newPackage._integrity);
11200
+ if (integrityHash) {
11201
+ component.hashes = [integrityHash];
10153
11202
  }
10154
11203
 
10155
11204
  if (!simple) {
@@ -10221,6 +11270,178 @@ export async function parseCargoData(
10221
11270
  return pkgList;
10222
11271
  }
10223
11272
 
11273
+ function collectCargoManifestDependencyRefs(
11274
+ cargoTomlFile,
11275
+ dependencyEntries,
11276
+ workspaceDependencies,
11277
+ workspaceContext,
11278
+ workspaceMemberMap,
11279
+ dependsOn,
11280
+ ) {
11281
+ if (!dependencyEntries || typeof dependencyEntries !== "object") {
11282
+ return;
11283
+ }
11284
+ for (const dependencyName of Object.keys(dependencyEntries)) {
11285
+ const dependencyNode = mergeCargoWorkspaceDependencySpec(
11286
+ dependencyName,
11287
+ dependencyEntries[dependencyName],
11288
+ workspaceDependencies,
11289
+ );
11290
+ const resolvedWorkspaceTarget = resolveCargoWorkspaceDependencyTarget(
11291
+ cargoTomlFile,
11292
+ dependencyName,
11293
+ dependencyNode,
11294
+ workspaceContext,
11295
+ workspaceMemberMap,
11296
+ );
11297
+ if (resolvedWorkspaceTarget?.ref) {
11298
+ dependsOn.add(resolvedWorkspaceTarget.ref);
11299
+ }
11300
+ }
11301
+ }
11302
+
11303
+ /**
11304
+ * Build a Cargo dependency graph from manifest relationships so workspace roots
11305
+ * and member-to-member links can complement lockfile-derived dependency data.
11306
+ *
11307
+ * @param {string} cargoTomlFile Cargo.toml path
11308
+ * @param {object} [context] manifest graph context
11309
+ * @returns {object[]} Cargo dependency relationships
11310
+ */
11311
+ export function parseCargoManifestDependencyData(cargoTomlFile, context = {}) {
11312
+ if (!cargoTomlFile || !safeExistsSync(cargoTomlFile)) {
11313
+ return [];
11314
+ }
11315
+ const normalizedCargoTomlFile = resolve(cargoTomlFile);
11316
+ cargoTomlFile = normalizedCargoTomlFile;
11317
+ const visitedCargoTomlFiles =
11318
+ context?.visitedCargoTomlDependencyGraphFiles || new Set();
11319
+ for (const visitedCargoTomlFile of visitedCargoTomlFiles) {
11320
+ if (typeof visitedCargoTomlFile === "string") {
11321
+ visitedCargoTomlFiles.add(resolve(visitedCargoTomlFile));
11322
+ }
11323
+ }
11324
+ if (visitedCargoTomlFiles.has(normalizedCargoTomlFile)) {
11325
+ return [];
11326
+ }
11327
+ visitedCargoTomlFiles.add(normalizedCargoTomlFile);
11328
+ const cargoData = readCargoTomlData(normalizedCargoTomlFile);
11329
+ if (!cargoData) {
11330
+ return [];
11331
+ }
11332
+ const workspaceContext = resolveCargoWorkspaceContext(
11333
+ cargoTomlFile,
11334
+ cargoData,
11335
+ context,
11336
+ );
11337
+ const workspaceMemberCache = context?.workspaceMemberCache || new Map();
11338
+ const workspaceMemberMap = resolveCargoWorkspaceMemberMap(
11339
+ workspaceContext?.workspaceRootFile,
11340
+ workspaceContext?.workspaceRootData,
11341
+ workspaceMemberCache,
11342
+ );
11343
+ const workspaceDependencies = workspaceContext?.workspaceData?.dependencies;
11344
+ const currentIdentity = resolveCargoManifestPackageIdentity(
11345
+ cargoTomlFile,
11346
+ cargoData,
11347
+ context,
11348
+ );
11349
+ const dependencyGraph = [];
11350
+ const dependsOn = new Set();
11351
+ if (workspaceContext?.isWorkspaceRoot) {
11352
+ for (const workspaceMember of workspaceMemberMap.values()) {
11353
+ if (workspaceMember?.ref) {
11354
+ dependsOn.add(workspaceMember.ref);
11355
+ }
11356
+ }
11357
+ }
11358
+ collectCargoManifestDependencyRefs(
11359
+ cargoTomlFile,
11360
+ cargoData.dependencies,
11361
+ workspaceDependencies,
11362
+ workspaceContext,
11363
+ workspaceMemberMap,
11364
+ dependsOn,
11365
+ );
11366
+ collectCargoManifestDependencyRefs(
11367
+ cargoTomlFile,
11368
+ cargoData["build-dependencies"],
11369
+ workspaceDependencies,
11370
+ workspaceContext,
11371
+ workspaceMemberMap,
11372
+ dependsOn,
11373
+ );
11374
+ collectCargoManifestDependencyRefs(
11375
+ cargoTomlFile,
11376
+ cargoData["dev-dependencies"],
11377
+ workspaceDependencies,
11378
+ workspaceContext,
11379
+ workspaceMemberMap,
11380
+ dependsOn,
11381
+ );
11382
+ if (cargoData.target && typeof cargoData.target === "object") {
11383
+ for (const targetBlock of Object.values(cargoData.target)) {
11384
+ if (!targetBlock || typeof targetBlock !== "object") {
11385
+ continue;
11386
+ }
11387
+ collectCargoManifestDependencyRefs(
11388
+ cargoTomlFile,
11389
+ targetBlock.dependencies,
11390
+ workspaceDependencies,
11391
+ workspaceContext,
11392
+ workspaceMemberMap,
11393
+ dependsOn,
11394
+ );
11395
+ collectCargoManifestDependencyRefs(
11396
+ cargoTomlFile,
11397
+ targetBlock["build-dependencies"],
11398
+ workspaceDependencies,
11399
+ workspaceContext,
11400
+ workspaceMemberMap,
11401
+ dependsOn,
11402
+ );
11403
+ collectCargoManifestDependencyRefs(
11404
+ cargoTomlFile,
11405
+ targetBlock["dev-dependencies"],
11406
+ workspaceDependencies,
11407
+ workspaceContext,
11408
+ workspaceMemberMap,
11409
+ dependsOn,
11410
+ );
11411
+ }
11412
+ }
11413
+ if (currentIdentity?.name && currentIdentity?.version) {
11414
+ dependencyGraph.push({
11415
+ dependsOn: [...dependsOn].sort(),
11416
+ ref: cargoPackageInfoToPurl(currentIdentity),
11417
+ });
11418
+ }
11419
+ if (
11420
+ context?.includeWorkspaceMembers !== false &&
11421
+ workspaceContext?.isWorkspaceRoot &&
11422
+ Array.isArray(cargoData?.workspace?.members)
11423
+ ) {
11424
+ for (const workspaceMemberFile of resolveCargoWorkspaceMembers(
11425
+ cargoTomlFile,
11426
+ cargoData.workspace,
11427
+ )) {
11428
+ if (workspaceMemberFile === cargoTomlFile) {
11429
+ continue;
11430
+ }
11431
+ dependencyGraph.push(
11432
+ ...parseCargoManifestDependencyData(workspaceMemberFile, {
11433
+ includeWorkspaceMembers: false,
11434
+ visitedCargoTomlDependencyGraphFiles: visitedCargoTomlFiles,
11435
+ workspaceMemberCache,
11436
+ workspaceRootData: cargoData,
11437
+ workspaceRootFile: cargoTomlFile,
11438
+ }),
11439
+ );
11440
+ }
11441
+ }
11442
+ return dependencyGraph;
11443
+ }
11444
+
10224
11445
  /**
10225
11446
  * Parses a Cargo.lock file's TOML data and returns a flat dependency graph as an
10226
11447
  * array of objects mapping each package purl to the purls it directly depends on.
@@ -11082,7 +12303,15 @@ export function parseMixLockData(mixData) {
11082
12303
  */
11083
12304
  export function parseGitHubWorkflowData(f) {
11084
12305
  const { components } = parseWorkflowFile(f);
11085
- return components.filter((c) => c.scope === "required");
12306
+ return components.filter(
12307
+ (component) =>
12308
+ component.scope === "required" ||
12309
+ component.properties?.some(
12310
+ (property) =>
12311
+ property?.name === "cdx:github:step:usesCargo" &&
12312
+ property?.value === "true",
12313
+ ),
12314
+ );
11086
12315
  }
11087
12316
 
11088
12317
  /**
@@ -13843,7 +15072,7 @@ export async function collectMvnDependencies(
13843
15072
  let jarNSMapping = {};
13844
15073
  const MAVEN_CACHE_DIR =
13845
15074
  process.env.MAVEN_CACHE_DIR || join(homedir(), ".m2", "repository");
13846
- const tempDir = mkdtempSync(join(tmpdir(), "mvn-deps-"));
15075
+ const tempDir = safeMkdtempSync(join(tmpdir(), "mvn-deps-"));
13847
15076
  let copyArgs = [
13848
15077
  "dependency:copy-dependencies",
13849
15078
  `-DoutputDirectory=${tempDir}`,
@@ -13888,8 +15117,8 @@ export async function collectMvnDependencies(
13888
15117
  }
13889
15118
 
13890
15119
  // Clean up
13891
- if (cleanup && tempDir?.startsWith(tmpdir()) && rmSync) {
13892
- rmSync(tempDir, { recursive: true, force: true });
15120
+ if (cleanup && tempDir?.startsWith(tmpdir())) {
15121
+ safeRmSync(tempDir, { recursive: true, force: true });
13893
15122
  }
13894
15123
  return jarNSMapping;
13895
15124
  }
@@ -14465,7 +15694,7 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
14465
15694
  lstatSync(jarFile).isFile()
14466
15695
  ) {
14467
15696
  // Only copy if the file doesn't exist
14468
- copyFileSync(jarFile, join(tempDir, fname), constants.COPYFILE_FICLONE);
15697
+ safeCopyFileSync(jarFile, join(tempDir, fname), constants.COPYFILE_FICLONE);
14469
15698
  }
14470
15699
  const env = {
14471
15700
  ...process.env,
@@ -14485,8 +15714,17 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
14485
15714
  if (safeExistsSync(join(tempDir, fname))) {
14486
15715
  try {
14487
15716
  const zip = new StreamZip.async({ file: join(tempDir, fname) });
14488
- await zip.extract(null, tempDir);
15717
+ const extracted = await safeExtractArchive(
15718
+ join(tempDir, fname),
15719
+ tempDir,
15720
+ async () => {
15721
+ await zip.extract(null, tempDir);
15722
+ },
15723
+ );
14489
15724
  await zip.close();
15725
+ if (!extracted) {
15726
+ return pkgList;
15727
+ }
14490
15728
  } catch (e) {
14491
15729
  console.log(`Unable to extract ${join(tempDir, fname)}. Skipping.`, e);
14492
15730
  return pkgList;
@@ -14541,9 +15779,11 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
14541
15779
  // Unzip natively
14542
15780
  try {
14543
15781
  const zip = new StreamZip.async({ file: jf });
14544
- await zip.extract(null, tempDir);
15782
+ const extracted = await safeExtractArchive(jf, tempDir, async () => {
15783
+ await zip.extract(null, tempDir);
15784
+ });
14545
15785
  await zip.close();
14546
- jarResult = { status: 0 };
15786
+ jarResult = { status: extracted ? 0 : 1 };
14547
15787
  } catch (_e) {
14548
15788
  if (DEBUG_MODE) {
14549
15789
  console.log(`Unable to extract ${jf}. Skipping.`);
@@ -14786,9 +16026,9 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
14786
16026
  }
14787
16027
  }
14788
16028
  try {
14789
- if (rmSync && safeExistsSync(join(tempDir, "META-INF"))) {
16029
+ if (safeExistsSync(join(tempDir, "META-INF"))) {
14790
16030
  // Clean up META-INF
14791
- rmSync(join(tempDir, "META-INF"), {
16031
+ safeRmSync(join(tempDir, "META-INF"), {
14792
16032
  recursive: true,
14793
16033
  force: true,
14794
16034
  });
@@ -14859,10 +16099,14 @@ export function addPlugin(projectPath, plugin) {
14859
16099
  let originalPluginsFile = null;
14860
16100
  if (safeExistsSync(pluginsFile)) {
14861
16101
  originalPluginsFile = `${pluginsFile}.cdxgen`;
14862
- copyFileSync(pluginsFile, originalPluginsFile, constants.COPYFILE_FICLONE);
16102
+ safeCopyFileSync(
16103
+ pluginsFile,
16104
+ originalPluginsFile,
16105
+ constants.COPYFILE_FICLONE,
16106
+ );
14863
16107
  }
14864
16108
 
14865
- writeFileSync(pluginsFile, plugin, { flag: "a" });
16109
+ safeWriteSync(pluginsFile, plugin, { flag: "a" });
14866
16110
  return originalPluginsFile;
14867
16111
  }
14868
16112
 
@@ -14878,12 +16122,16 @@ export function cleanupPlugin(projectPath, originalPluginsFile) {
14878
16122
  if (safeExistsSync(pluginsFile)) {
14879
16123
  if (!originalPluginsFile) {
14880
16124
  // just remove the file, it was never there
14881
- unlinkSync(pluginsFile);
16125
+ safeUnlinkSync(pluginsFile);
14882
16126
  return !safeExistsSync(pluginsFile);
14883
16127
  }
14884
16128
  // Bring back the original file
14885
- copyFileSync(originalPluginsFile, pluginsFile, constants.COPYFILE_FICLONE);
14886
- unlinkSync(originalPluginsFile);
16129
+ safeCopyFileSync(
16130
+ originalPluginsFile,
16131
+ pluginsFile,
16132
+ constants.COPYFILE_FICLONE,
16133
+ );
16134
+ safeUnlinkSync(originalPluginsFile);
14887
16135
  return true;
14888
16136
  }
14889
16137
  return false;
@@ -15600,7 +16848,7 @@ async function fullScanCocoaPod(dependency, component, options) {
15600
16848
  dirname(podspecLocation),
15601
16849
  `${randomUUID()}.${podspecLocation.substring(podspecLocation.lastIndexOf(".") + 1)}`,
15602
16850
  );
15603
- writeFileSync(podspecLocation, podspecContent);
16851
+ safeWriteSync(podspecLocation, podspecContent);
15604
16852
  temporaryFiles.add(podspecLocation);
15605
16853
  }
15606
16854
  result = executePodCommand(
@@ -16123,7 +17371,7 @@ export function findAppModules(
16123
17371
  methodology = "usages",
16124
17372
  slicesFile = undefined,
16125
17373
  ) {
16126
- const tempDir = mkdtempSync(join(tmpdir(), "atom-deps-"));
17374
+ const tempDir = safeMkdtempSync(join(tmpdir(), "atom-deps-"));
16127
17375
  const atomFile = join(tempDir, `${language}-app.atom`);
16128
17376
  if (!slicesFile) {
16129
17377
  slicesFile = join(tempDir, "slices.json");
@@ -16155,8 +17403,8 @@ export function findAppModules(
16155
17403
  );
16156
17404
  }
16157
17405
  // Clean up
16158
- if (tempDir?.startsWith(tmpdir()) && rmSync) {
16159
- rmSync(tempDir, { recursive: true, force: true });
17406
+ if (tempDir?.startsWith(tmpdir())) {
17407
+ safeRmSync(tempDir, { recursive: true, force: true });
16160
17408
  }
16161
17409
  return retList;
16162
17410
  }