@cdxgen/cdxgen 13.0.1 → 13.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -13
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +125 -4
- package/bin/evinse.js +1 -1
- package/data/component-tags.json +8 -1
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1144 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/cli/bomAssembly.js +20 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +76 -48
- package/lib/cli/jsBom.js +32 -3
- package/lib/cli/jvmBom.js +1494 -1315
- package/lib/cli/managedBom.js +97 -28
- package/lib/cli/nativeBom.js +189 -10
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +69 -1
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/ecosystems/ecosystems.js +9 -0
- package/lib/ecosystems/gradleutils.js +355 -15
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-misc.js +16 -2
- package/lib/ecosystems/parsers-python.js +171 -35
- package/lib/ecosystems/parsers-rust.js +12 -2
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/evinser/evinser.js +30 -12
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/auditCategories.js +9 -0
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +431 -22
- package/lib/inventory/envcontext.js +206 -18
- package/lib/inventory/fetchBatch.js +57 -0
- package/lib/inventory/jvmToolEnv.js +593 -0
- package/lib/inventory/osinfo.js +1 -0
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +227 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +115 -2
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +14 -14
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +2 -11
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +33 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +20 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +5 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +8 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +20 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +9 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +189 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +57 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +2 -2
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -10,6 +10,14 @@ import {
|
|
|
10
10
|
isSecureMode,
|
|
11
11
|
readEnvironmentVariable,
|
|
12
12
|
} from "../core/activity.js";
|
|
13
|
+
import {
|
|
14
|
+
commandOutputText,
|
|
15
|
+
isLedgerEnabled,
|
|
16
|
+
LEDGER_EVENT_IMPACTS,
|
|
17
|
+
LEDGER_EVENT_KINDS,
|
|
18
|
+
recordDegradation,
|
|
19
|
+
recordLedgerEvent,
|
|
20
|
+
} from "../core/buildLedger.js";
|
|
13
21
|
import {
|
|
14
22
|
CARGO_CMD,
|
|
15
23
|
DOTNET_CMD,
|
|
@@ -33,7 +41,12 @@ import {
|
|
|
33
41
|
} from "../core/fs.js";
|
|
34
42
|
import { diagnosticStream } from "../core/logger.js";
|
|
35
43
|
import { dirNameStr, isMac, isWin } from "../core/paths.js";
|
|
44
|
+
import { isValidSdkmanVersion } from "./jvmToolEnv.js";
|
|
36
45
|
import { applyPurl, tryBuildPurl } from "./purl.js";
|
|
46
|
+
import {
|
|
47
|
+
classifySpawnRestriction,
|
|
48
|
+
describeSpawnRestriction,
|
|
49
|
+
} from "./toolRequirements.js";
|
|
37
50
|
|
|
38
51
|
/**
|
|
39
52
|
* Git executable name used for all git invocations, overridable via GIT_CMD.
|
|
@@ -205,12 +218,17 @@ export function execGitCommand(dir, args) {
|
|
|
205
218
|
// Prepend hardening `-c` overrides before the subcommand and run git with a
|
|
206
219
|
// hardened environment so a scanned/untrusted repo cannot execute hooks,
|
|
207
220
|
// fsmonitor commands, or config-driven scripts.
|
|
221
|
+
// git is a real executable, so Windows can spawn it without a shell. Doing
|
|
222
|
+
// so keeps the pretty-format arguments intact: `%H`, `%n` and `%x1e` are
|
|
223
|
+
// cmd.exe variable-expansion syntax, which the shell-execution guard rightly
|
|
224
|
+
// refuses to pass through a shell.
|
|
208
225
|
return getCommandOutput(
|
|
209
226
|
GIT_COMMAND,
|
|
210
227
|
dir,
|
|
211
228
|
[...GIT_HARDENING_CONFIG_ARGS, ...args],
|
|
212
229
|
{
|
|
213
230
|
env: hardenedGitEnv(),
|
|
231
|
+
shell: false,
|
|
214
232
|
},
|
|
215
233
|
);
|
|
216
234
|
}
|
|
@@ -292,6 +310,60 @@ export function gitLogTrailers(dir, maxCount = 20) {
|
|
|
292
310
|
return commits;
|
|
293
311
|
}
|
|
294
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Record a successful version probe with the version the tool reported.
|
|
315
|
+
*
|
|
316
|
+
* @param {string} tool Tool name
|
|
317
|
+
* @param {string} ecosystem Ecosystem the tool belongs to
|
|
318
|
+
* @param {string} found Version string reported by the tool
|
|
319
|
+
*/
|
|
320
|
+
function recordToolProbeResolved(tool, ecosystem, found) {
|
|
321
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
322
|
+
ecosystem,
|
|
323
|
+
tool,
|
|
324
|
+
found,
|
|
325
|
+
source: "PATH",
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Record a failed version probe. A probe the environment did not let run is
|
|
331
|
+
* degraded evidence — the tool may well be present — while only a probe that
|
|
332
|
+
* ran and found nothing counts as a missing tool.
|
|
333
|
+
*
|
|
334
|
+
* @param {string} tool Tool name
|
|
335
|
+
* @param {string} ecosystem Ecosystem the tool belongs to
|
|
336
|
+
* @param {string} command Probe command that failed
|
|
337
|
+
*/
|
|
338
|
+
function recordToolProbeFailure(tool, ecosystem, command) {
|
|
339
|
+
if (!isLedgerEnabled()) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const restriction = describeSpawnRestriction(command);
|
|
343
|
+
if (restriction) {
|
|
344
|
+
// A dry-run block is the user's own request, so it costs nothing; any
|
|
345
|
+
// other policy restriction hides a version the tool may well have.
|
|
346
|
+
const isDryRunBlock = classifySpawnRestriction(command) === "dry-run";
|
|
347
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.EVIDENCE_DEGRADED, {
|
|
348
|
+
ecosystem,
|
|
349
|
+
tool,
|
|
350
|
+
source: "PATH",
|
|
351
|
+
remediationId: isDryRunBlock ? "policy.dry-run" : "policy.secure-mode",
|
|
352
|
+
impact: isDryRunBlock
|
|
353
|
+
? LEDGER_EVENT_IMPACTS.NONE
|
|
354
|
+
: LEDGER_EVENT_IMPACTS.VERSIONS,
|
|
355
|
+
detail: `The ${tool} version probe could not run: ${restriction}.`,
|
|
356
|
+
});
|
|
357
|
+
} else {
|
|
358
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
359
|
+
ecosystem,
|
|
360
|
+
tool,
|
|
361
|
+
source: "PATH",
|
|
362
|
+
detail: `No ${tool} command answered the version probe.`,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
295
367
|
/**
|
|
296
368
|
* Collect Java version and installed modules
|
|
297
369
|
*
|
|
@@ -303,7 +375,7 @@ export function collectJavaInfo(dir) {
|
|
|
303
375
|
const moduleDesc =
|
|
304
376
|
getCommandOutput(getJavaCommand(), dir, ["--list-modules"]) || "";
|
|
305
377
|
if (versionDesc) {
|
|
306
|
-
|
|
378
|
+
const info = {
|
|
307
379
|
type: "platform",
|
|
308
380
|
name: "java",
|
|
309
381
|
version: versionDesc.split("\n")[0].replace("java ", ""),
|
|
@@ -315,7 +387,10 @@ export function collectJavaInfo(dir) {
|
|
|
315
387
|
},
|
|
316
388
|
],
|
|
317
389
|
};
|
|
390
|
+
recordToolProbeResolved("java", "java", info.version);
|
|
391
|
+
return info;
|
|
318
392
|
}
|
|
393
|
+
recordToolProbeFailure("java", "java", getJavaCommand());
|
|
319
394
|
return undefined;
|
|
320
395
|
}
|
|
321
396
|
|
|
@@ -330,13 +405,16 @@ export function collectDotnetInfo(dir) {
|
|
|
330
405
|
const moduleDesc =
|
|
331
406
|
getCommandOutput(DOTNET_CMD, dir, ["--list-runtimes"]) || "";
|
|
332
407
|
if (versionDesc) {
|
|
333
|
-
|
|
408
|
+
const info = {
|
|
334
409
|
type: "platform",
|
|
335
410
|
name: "dotnet",
|
|
336
411
|
version: versionDesc.trim(),
|
|
337
412
|
description: moduleDesc.replaceAll("\n", "\\n"),
|
|
338
413
|
};
|
|
414
|
+
recordToolProbeResolved("dotnet", "csharp", info.version);
|
|
415
|
+
return info;
|
|
339
416
|
}
|
|
417
|
+
recordToolProbeFailure("dotnet", "csharp", DOTNET_CMD);
|
|
340
418
|
return undefined;
|
|
341
419
|
}
|
|
342
420
|
|
|
@@ -347,25 +425,21 @@ export function collectDotnetInfo(dir) {
|
|
|
347
425
|
* @returns Object containing python details
|
|
348
426
|
*/
|
|
349
427
|
export function collectPythonInfo(dir) {
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
"--version",
|
|
353
|
-
]);
|
|
428
|
+
const pythonCmd = getPythonCommand();
|
|
429
|
+
const versionDesc = getCommandOutput(pythonCmd, dir, ["-S", "--version"]);
|
|
354
430
|
const moduleDesc =
|
|
355
|
-
getCommandOutput(
|
|
356
|
-
"-I",
|
|
357
|
-
"-m",
|
|
358
|
-
"pip",
|
|
359
|
-
"--version",
|
|
360
|
-
]) || "";
|
|
431
|
+
getCommandOutput(pythonCmd, dir, ["-I", "-m", "pip", "--version"]) || "";
|
|
361
432
|
if (versionDesc) {
|
|
362
|
-
|
|
433
|
+
const info = {
|
|
363
434
|
type: "platform",
|
|
364
435
|
name: "python",
|
|
365
436
|
version: versionDesc.replace("Python ", ""),
|
|
366
437
|
description: moduleDesc.replaceAll("\n", "\\n"),
|
|
367
438
|
};
|
|
439
|
+
recordToolProbeResolved("python", "python", info.version);
|
|
440
|
+
return info;
|
|
368
441
|
}
|
|
442
|
+
recordToolProbeFailure("python", "python", pythonCmd);
|
|
369
443
|
return undefined;
|
|
370
444
|
}
|
|
371
445
|
|
|
@@ -386,6 +460,9 @@ export function collectNodeInfo(dir) {
|
|
|
386
460
|
const moduleDesc = getCommandOutput(NPM_CMD, dir, ["--version"]);
|
|
387
461
|
if (moduleDesc) {
|
|
388
462
|
nodeInfo.description = `npm: ${moduleDesc}`;
|
|
463
|
+
recordToolProbeResolved("npm", "npm", moduleDesc);
|
|
464
|
+
} else {
|
|
465
|
+
recordToolProbeFailure("npm", "npm", NPM_CMD);
|
|
389
466
|
}
|
|
390
467
|
return nodeInfo;
|
|
391
468
|
}
|
|
@@ -400,13 +477,16 @@ export function collectGccInfo(dir) {
|
|
|
400
477
|
const versionDesc = getCommandOutput(GCC_CMD, dir, ["--version"]);
|
|
401
478
|
const moduleDesc = getCommandOutput(GCC_CMD, dir, ["-print-search-dirs"]);
|
|
402
479
|
if (versionDesc) {
|
|
403
|
-
|
|
480
|
+
const info = {
|
|
404
481
|
type: "platform",
|
|
405
482
|
name: "gcc",
|
|
406
483
|
version: versionDesc.split("\n")[0],
|
|
407
484
|
description: (moduleDesc || "").replaceAll("\n", "\\n"),
|
|
408
485
|
};
|
|
486
|
+
recordToolProbeResolved("gcc", "c", info.version);
|
|
487
|
+
return info;
|
|
409
488
|
}
|
|
489
|
+
recordToolProbeFailure("gcc", "c", GCC_CMD);
|
|
410
490
|
return undefined;
|
|
411
491
|
}
|
|
412
492
|
|
|
@@ -420,13 +500,16 @@ export function collectRustInfo(dir) {
|
|
|
420
500
|
const versionDesc = getCommandOutput(RUSTC_CMD, dir, ["--version"]);
|
|
421
501
|
const moduleDesc = getCommandOutput(CARGO_CMD, dir, ["--version"]);
|
|
422
502
|
if (versionDesc) {
|
|
423
|
-
|
|
503
|
+
const info = {
|
|
424
504
|
type: "platform",
|
|
425
505
|
name: "rustc",
|
|
426
506
|
version: versionDesc.trim(),
|
|
427
507
|
description: (moduleDesc || "").trim(),
|
|
428
508
|
};
|
|
509
|
+
recordToolProbeResolved("rustc", "rust", info.version);
|
|
510
|
+
return info;
|
|
429
511
|
}
|
|
512
|
+
recordToolProbeFailure("rustc", "rust", RUSTC_CMD);
|
|
430
513
|
return undefined;
|
|
431
514
|
}
|
|
432
515
|
|
|
@@ -439,12 +522,15 @@ export function collectRustInfo(dir) {
|
|
|
439
522
|
export function collectGoInfo(dir) {
|
|
440
523
|
const versionDesc = getCommandOutput(GO_CMD, dir, ["version"]);
|
|
441
524
|
if (versionDesc) {
|
|
442
|
-
|
|
525
|
+
const info = {
|
|
443
526
|
type: "platform",
|
|
444
527
|
name: "go",
|
|
445
528
|
version: versionDesc.trim(),
|
|
446
529
|
};
|
|
530
|
+
recordToolProbeResolved("go", "go", info.version);
|
|
531
|
+
return info;
|
|
447
532
|
}
|
|
533
|
+
recordToolProbeFailure("go", "go", GO_CMD);
|
|
448
534
|
return undefined;
|
|
449
535
|
}
|
|
450
536
|
|
|
@@ -457,12 +543,15 @@ export function collectGoInfo(dir) {
|
|
|
457
543
|
export function collectSwiftInfo(dir) {
|
|
458
544
|
const versionDesc = getCommandOutput(SWIFT_CMD, dir, ["--version"]);
|
|
459
545
|
if (versionDesc) {
|
|
460
|
-
|
|
546
|
+
const info = {
|
|
461
547
|
type: "platform",
|
|
462
548
|
name: "swift",
|
|
463
549
|
version: versionDesc.trim(),
|
|
464
550
|
};
|
|
551
|
+
recordToolProbeResolved("swift", "swift", info.version);
|
|
552
|
+
return info;
|
|
465
553
|
}
|
|
554
|
+
recordToolProbeFailure("swift", "swift", SWIFT_CMD);
|
|
466
555
|
return undefined;
|
|
467
556
|
}
|
|
468
557
|
|
|
@@ -475,12 +564,15 @@ export function collectSwiftInfo(dir) {
|
|
|
475
564
|
export function collectRubyInfo(dir) {
|
|
476
565
|
const versionDesc = getCommandOutput(RUBY_CMD, dir, ["--version"]);
|
|
477
566
|
if (versionDesc) {
|
|
478
|
-
|
|
567
|
+
const info = {
|
|
479
568
|
type: "platform",
|
|
480
569
|
name: "ruby",
|
|
481
570
|
version: versionDesc.trim(),
|
|
482
571
|
};
|
|
572
|
+
recordToolProbeResolved("ruby", "ruby", info.version);
|
|
573
|
+
return info;
|
|
483
574
|
}
|
|
575
|
+
recordToolProbeFailure("ruby", "ruby", RUBY_CMD);
|
|
484
576
|
return undefined;
|
|
485
577
|
}
|
|
486
578
|
|
|
@@ -667,6 +759,17 @@ export function installSdkmanTool(toolType, toolName) {
|
|
|
667
759
|
return false;
|
|
668
760
|
}
|
|
669
761
|
toolName = getSdkmanToolFullname(toolName);
|
|
762
|
+
// The version is interpolated into an interactive shell command, so reject
|
|
763
|
+
// any identifier that is not a single safe token before spawning.
|
|
764
|
+
if (
|
|
765
|
+
!/^[a-z][a-z0-9-]*$/.test(`${toolType}`) ||
|
|
766
|
+
!isValidSdkmanVersion(toolName)
|
|
767
|
+
) {
|
|
768
|
+
console.log(
|
|
769
|
+
`Unable to install ${toolType} with an invalid version identifier '${toolName}'. Use exact sdkman version identifiers such as '3.9.9' or '21.0.7-tem'.`,
|
|
770
|
+
);
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
670
773
|
let result;
|
|
671
774
|
if (!isSdkmanToolAvailable(toolType, toolName)) {
|
|
672
775
|
let installDir = "";
|
|
@@ -968,6 +1071,14 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
968
1071
|
if (result.stderr) {
|
|
969
1072
|
console.log(result.stderr);
|
|
970
1073
|
}
|
|
1074
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1075
|
+
ecosystem: "ruby",
|
|
1076
|
+
tool: "rbenv",
|
|
1077
|
+
command: `rbenv local ${rubyVersion}`,
|
|
1078
|
+
exitCode: typeof result.status === "number" ? result.status : undefined,
|
|
1079
|
+
detail: `Selecting Ruby ${rubyVersion} with rbenv local did not succeed.`,
|
|
1080
|
+
outputExcerpt: commandOutputText(result),
|
|
1081
|
+
});
|
|
971
1082
|
}
|
|
972
1083
|
if (result.status === 0) {
|
|
973
1084
|
return { fullToolBinDir, status: true };
|
|
@@ -975,6 +1086,14 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
975
1086
|
}
|
|
976
1087
|
// Check if we're trying to install Ruby 1.x or 2.x
|
|
977
1088
|
if (rubyVersion.startsWith("1.")) {
|
|
1089
|
+
recordDegradation("ruby.version.unsupported-host", {
|
|
1090
|
+
ecosystem: "ruby",
|
|
1091
|
+
tool: "ruby",
|
|
1092
|
+
impact: "transitive-deps",
|
|
1093
|
+
wanted: rubyVersion,
|
|
1094
|
+
detail:
|
|
1095
|
+
"Building Ruby 1.x on this host is unsupported; a matching legacy container image is required to build this project.",
|
|
1096
|
+
});
|
|
978
1097
|
console.log(
|
|
979
1098
|
`Ruby version ${rubyVersion} requires very old versions of Linux such as debian:8. Consider using the container image "ghcr.io/cdxgen/debian-ruby18:master" to build the application first and then invoke cdxgen with the arguments "--lifecycle pre-build".`,
|
|
980
1099
|
);
|
|
@@ -983,6 +1102,14 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
983
1102
|
rubyVersion.startsWith("2.") &&
|
|
984
1103
|
readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true"
|
|
985
1104
|
) {
|
|
1105
|
+
recordDegradation("ruby.build-deps.missing", {
|
|
1106
|
+
ecosystem: "ruby",
|
|
1107
|
+
tool: "ruby",
|
|
1108
|
+
impact: "transitive-deps",
|
|
1109
|
+
wanted: rubyVersion,
|
|
1110
|
+
detail:
|
|
1111
|
+
"Building Ruby 2.x on this host requires development libraries that are not installed.",
|
|
1112
|
+
});
|
|
986
1113
|
console.log(
|
|
987
1114
|
`Installing Ruby version ${rubyVersion} requires specific development libraries. Consider using the custom container image "ghcr.io/cdxgen/cdxgen-debian-ruby26:v13" instead.`,
|
|
988
1115
|
);
|
|
@@ -996,6 +1123,12 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
996
1123
|
`To speed up this step, use bind mounts. Example: "--mount type=bind,src=/tmp/rbenv,dst=/root/.rbenv/versions/${rubyVersion}"`,
|
|
997
1124
|
);
|
|
998
1125
|
}
|
|
1126
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_ATTEMPTED, {
|
|
1127
|
+
ecosystem: "ruby",
|
|
1128
|
+
tool: "rbenv",
|
|
1129
|
+
command: `rbenv install ${rubyVersion}`,
|
|
1130
|
+
detail: `Installing Ruby ${rubyVersion} with rbenv.`,
|
|
1131
|
+
});
|
|
999
1132
|
const result = safeSpawnSync(
|
|
1000
1133
|
readEnvironmentVariable("RBENV_CMD") || "rbenv",
|
|
1001
1134
|
["install", rubyVersion],
|
|
@@ -1004,6 +1137,14 @@ export function installRubyVersion(rubyVersion, filePath) {
|
|
|
1004
1137
|
},
|
|
1005
1138
|
);
|
|
1006
1139
|
if (result.error || result.status !== 0) {
|
|
1140
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1141
|
+
ecosystem: "ruby",
|
|
1142
|
+
tool: "rbenv",
|
|
1143
|
+
command: `rbenv install ${rubyVersion}`,
|
|
1144
|
+
exitCode: typeof result.status === "number" ? result.status : undefined,
|
|
1145
|
+
detail: `The rbenv install of Ruby ${rubyVersion} did not succeed.`,
|
|
1146
|
+
outputExcerpt: commandOutputText(result),
|
|
1147
|
+
});
|
|
1007
1148
|
if (result.stdout) {
|
|
1008
1149
|
console.log(result.stdout);
|
|
1009
1150
|
}
|
|
@@ -1129,6 +1270,13 @@ export function performBundleInstall(
|
|
|
1129
1270
|
console.log(
|
|
1130
1271
|
`Invoking ${bundleCommand} ${installArgs.join(" ")} from ${basePath} with GEM_HOME ${cdxgenGemHome}. Please wait ...`,
|
|
1131
1272
|
);
|
|
1273
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_ATTEMPTED, {
|
|
1274
|
+
ecosystem: "ruby",
|
|
1275
|
+
tool: "bundler",
|
|
1276
|
+
command: `${bundleCommand} ${installArgs.join(" ")}`,
|
|
1277
|
+
path: basePath,
|
|
1278
|
+
detail: "Installing the project dependencies with bundler.",
|
|
1279
|
+
});
|
|
1132
1280
|
let result = safeSpawnSync(bundleCommand, installArgs, {
|
|
1133
1281
|
shell: isWin,
|
|
1134
1282
|
cwd: basePath,
|
|
@@ -1138,6 +1286,15 @@ export function performBundleInstall(
|
|
|
1138
1286
|
},
|
|
1139
1287
|
});
|
|
1140
1288
|
if (result.error || result.status !== 0) {
|
|
1289
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1290
|
+
ecosystem: "ruby",
|
|
1291
|
+
tool: "bundler",
|
|
1292
|
+
command: `${bundleCommand} ${installArgs.join(" ")}`,
|
|
1293
|
+
path: basePath,
|
|
1294
|
+
exitCode: typeof result.status === "number" ? result.status : undefined,
|
|
1295
|
+
detail: "The bundle install did not succeed.",
|
|
1296
|
+
outputExcerpt: commandOutputText(result),
|
|
1297
|
+
});
|
|
1141
1298
|
let pythonWarningShown = false;
|
|
1142
1299
|
let rubyVersionWarningShown = false;
|
|
1143
1300
|
if (
|
|
@@ -1184,6 +1341,13 @@ export function performBundleInstall(
|
|
|
1184
1341
|
);
|
|
1185
1342
|
}
|
|
1186
1343
|
console.log(`${bundleCommand}`);
|
|
1344
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_ATTEMPTED, {
|
|
1345
|
+
ecosystem: "ruby",
|
|
1346
|
+
tool: "bundler",
|
|
1347
|
+
command: `${bundleCommand} ${updateArgs.join(" ")}`,
|
|
1348
|
+
path: basePath,
|
|
1349
|
+
detail: "Updating the outdated lockfile with bundler.",
|
|
1350
|
+
});
|
|
1187
1351
|
result = safeSpawnSync(bundleCommand, updateArgs, {
|
|
1188
1352
|
shell: isWin,
|
|
1189
1353
|
cwd: basePath,
|
|
@@ -1193,6 +1357,16 @@ export function performBundleInstall(
|
|
|
1193
1357
|
},
|
|
1194
1358
|
});
|
|
1195
1359
|
if (result.error || result.status !== 0) {
|
|
1360
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1361
|
+
ecosystem: "ruby",
|
|
1362
|
+
tool: "bundler",
|
|
1363
|
+
command: `${bundleCommand} ${updateArgs.join(" ")}`,
|
|
1364
|
+
path: basePath,
|
|
1365
|
+
exitCode:
|
|
1366
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1367
|
+
detail: "The bundle update did not succeed.",
|
|
1368
|
+
outputExcerpt: commandOutputText(result),
|
|
1369
|
+
});
|
|
1196
1370
|
console.log("------------");
|
|
1197
1371
|
if (result.stdout) {
|
|
1198
1372
|
console.log(result.stdout);
|
|
@@ -1740,6 +1914,20 @@ export function getRuntimeInformation() {
|
|
|
1740
1914
|
runtimeInfo.version = "N/A";
|
|
1741
1915
|
}
|
|
1742
1916
|
|
|
1917
|
+
// The runtime hosting the cdxgen run is part of the report's
|
|
1918
|
+
// reproducibility context, so it is recorded alongside the scanned tools.
|
|
1919
|
+
const runtimeTools = { Deno: "deno", Bun: "bun", "Node.js": "node" };
|
|
1920
|
+
const runtimeTool = runtimeTools[runtimeInfo.runtime];
|
|
1921
|
+
if (runtimeTool && runtimeInfo.version && runtimeInfo.version !== "N/A") {
|
|
1922
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
1923
|
+
ecosystem: "generic",
|
|
1924
|
+
tool: runtimeTool,
|
|
1925
|
+
found: runtimeInfo.version,
|
|
1926
|
+
source: "runtime",
|
|
1927
|
+
detail:
|
|
1928
|
+
"The runtime hosting this cdxgen run, recorded for report reproducibility.",
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1743
1931
|
return runtimeInfo;
|
|
1744
1932
|
}
|
|
1745
1933
|
|
|
@@ -34,10 +34,13 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import {
|
|
37
|
+
BLOCKED_HOST_ERROR_CODE,
|
|
37
38
|
cdxgenAgent,
|
|
38
39
|
DEBUG_MODE,
|
|
40
|
+
DRY_RUN_ERROR_CODE,
|
|
39
41
|
readEnvironmentVariable,
|
|
40
42
|
} from "../core/activity.js";
|
|
43
|
+
import { isLedgerEnabled, recordDegradation } from "../core/buildLedger.js";
|
|
41
44
|
import { resolveCacheDir } from "./cacheDir.js";
|
|
42
45
|
import { cdxrsAvailable, runCdxrs } from "./cdxrs.js";
|
|
43
46
|
import {
|
|
@@ -545,6 +548,59 @@ async function issueWithRetries(request, limiter) {
|
|
|
545
548
|
}
|
|
546
549
|
}
|
|
547
550
|
|
|
551
|
+
/** Policy and connectivity degradations already recorded this run; these describe the run, not one URL. */
|
|
552
|
+
const recordedPolicyDegradations = new Set();
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Record a run-level policy or connectivity degradation once per process. A
|
|
556
|
+
* batch touches many URLs, but "dry-run blocked the network", "a host was not
|
|
557
|
+
* permitted" and "the network is unreachable" are properties of the run, so
|
|
558
|
+
* only the first occurrence is recorded and the report reads one line, not
|
|
559
|
+
* one per URL.
|
|
560
|
+
*
|
|
561
|
+
* @param {string} remediationId Remediation id from data/remediations.json
|
|
562
|
+
* @param {Object} fields Ledger event fields
|
|
563
|
+
* @returns {void}
|
|
564
|
+
*/
|
|
565
|
+
function recordPolicyDegradationOnce(remediationId, fields) {
|
|
566
|
+
if (!isLedgerEnabled() || recordedPolicyDegradations.has(remediationId)) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
recordedPolicyDegradations.add(remediationId);
|
|
570
|
+
recordDegradation(remediationId, fields);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Record the run-level policy or connectivity condition carried by a caught
|
|
575
|
+
* fetch error, if it names one. Shared by the batch retry loop and by the
|
|
576
|
+
* serial enrichment paths that catch and swallow the same typed errors.
|
|
577
|
+
*
|
|
578
|
+
* @param {Error} err Error caught from a `cdxgenAgent` request.
|
|
579
|
+
* @returns {void}
|
|
580
|
+
*/
|
|
581
|
+
export function recordPolicyDegradationFromError(err) {
|
|
582
|
+
if (err?.code === DRY_RUN_ERROR_CODE) {
|
|
583
|
+
recordPolicyDegradationOnce("policy.dry-run", {
|
|
584
|
+
ecosystem: "generic",
|
|
585
|
+
impact: "none",
|
|
586
|
+
detail: `Dry-run mode blocked a registry request: ${err.message}`,
|
|
587
|
+
});
|
|
588
|
+
} else if (err?.code === BLOCKED_HOST_ERROR_CODE) {
|
|
589
|
+
recordPolicyDegradationOnce("policy.host-blocked", {
|
|
590
|
+
ecosystem: "generic",
|
|
591
|
+
impact: "licenses",
|
|
592
|
+
detail: `A registry request was blocked by policy: ${err.message}`,
|
|
593
|
+
});
|
|
594
|
+
} else if (err?.name === "RequestError") {
|
|
595
|
+
recordPolicyDegradationOnce("policy.offline", {
|
|
596
|
+
ecosystem: "generic",
|
|
597
|
+
impact: "licenses",
|
|
598
|
+
detail:
|
|
599
|
+
"A registry request failed with a transport error, so registry metadata and license enrichment may be missing.",
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
548
604
|
/**
|
|
549
605
|
* Classify an error thrown by `cdxgenAgent.get` into a retry decision and the
|
|
550
606
|
* batch entry to record when retries are exhausted.
|
|
@@ -553,6 +609,7 @@ async function issueWithRetries(request, limiter) {
|
|
|
553
609
|
* @returns {{retryable: boolean, delay: number|null, entry: BatchEntry}}
|
|
554
610
|
*/
|
|
555
611
|
function classifyAgentError(err) {
|
|
612
|
+
recordPolicyDegradationFromError(err);
|
|
556
613
|
// Secure-mode and dry-run blocks must not be retried, and the caller's own
|
|
557
614
|
// fallback `cdxgenAgent.get` will surface the same error. Recording this as
|
|
558
615
|
// non-definite lets the caller's catch run exactly as it would on the serial
|