@cdxgen/cdxgen 13.3.0 → 13.4.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 +9 -6
- package/bin/cdxgen.js +90 -26
- package/bin/evinse.js +18 -1
- package/bin/repl.js +86 -0
- package/bin/sign.js +45 -15
- package/bin/tracebom.js +5 -1
- package/bin/validate.js +84 -18
- package/bin/verify.js +155 -86
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +61 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/index.js +113 -54
- package/lib/cli/jsBom.js +50 -29
- package/lib/cli/jvmBom.js +665 -138
- package/lib/cli/managedBom.js +320 -200
- package/lib/cli/nativeBom.js +139 -30
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +17 -9
- package/lib/core/fs.js +3 -2
- package/lib/core/httpClient.js +159 -24
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +5 -7
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/cmakeResolver.js +10 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +337 -22
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1967 -788
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +314 -30
- package/lib/ecosystems/jsEvidence.js +85 -24
- package/lib/ecosystems/kosi.js +21 -4
- package/lib/ecosystems/npmutils.js +64 -0
- package/lib/ecosystems/parsers-dotnet.js +567 -151
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +324 -61
- package/lib/ecosystems/parsers-js.js +92 -26
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +182 -55
- package/lib/ecosystems/parsers-python.js +141 -10
- package/lib/ecosystems/parsers-rust.js +4 -4
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +67 -17
- package/lib/ecosystems/rusi.js +22 -9
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +578 -69
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +576 -128
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +171 -48
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/dosai.js +248 -44
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +52 -6
- package/lib/inventory/evidenceUtils.js +154 -109
- package/lib/inventory/fetchBatch.js +574 -38
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +8 -8
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +88 -7
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/docker.js +33 -32
- package/lib/managers/oci.js +24 -2
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +16 -2
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +15 -1
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/index.js +25 -10
- package/lib/validator/reporters/json.js +1 -0
- package/package.json +21 -21
- package/types/lib/audit/index.d.ts.map +1 -1
- 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 +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +9 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +18 -2
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +50 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +15 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +44 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +6 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +3 -1
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +21 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +75 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +80 -4
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +82 -10
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +57 -8
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +12 -0
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +3 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +2 -1
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +8 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing for plugin (rusi/golem/kosi) invocations: resolving the
|
|
3
|
+
* directory to analyze, and reporting a failed run.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isAbsolute, join, normalize, resolve, sep } from "node:path";
|
|
7
|
+
import process from "node:process";
|
|
8
|
+
|
|
9
|
+
import { DEBUG_MODE } from "./activity.js";
|
|
10
|
+
import { deferFailOnError } from "./deferredExit.js";
|
|
11
|
+
import { safeExistsSync } from "./fs.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The working directory, when a relative source argument names it.
|
|
15
|
+
*
|
|
16
|
+
* cdxgen is frequently launched with its working directory already set to the
|
|
17
|
+
* project (depscan does exactly that) while the path argument stays relative
|
|
18
|
+
* to some repository root, e.g. cwd `/work/repo/test/data/app` with argument
|
|
19
|
+
* `test/data/app`. A plain `path.resolve` then produces the doubled
|
|
20
|
+
* `/work/repo/test/data/app/test/data/app`. This recognises that shape and
|
|
21
|
+
* nothing looser: the argument's path segments must be the trailing segments
|
|
22
|
+
* of the working directory, so a mistyped argument run from some other
|
|
23
|
+
* project is never silently redirected to that project.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} src The source path as the caller typed it.
|
|
26
|
+
* @param {string} [cwd] Working directory; defaults to `process.cwd()`.
|
|
27
|
+
* @returns {string|undefined} The absolute working directory when the
|
|
28
|
+
* argument names it, otherwise undefined.
|
|
29
|
+
*/
|
|
30
|
+
export function cwdNamedByRelativeSource(src, cwd = process.cwd()) {
|
|
31
|
+
if (typeof src !== "string" || !src || isAbsolute(src)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
// normalize() converts separators to the platform's, so one split on sep
|
|
35
|
+
// handles both "a/b" and "a\\b" on Windows; on POSIX a backslash is an
|
|
36
|
+
// ordinary file-name character and must not split.
|
|
37
|
+
const relParts = normalize(src)
|
|
38
|
+
.split(sep)
|
|
39
|
+
.filter((part) => part && part !== ".");
|
|
40
|
+
if (!relParts.length || relParts.includes("..")) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const absCwd = resolve(cwd);
|
|
44
|
+
const cwdParts = absCwd.split(sep).filter(Boolean);
|
|
45
|
+
if (relParts.length > cwdParts.length) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const tail = cwdParts.slice(cwdParts.length - relParts.length);
|
|
49
|
+
const caseInsensitive = process.platform === "win32";
|
|
50
|
+
const same = (a, b) =>
|
|
51
|
+
caseInsensitive ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
52
|
+
return relParts.every((part, i) => same(part, tail[i])) ? absCwd : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the source path argument once, at cdxgen's entry point.
|
|
57
|
+
*
|
|
58
|
+
* Everything downstream — the BOM generators, evinse, and the rusi/golem/kosi
|
|
59
|
+
* launchers — resolves the argument against the working directory, so the
|
|
60
|
+
* doubled-directory shape has to be corrected before any of them run;
|
|
61
|
+
* correcting it only in the plugin launchers leaves the main BOM built from a
|
|
62
|
+
* directory that does not exist.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} src The source path as the caller typed it.
|
|
65
|
+
* @param {string} [cwd] Working directory; defaults to `process.cwd()`.
|
|
66
|
+
* @returns {{path: string, rewritten: boolean, missing: boolean}} `path` is
|
|
67
|
+
* the argument to use: unchanged when it resolves to an existing path, the
|
|
68
|
+
* working directory when the argument names it (`rewritten`), and otherwise
|
|
69
|
+
* unchanged with `missing` set.
|
|
70
|
+
*/
|
|
71
|
+
export function resolveSourcePathArgument(src, cwd = process.cwd()) {
|
|
72
|
+
if (typeof src !== "string" || !src) {
|
|
73
|
+
return { path: src, rewritten: false, missing: false };
|
|
74
|
+
}
|
|
75
|
+
if (safeExistsSync(resolve(cwd, src))) {
|
|
76
|
+
return { path: src, rewritten: false, missing: false };
|
|
77
|
+
}
|
|
78
|
+
const named = cwdNamedByRelativeSource(src, cwd);
|
|
79
|
+
if (named) {
|
|
80
|
+
return { path: named, rewritten: true, missing: false };
|
|
81
|
+
}
|
|
82
|
+
return { path: src, rewritten: false, missing: true };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve the directory a plugin should analyze.
|
|
87
|
+
*
|
|
88
|
+
* The entry point already corrects the doubled-directory shape (see
|
|
89
|
+
* resolveSourcePathArgument); this is the same rule for callers that reach a
|
|
90
|
+
* plugin launcher directly (evinse, the library API). An existing directory
|
|
91
|
+
* wins and an absolute path is never rewritten. Otherwise the working
|
|
92
|
+
* directory is used only when the argument names it AND it carries the
|
|
93
|
+
* project marker the plugin needs (a `Cargo.toml` for rusi, a `go.mod` for
|
|
94
|
+
* golem). Anything else keeps the resolved path so the plugin's own error
|
|
95
|
+
* names the directory that was actually attempted.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} src The source path as the caller typed it.
|
|
98
|
+
* @param {string[]} markerFiles File names that, present in the working
|
|
99
|
+
* directory, identify it as the project the plugin should analyze.
|
|
100
|
+
* @returns {string} The directory to pass to the plugin.
|
|
101
|
+
*/
|
|
102
|
+
export function resolvePluginSourceDir(src, markerFiles = []) {
|
|
103
|
+
const resolved = resolve(src);
|
|
104
|
+
if (safeExistsSync(resolved) || isAbsolute(src)) {
|
|
105
|
+
return resolved;
|
|
106
|
+
}
|
|
107
|
+
const cwd = cwdNamedByRelativeSource(src);
|
|
108
|
+
if (!cwd) {
|
|
109
|
+
return resolved;
|
|
110
|
+
}
|
|
111
|
+
for (const marker of markerFiles) {
|
|
112
|
+
if (marker && safeExistsSync(join(cwd, marker))) {
|
|
113
|
+
console.error(
|
|
114
|
+
`Resolved source directory ${resolved} does not exist; the working directory ${cwd} is the project the argument names (found ${marker}), using it instead.`,
|
|
115
|
+
);
|
|
116
|
+
return cwd;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return resolved;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Report a failed plugin run, keeping the two failure classes apart.
|
|
124
|
+
*
|
|
125
|
+
* A spawn error means the plugin binary itself could not run — not installed,
|
|
126
|
+
* not executable, blocked by the command policy — which is an installation
|
|
127
|
+
* problem. A non-zero exit status or a missing report means the plugin ran
|
|
128
|
+
* and the analysis failed, which must not be dressed up as an installation
|
|
129
|
+
* problem: the message names the directory and says the reachability
|
|
130
|
+
* evidence will be missing, because a silent degradation is what let a
|
|
131
|
+
* doubled `--dir` go unnoticed in the first place.
|
|
132
|
+
*
|
|
133
|
+
* Under `--fail-on-error` the failure additionally claims the process exit
|
|
134
|
+
* status through the deferred-exit contract.
|
|
135
|
+
*
|
|
136
|
+
* @param {Object} run What ran and how it failed.
|
|
137
|
+
* @param {string} run.tool Plugin name, e.g. "rusi".
|
|
138
|
+
* @param {string} run.ecosystem Ecosystem the plugin serves, e.g. "rust".
|
|
139
|
+
* @param {string} run.executable The binary that was invoked.
|
|
140
|
+
* @param {string[]} run.args The arguments it was invoked with.
|
|
141
|
+
* @param {string} run.dir The directory that was analyzed.
|
|
142
|
+
* @param {Object} run.result The spawn result.
|
|
143
|
+
* @param {string} run.outputFile The report file the plugin should have written.
|
|
144
|
+
* @param {Object} [run.options] CLI options, for the `--fail-on-error` contract.
|
|
145
|
+
* @returns {void}
|
|
146
|
+
*/
|
|
147
|
+
export function reportPluginRunFailure({
|
|
148
|
+
tool,
|
|
149
|
+
ecosystem,
|
|
150
|
+
executable,
|
|
151
|
+
args,
|
|
152
|
+
dir,
|
|
153
|
+
result,
|
|
154
|
+
outputFile,
|
|
155
|
+
options,
|
|
156
|
+
}) {
|
|
157
|
+
const command = `${executable} ${(args || []).join(" ")}`.trim();
|
|
158
|
+
if (result?.error) {
|
|
159
|
+
console.error(
|
|
160
|
+
`${tool} plugin could not be executed for ${dir}: ${result.error.message}. Check if the ${tool} plugin was installed successfully.`,
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
const status =
|
|
164
|
+
typeof result?.status === "number" ? result.status : "unknown";
|
|
165
|
+
const reason = safeExistsSync(outputFile)
|
|
166
|
+
? "exited unsuccessfully"
|
|
167
|
+
: "wrote no report";
|
|
168
|
+
console.error(
|
|
169
|
+
`${tool} analysis ${reason} for directory ${dir} (exit status ${status}); ${ecosystem} reachability evidence will be missing from the BOM.`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (DEBUG_MODE && (result?.stdout || result?.stderr)) {
|
|
173
|
+
console.error(result.stdout, result.stderr);
|
|
174
|
+
}
|
|
175
|
+
deferFailOnError(options, {
|
|
176
|
+
ecosystem,
|
|
177
|
+
tool,
|
|
178
|
+
detail: result?.error
|
|
179
|
+
? `${tool} plugin could not be executed: ${result.error.message}`
|
|
180
|
+
: `${tool} analysis failed for directory ${dir}`,
|
|
181
|
+
exitCode: typeof result?.status === "number" ? result.status : undefined,
|
|
182
|
+
command,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for external commands stopped by cdxgen's spawn timeout.
|
|
3
|
+
*
|
|
4
|
+
* Layer 0: this module imports nothing outside `lib/core`, so both the atom
|
|
5
|
+
* and the dosai runners (layer 2) can share it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { DEBUG_MODE } from "./activity.js";
|
|
9
|
+
import { safeSpawnSync } from "./fs.js";
|
|
10
|
+
import { isWin } from "./paths.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A duration for a message: seconds to one decimal below a minute, minutes to
|
|
14
|
+
* one decimal from there.
|
|
15
|
+
*
|
|
16
|
+
* @param {number} ms Duration in milliseconds
|
|
17
|
+
* @returns {string} Human-readable duration
|
|
18
|
+
*/
|
|
19
|
+
export function formatDuration(ms) {
|
|
20
|
+
if (ms >= 60_000) {
|
|
21
|
+
const minutes = Math.round(ms / 6_000) / 10;
|
|
22
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
23
|
+
}
|
|
24
|
+
return `${Math.round(ms / 100) / 10} seconds`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Stop the descendants a timed-out run left behind on Windows.
|
|
29
|
+
*
|
|
30
|
+
* The spawn timeout terminates only the process cdxgen spawned, and Windows
|
|
31
|
+
* neither kills nor re-parents its children: a shell's command, a runtime's
|
|
32
|
+
* workers, or the build hosts a .NET analyzer starts keep running while still
|
|
33
|
+
* pointing at the dead process's pid. `taskkill /T` on that pid finds nothing,
|
|
34
|
+
* because the root is already gone. The live descendants are therefore found
|
|
35
|
+
* by parent pid in a process snapshot and stopped, limited to processes
|
|
36
|
+
* created after the run started. The root's own children must also predate
|
|
37
|
+
* its end: once the root is gone its pid can be handed to an unrelated
|
|
38
|
+
* process, whose children would otherwise look like the run's. Elsewhere this
|
|
39
|
+
* does nothing: an orphan is re-parented and can no longer be told apart.
|
|
40
|
+
*
|
|
41
|
+
* @param {number|undefined} rootPid pid of the process cdxgen spawned
|
|
42
|
+
* @param {number} startedAt Epoch milliseconds at which the run started
|
|
43
|
+
* @param {number} [endedAt] Epoch milliseconds at which the spawned process ended
|
|
44
|
+
* @param {string} [toolName] Name of the tool, for the debug message
|
|
45
|
+
* @returns {number[]} The pids that were stopped
|
|
46
|
+
*/
|
|
47
|
+
export function reapProcessTree(
|
|
48
|
+
rootPid,
|
|
49
|
+
startedAt,
|
|
50
|
+
endedAt = Date.now(),
|
|
51
|
+
toolName = "external",
|
|
52
|
+
) {
|
|
53
|
+
if (!isWin || !Number.isInteger(rootPid) || rootPid <= 0) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
// All three values are integers, so interpolating them cannot inject anything.
|
|
57
|
+
const since = Math.max(0, Math.floor(startedAt) - 1000);
|
|
58
|
+
const until = Math.max(since, Math.floor(endedAt) + 1000);
|
|
59
|
+
const script = [
|
|
60
|
+
`$root = ${rootPid}`,
|
|
61
|
+
`$since = [DateTimeOffset]::FromUnixTimeMilliseconds(${since}).UtcDateTime`,
|
|
62
|
+
`$until = [DateTimeOffset]::FromUnixTimeMilliseconds(${until}).UtcDateTime`,
|
|
63
|
+
"$children = @{}",
|
|
64
|
+
"foreach ($p in Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate) {",
|
|
65
|
+
" if (-not $p.CreationDate) { continue }",
|
|
66
|
+
" $created = $p.CreationDate.ToUniversalTime()",
|
|
67
|
+
" if ($created -lt $since) { continue }",
|
|
68
|
+
" if ([int]$p.ParentProcessId -eq $root -and $created -gt $until) { continue }",
|
|
69
|
+
" $children[[int]$p.ParentProcessId] += @([int]$p.ProcessId)",
|
|
70
|
+
"}",
|
|
71
|
+
"$found = [System.Collections.Generic.List[int]]::new()",
|
|
72
|
+
"$queue = [System.Collections.Generic.Queue[int]]::new()",
|
|
73
|
+
"$queue.Enqueue($root)",
|
|
74
|
+
"while ($queue.Count) {",
|
|
75
|
+
" foreach ($child in @($children[$queue.Dequeue()])) {",
|
|
76
|
+
" if ($child -and -not $found.Contains($child)) { $found.Add($child); $queue.Enqueue($child) }",
|
|
77
|
+
" }",
|
|
78
|
+
"}",
|
|
79
|
+
"foreach ($child in $found) { Stop-Process -Id $child -Force -ErrorAction SilentlyContinue }",
|
|
80
|
+
"$found -join ','",
|
|
81
|
+
].join("\n");
|
|
82
|
+
const result = safeSpawnSync(
|
|
83
|
+
"powershell",
|
|
84
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
85
|
+
{ timeout: 60000 },
|
|
86
|
+
);
|
|
87
|
+
const reaped = `${result?.stdout || ""}`
|
|
88
|
+
.trim()
|
|
89
|
+
.split(",")
|
|
90
|
+
.map((pid) => Number.parseInt(pid, 10))
|
|
91
|
+
.filter((pid) => pid > 0);
|
|
92
|
+
if (DEBUG_MODE) {
|
|
93
|
+
console.log(
|
|
94
|
+
reaped.length
|
|
95
|
+
? `Stopped ${reaped.length} process(es) the timed-out ${toolName} run left behind: ${reaped.join(", ")}.`
|
|
96
|
+
: `The timed-out ${toolName} run left no processes behind.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return reaped;
|
|
100
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State that lasts for one run.
|
|
3
|
+
*
|
|
4
|
+
* Registry answers, prefetched documents, recorded misses and local cache
|
|
5
|
+
* lookups are kept in module state, so that a package several lockfiles name
|
|
6
|
+
* costs one lookup. A run is one BOM generation. The CLI exits when it ends, but
|
|
7
|
+
* a long-lived process such as the server performs many, and nothing a run
|
|
8
|
+
* remembered should outlive it: the registries publish new releases, builds
|
|
9
|
+
* download new artifacts, and the memory belongs to the next scan.
|
|
10
|
+
*
|
|
11
|
+
* Every module that keeps such state registers a reset when it loads. The
|
|
12
|
+
* server calls {@link resetRunState} between scans. A module that has not
|
|
13
|
+
* loaded yet holds nothing to reset.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** @type {Map<string, () => void>} */
|
|
17
|
+
const runResets = new Map();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Register the function that empties a module's per-run state.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} name Unique name, normally the module and what it holds
|
|
23
|
+
* @param {() => void} reset Empties that state
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*/
|
|
26
|
+
export function registerRunReset(name, reset) {
|
|
27
|
+
runResets.set(name, reset);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Empty the per-run state of every module that registered one.
|
|
32
|
+
*
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
35
|
+
export function resetRunState() {
|
|
36
|
+
for (const reset of runResets.values()) {
|
|
37
|
+
reset();
|
|
38
|
+
}
|
|
39
|
+
}
|
package/lib/core/toml.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOML parsing with stable, ordinary objects.
|
|
3
|
+
*
|
|
4
|
+
* smol-toml 1.9 began returning tables and inline tables with a `null`
|
|
5
|
+
* prototype so that a hostile `__proto__` key in a manifest cannot pollute
|
|
6
|
+
* `Object.prototype`. The SBOM data itself is unaffected, but cdxgen hands
|
|
7
|
+
* parsed manifests straight to callers (and `assert.deepStrictEqual` in the
|
|
8
|
+
* test suite), which compare by prototype and would see every table as a
|
|
9
|
+
* different kind of value. Re-wrapping the parse result with the standard
|
|
10
|
+
* prototype keeps the pre-1.9 contract: plain objects all the way down,
|
|
11
|
+
* `__proto__` keys included as ordinary own properties.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { parse as smolParse } from "smol-toml";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Recursively rebuild a parsed value with `Object.prototype` roots.
|
|
18
|
+
*
|
|
19
|
+
* Class instances other than plain tables (smol-toml dates, strings, numbers)
|
|
20
|
+
* are passed through untouched, and arrays are copied only when an element
|
|
21
|
+
* actually changes, so unmodified branches keep their original identity.
|
|
22
|
+
*
|
|
23
|
+
* @param {unknown} value value produced by smol-toml's parse
|
|
24
|
+
* @returns {unknown} the same data with plain-prototype objects
|
|
25
|
+
*/
|
|
26
|
+
function toPlainPrototype(value) {
|
|
27
|
+
if (!value || typeof value !== "object") {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
let changed = false;
|
|
32
|
+
const out = new Array(value.length);
|
|
33
|
+
for (let i = 0; i < value.length; i++) {
|
|
34
|
+
out[i] = toPlainPrototype(value[i]);
|
|
35
|
+
if (out[i] !== value[i]) {
|
|
36
|
+
changed = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return changed ? out : value;
|
|
40
|
+
}
|
|
41
|
+
const proto = Object.getPrototypeOf(value);
|
|
42
|
+
if (proto !== null && proto !== Object.prototype) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
47
|
+
if (key === "__proto__") {
|
|
48
|
+
// Plain assignment would re-point the new object's prototype instead
|
|
49
|
+
// of recording the key, reintroducing the pollution smol-toml guards
|
|
50
|
+
// against.
|
|
51
|
+
Object.defineProperty(out, key, {
|
|
52
|
+
value: toPlainPrototype(entry),
|
|
53
|
+
enumerable: true,
|
|
54
|
+
writable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
out[key] = toPlainPrototype(entry);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse a TOML document into plain objects.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} text TOML source
|
|
68
|
+
* @returns {Object} parsed document whose tables use `Object.prototype`
|
|
69
|
+
*/
|
|
70
|
+
export function parseToml(text) {
|
|
71
|
+
return toPlainPrototype(smolParse(text));
|
|
72
|
+
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "../inventory/analyzer.js";
|
|
31
31
|
import { tryBuildPurl } from "../inventory/purl.js";
|
|
32
32
|
import { xml2js } from "../parsers/xml.js";
|
|
33
|
+
import { resolveNpmLicense } from "./npmutils.js";
|
|
33
34
|
|
|
34
35
|
const ASAR_JS_ANALYSIS_EXTENSIONS = new Set([
|
|
35
36
|
".cjs",
|
|
@@ -1040,8 +1041,9 @@ function createArchiveParentComponent(
|
|
|
1040
1041
|
? primaryPackageJson.author
|
|
1041
1042
|
: primaryPackageJson.author?.name || "";
|
|
1042
1043
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1044
|
+
const license = resolveNpmLicense(primaryPackageJson);
|
|
1045
|
+
if (license) {
|
|
1046
|
+
component.license = license;
|
|
1045
1047
|
}
|
|
1046
1048
|
if (primaryPackageJson?.repository) {
|
|
1047
1049
|
const repositoryUrl =
|
|
@@ -3,8 +3,8 @@ import { dirname } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { build } from "@cdxgen/cdx-purl";
|
|
5
5
|
|
|
6
|
-
import { DEBUG_MODE
|
|
7
|
-
import {
|
|
6
|
+
import { DEBUG_MODE } from "../core/activity.js";
|
|
7
|
+
import { shouldFetchPackageMetadata } from "../core/env.js";
|
|
8
8
|
import { safeExistsSync } from "../core/fs.js";
|
|
9
9
|
import { getNpmMetadata } from "./ecosystems.js";
|
|
10
10
|
import {
|
|
@@ -554,11 +554,9 @@ export async function parseBunLock(bunLockFile, options = {}) {
|
|
|
554
554
|
});
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
readEnvironmentVariable("FETCH_LICENSE") === "true"
|
|
561
|
-
) {
|
|
557
|
+
// The shared metadata gate: FETCH_LICENSE and the provenance fetch that
|
|
558
|
+
// --bom-audit turns on both reach the registry from here.
|
|
559
|
+
if (shouldFetchPackageMetadata()) {
|
|
562
560
|
if (DEBUG_MODE) {
|
|
563
561
|
console.log(
|
|
564
562
|
`About to fetch npm registry metadata for ${pkgList.length} packages in parseBunLock`,
|