@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
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
|
|
4
|
-
import * as toml from "smol-toml";
|
|
5
|
-
|
|
6
4
|
import {
|
|
7
5
|
sanitizeBomPropertyValue,
|
|
8
6
|
sanitizeBomUrl,
|
|
9
7
|
} from "../core/propertySanitizer.js";
|
|
8
|
+
import { parseToml } from "../core/toml.js";
|
|
10
9
|
import { parseJsonLike } from "./jsonLike.js";
|
|
11
10
|
import { classifyMcpReference } from "./mcp.js";
|
|
12
11
|
import {
|
|
@@ -813,7 +812,7 @@ export const mcpConfigParser = {
|
|
|
813
812
|
let configJson;
|
|
814
813
|
try {
|
|
815
814
|
configJson = filePath.toLowerCase().endsWith(".toml")
|
|
816
|
-
?
|
|
815
|
+
? parseToml(raw)
|
|
817
816
|
: parseJsonLike(raw);
|
|
818
817
|
} catch {
|
|
819
818
|
continue;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which detected project becomes the parent (`metadata.component`) when one
|
|
3
|
+
* scan covers several ecosystems and no parent was given.
|
|
4
|
+
*
|
|
5
|
+
* Each ecosystem detects its own project from its own manifest. The project
|
|
6
|
+
* whose manifest sits at the scan root describes the repository; one found
|
|
7
|
+
* deeper is a part of it (a web client in `tools/ui/`, a helper script's
|
|
8
|
+
* `pyproject.toml`). So candidates are ordered by how deep their ecosystem's
|
|
9
|
+
* manifests sit, the scan root first, and ties keep the ecosystem order the
|
|
10
|
+
* scan ran in.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readdirSync } from "node:fs";
|
|
14
|
+
import { dirname, relative, resolve, sep } from "node:path";
|
|
15
|
+
|
|
16
|
+
import { getAllFiles } from "../core/fs.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Manifest file names (or `*.ext` suffixes) per purl type: the files a
|
|
20
|
+
* project of that ecosystem is described by.
|
|
21
|
+
*/
|
|
22
|
+
const MANIFESTS_BY_PURL_TYPE = {
|
|
23
|
+
npm: ["package.json"],
|
|
24
|
+
pypi: [
|
|
25
|
+
"pyproject.toml",
|
|
26
|
+
"setup.py",
|
|
27
|
+
"setup.cfg",
|
|
28
|
+
"Pipfile",
|
|
29
|
+
"requirements.txt",
|
|
30
|
+
"uv.lock",
|
|
31
|
+
"poetry.lock",
|
|
32
|
+
"pixi.toml",
|
|
33
|
+
],
|
|
34
|
+
generic: [
|
|
35
|
+
"CMakeLists.txt",
|
|
36
|
+
"meson.build",
|
|
37
|
+
"conanfile.txt",
|
|
38
|
+
"conanfile.py",
|
|
39
|
+
"vcpkg.json",
|
|
40
|
+
"xmake.lua",
|
|
41
|
+
],
|
|
42
|
+
maven: ["pom.xml", "build.gradle", "build.gradle.kts", "build.sbt"],
|
|
43
|
+
golang: ["go.mod"],
|
|
44
|
+
cargo: ["Cargo.toml"],
|
|
45
|
+
composer: ["composer.json"],
|
|
46
|
+
gem: ["Gemfile", "*.gemspec"],
|
|
47
|
+
nuget: ["*.csproj", "*.fsproj", "*.vbproj", "*.sln", "packages.config"],
|
|
48
|
+
pub: ["pubspec.yaml"],
|
|
49
|
+
hex: ["mix.exs"],
|
|
50
|
+
hackage: ["*.cabal", "stack.yaml"],
|
|
51
|
+
swift: ["Package.swift"],
|
|
52
|
+
cocoapods: ["Podfile"],
|
|
53
|
+
clojars: ["deps.edn", "project.clj"],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** The purl type of a component, or `undefined`. */
|
|
57
|
+
function purlTypeOf(component) {
|
|
58
|
+
const purl = `${component?.purl || ""}`;
|
|
59
|
+
if (!purl.startsWith("pkg:")) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const rest = purl.slice(4);
|
|
63
|
+
const end = rest.search(/[/@?#]/);
|
|
64
|
+
return (end < 0 ? rest : rest.slice(0, end)).toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function matchesManifest(name, patterns) {
|
|
68
|
+
return patterns.some((p) =>
|
|
69
|
+
p.startsWith("*.") ? name.endsWith(p.slice(1)) : name === p,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* How deep the shallowest manifest of an ecosystem sits under a scan root:
|
|
75
|
+
* 0 for one at the root itself, `Infinity` when there is none (or the
|
|
76
|
+
* ecosystem is not one this module knows).
|
|
77
|
+
*
|
|
78
|
+
* @param {string} root Scan root
|
|
79
|
+
* @param {string} purlType Purl type of the ecosystem
|
|
80
|
+
* @param {Object} options CLI options (exclusions)
|
|
81
|
+
* @returns {number}
|
|
82
|
+
*/
|
|
83
|
+
export function manifestDepth(root, purlType, options = {}) {
|
|
84
|
+
const patterns = MANIFESTS_BY_PURL_TYPE[purlType];
|
|
85
|
+
if (!patterns || !root) {
|
|
86
|
+
return Number.POSITIVE_INFINITY;
|
|
87
|
+
}
|
|
88
|
+
const absRoot = resolve(root);
|
|
89
|
+
try {
|
|
90
|
+
if (readdirSync(absRoot).some((name) => matchesManifest(name, patterns))) {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
} catch (_err) {
|
|
94
|
+
return Number.POSITIVE_INFINITY;
|
|
95
|
+
}
|
|
96
|
+
const glob =
|
|
97
|
+
patterns.length === 1 ? `**/${patterns[0]}` : `**/{${patterns.join(",")}}`;
|
|
98
|
+
const files = getAllFiles(absRoot, glob, options);
|
|
99
|
+
let depth = Number.POSITIVE_INFINITY;
|
|
100
|
+
for (const f of files) {
|
|
101
|
+
const rel = relative(absRoot, dirname(resolve(f)));
|
|
102
|
+
if (!rel || rel.startsWith("..")) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
depth = Math.min(depth, rel.split(sep).length);
|
|
106
|
+
}
|
|
107
|
+
return depth;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Order parent candidates by the depth of their ecosystem's manifests under
|
|
112
|
+
* the scan roots (the shallowest first). The sort is stable, so candidates of
|
|
113
|
+
* equal depth keep the order the ecosystems were scanned in.
|
|
114
|
+
*
|
|
115
|
+
* @param {Object[]} candidates Detected parent components, in scan order
|
|
116
|
+
* @param {string[]} roots Scan roots
|
|
117
|
+
* @param {Object} options CLI options
|
|
118
|
+
* @returns {Object[]} The candidates, reordered
|
|
119
|
+
*/
|
|
120
|
+
export function orderParentCandidates(candidates, roots, options = {}) {
|
|
121
|
+
const depthByType = new Map();
|
|
122
|
+
const depthOf = (component) => {
|
|
123
|
+
const type = purlTypeOf(component);
|
|
124
|
+
if (!type) {
|
|
125
|
+
return Number.POSITIVE_INFINITY;
|
|
126
|
+
}
|
|
127
|
+
if (!depthByType.has(type)) {
|
|
128
|
+
depthByType.set(
|
|
129
|
+
type,
|
|
130
|
+
Math.min(
|
|
131
|
+
Number.POSITIVE_INFINITY,
|
|
132
|
+
...(roots || []).map((r) => manifestDepth(r, type, options)),
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return depthByType.get(type);
|
|
137
|
+
};
|
|
138
|
+
return candidates
|
|
139
|
+
.map((component, index) => ({
|
|
140
|
+
component,
|
|
141
|
+
index,
|
|
142
|
+
depth: depthOf(component),
|
|
143
|
+
}))
|
|
144
|
+
.sort((a, b) => a.depth - b.depth || a.index - b.index)
|
|
145
|
+
.map((c) => c.component);
|
|
146
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
3
|
|
|
4
|
-
import * as toml from "smol-toml";
|
|
5
|
-
|
|
6
4
|
import { safeExistsSync } from "../core/fs.js";
|
|
5
|
+
import { parseToml } from "../core/toml.js";
|
|
7
6
|
|
|
8
7
|
// Keep this list conservative and high-signal: it is meant to surface common
|
|
9
8
|
// Rust native build helpers for audit triage, not to exhaustively model every
|
|
@@ -67,7 +66,7 @@ function createFormulationComponent(name, filePath, toolName) {
|
|
|
67
66
|
function parseCargoManifest(filePath) {
|
|
68
67
|
let cargoData;
|
|
69
68
|
try {
|
|
70
|
-
cargoData =
|
|
69
|
+
cargoData = parseToml(readFileSync(filePath, { encoding: "utf-8" }));
|
|
71
70
|
} catch {
|
|
72
71
|
return undefined;
|
|
73
72
|
}
|
|
@@ -252,7 +251,7 @@ function parseCargoManifest(filePath) {
|
|
|
252
251
|
function parsePyProject(filePath) {
|
|
253
252
|
let pyprojectData;
|
|
254
253
|
try {
|
|
255
|
-
pyprojectData =
|
|
254
|
+
pyprojectData = parseToml(readFileSync(filePath, { encoding: "utf-8" }));
|
|
256
255
|
} catch {
|
|
257
256
|
return undefined;
|
|
258
257
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Purl } from "@cdxgen/cdx-purl";
|
|
2
|
+
|
|
3
|
+
// Scala libraries publish their Maven artifacts with a trailing Scala binary
|
|
4
|
+
// version suffix (`jwt-core_3`, `coursier_2.13`), optionally preceded by a
|
|
5
|
+
// platform suffix for Scala.js and Scala Native builds (`upickle_sjs1_3`,
|
|
6
|
+
// `upickle_native0.5_3`). sbt components strip the binary suffix and keep the
|
|
7
|
+
// platform suffix (`jwt-core`, `upickle_sjs1`), so the same library is named
|
|
8
|
+
// differently depending on the build tool that reported it.
|
|
9
|
+
const SCALA_BINARY_SUFFIX_REGEX = /_(2\.\d+|3)$/;
|
|
10
|
+
const SCALA_PLATFORM_SUFFIX_REGEX = /_(sjs1|sjs0\.6|native0\.\d+)$/;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse a Scala-published Maven artifact name into its coordinate parts.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} name Maven artifactId, for example `upickle_sjs1_3`
|
|
16
|
+
* @returns {{ originalName: string, artifactBase: string, binaryVersion: string|null,
|
|
17
|
+
* platformSuffix: string, purlName: string }} The artifact name without any
|
|
18
|
+
* Scala suffix (`upickle`), the Scala binary version from the suffix (`3`),
|
|
19
|
+
* the platform suffix including the underscore (`_sjs1`) or an empty string,
|
|
20
|
+
* and the artifact name sbt-style purls emit (binary suffix dropped, platform
|
|
21
|
+
* suffix kept).
|
|
22
|
+
*/
|
|
23
|
+
export function parseScalaArtifact(name) {
|
|
24
|
+
const result = {
|
|
25
|
+
originalName: name,
|
|
26
|
+
artifactBase: name,
|
|
27
|
+
binaryVersion: null,
|
|
28
|
+
platformSuffix: "",
|
|
29
|
+
purlName: name,
|
|
30
|
+
};
|
|
31
|
+
if (!name) {
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
let rest = name;
|
|
35
|
+
const binaryMatch = rest.match(SCALA_BINARY_SUFFIX_REGEX);
|
|
36
|
+
if (binaryMatch) {
|
|
37
|
+
result.binaryVersion = binaryMatch[1];
|
|
38
|
+
rest = rest.slice(0, rest.length - binaryMatch[0].length);
|
|
39
|
+
}
|
|
40
|
+
const platformMatch = rest.match(SCALA_PLATFORM_SUFFIX_REGEX);
|
|
41
|
+
if (platformMatch) {
|
|
42
|
+
result.platformSuffix = platformMatch[0];
|
|
43
|
+
rest = rest.slice(0, rest.length - platformMatch[0].length);
|
|
44
|
+
}
|
|
45
|
+
result.artifactBase = rest;
|
|
46
|
+
result.purlName = binaryMatch ? `${rest}${result.platformSuffix}` : name;
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build a qualifier-free coordinate key for a Scala-published artifact.
|
|
52
|
+
*
|
|
53
|
+
* The key ignores purl qualifiers such as `repository_url` and `type`, so the
|
|
54
|
+
* POM-coordinate keys of the jar namespace mapping and the sbt-style purls of
|
|
55
|
+
* the components resolve to the same entry.
|
|
56
|
+
*
|
|
57
|
+
* @param {Object} parts Coordinate parts
|
|
58
|
+
* @param {string} [parts.type] Purl type, defaults to `maven`
|
|
59
|
+
* @param {string} [parts.group] Maven groupId
|
|
60
|
+
* @param {string} parts.artifactBase Artifact name without any Scala suffix
|
|
61
|
+
* @param {string} [parts.platformSuffix] `_sjs1`, `_native0.5` or empty
|
|
62
|
+
* @param {string} [parts.version] Package version
|
|
63
|
+
* @returns {string} The coordinate key
|
|
64
|
+
*/
|
|
65
|
+
export function scalaCoordinateKey({
|
|
66
|
+
type = "maven",
|
|
67
|
+
group = "",
|
|
68
|
+
artifactBase = "",
|
|
69
|
+
platformSuffix = "",
|
|
70
|
+
version = "",
|
|
71
|
+
} = {}) {
|
|
72
|
+
return [type, group, artifactBase, platformSuffix, version].join(":");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build the qualifier-free coordinate key of a purl string.
|
|
77
|
+
*
|
|
78
|
+
* The artifact name is normalized with {@link parseScalaArtifact}, so
|
|
79
|
+
* `pkg:maven/g/foo_3@1.0` and the sbt-style `pkg:maven/g/foo@1.0` share a key,
|
|
80
|
+
* while the Scala.js build `foo_sjs1_3` keeps its own key (`foo` plus the
|
|
81
|
+
* `_sjs1` platform suffix) and never joins the plain `foo` component.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} purlString Package URL to normalize
|
|
84
|
+
* @returns {string|undefined} The coordinate key, or undefined when the string
|
|
85
|
+
* is not a parseable purl
|
|
86
|
+
*/
|
|
87
|
+
export function coordinateKeyFromPurl(purlString) {
|
|
88
|
+
return scalaCoordinateOfPurl(purlString)?.key;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The coordinate key of a purl together with the Scala binary version its
|
|
93
|
+
* artifact name carries, if any.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} purlString Package URL to normalize
|
|
96
|
+
* @returns {{ key: string, binaryVersion: string|null }|undefined} The key
|
|
97
|
+
* and binary version, or undefined when the string is not a parseable purl
|
|
98
|
+
*/
|
|
99
|
+
export function scalaCoordinateOfPurl(purlString) {
|
|
100
|
+
if (!purlString?.startsWith("pkg:")) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
let purlObj;
|
|
104
|
+
try {
|
|
105
|
+
purlObj = Purl.parse(purlString);
|
|
106
|
+
} catch (_err) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
if (!purlObj?.name) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const scalaInfo = parseScalaArtifact(purlObj.name);
|
|
113
|
+
return {
|
|
114
|
+
key: scalaCoordinateKey({
|
|
115
|
+
type: purlObj.type,
|
|
116
|
+
group: purlObj.namespace || "",
|
|
117
|
+
artifactBase: scalaInfo.artifactBase,
|
|
118
|
+
platformSuffix: scalaInfo.platformSuffix,
|
|
119
|
+
version: purlObj.version || "",
|
|
120
|
+
}),
|
|
121
|
+
binaryVersion: scalaInfo.binaryVersion,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// An artifact name that already ends in a full Scala version, as scala-cli
|
|
126
|
+
// `:::` and sbt `CrossVersion.full` artifacts do (`_3.3.7`, `_2.13.16`). A
|
|
127
|
+
// binary suffix such as `_3` left on a name is part of the name: the
|
|
128
|
+
// component producers strip exactly one, into the compiler version.
|
|
129
|
+
const SCALA_FULL_VERSION_SUFFIX_REGEX = /_\d+\.\d+\.\d+(?:-[A-Za-z0-9.]+)?$/;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The artifactId the library is published under, for cache and repository
|
|
133
|
+
* lookups only. Components a Scala build tool reports carry the name with the
|
|
134
|
+
* Scala binary suffix stripped (the platform suffix kept) together with
|
|
135
|
+
* `cdx:scala:compilerVersion`; on disk and in the repository the artifact is
|
|
136
|
+
* `cats-core_2.13` or `upickle_sjs1_3`. Names that end in a full Scala version
|
|
137
|
+
* are used as they are.
|
|
138
|
+
*
|
|
139
|
+
* The component's own `name`, `purl` and `bom-ref` never change.
|
|
140
|
+
*
|
|
141
|
+
* @param {Object} pkg Package with `name` and `properties`.
|
|
142
|
+
* @returns {string} The artifactId to look up.
|
|
143
|
+
*/
|
|
144
|
+
export function publishedArtifactId(pkg) {
|
|
145
|
+
const name = pkg?.name;
|
|
146
|
+
if (!name || SCALA_FULL_VERSION_SUFFIX_REGEX.test(name)) {
|
|
147
|
+
return name;
|
|
148
|
+
}
|
|
149
|
+
const binaryVersion = (pkg.properties || []).find(
|
|
150
|
+
(property) => property.name === "cdx:scala:compilerVersion",
|
|
151
|
+
)?.value;
|
|
152
|
+
return binaryVersion ? `${name}_${binaryVersion}` : name;
|
|
153
|
+
}
|
package/lib/inventory/source.js
CHANGED
|
@@ -7,7 +7,6 @@ import { Purl } from "@cdxgen/cdx-purl";
|
|
|
7
7
|
import { coerce, diff, prerelease } from "semver";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
cdxgenAgent,
|
|
11
10
|
createDryRunError,
|
|
12
11
|
DEBUG_MODE,
|
|
13
12
|
hasDangerousUnicode,
|
|
@@ -24,11 +23,12 @@ import {
|
|
|
24
23
|
} from "../core/fs.js";
|
|
25
24
|
import { thoughtLog } from "../core/logger.js";
|
|
26
25
|
import { isValidDriveRoot, isWin } from "../core/paths.js";
|
|
26
|
+
import { registerRunReset } from "../core/runState.js";
|
|
27
27
|
import {
|
|
28
|
+
gatedGet,
|
|
28
29
|
prefetchEnabled,
|
|
29
30
|
prefetchedResponse,
|
|
30
31
|
prefetchJson,
|
|
31
|
-
withHostRateLimit,
|
|
32
32
|
} from "./fetchBatch.js";
|
|
33
33
|
import { canonicalPurlFromLooseString } from "./purl.js";
|
|
34
34
|
|
|
@@ -1165,6 +1165,8 @@ export function resetGitUrlSourcePrefetch() {
|
|
|
1165
1165
|
gitUrlSourcePrefetch = new Map();
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
|
+
registerRunReset("source:registry-documents", resetGitUrlSourcePrefetch);
|
|
1169
|
+
|
|
1168
1170
|
/**
|
|
1169
1171
|
* Prefetch the registry documents a list of purls will be resolved from.
|
|
1170
1172
|
*
|
|
@@ -1218,12 +1220,10 @@ async function fetchRegistryDocument(lookup) {
|
|
|
1218
1220
|
// whose policy is one request per second however the request was reached.
|
|
1219
1221
|
return (
|
|
1220
1222
|
prefetchedResponse(gitUrlSourcePrefetch, lookup.url) ||
|
|
1221
|
-
(await
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
}),
|
|
1226
|
-
))
|
|
1223
|
+
(await gatedGet(lookup.url, {
|
|
1224
|
+
...(lookup.accept ? { headers: { Accept: lookup.accept } } : {}),
|
|
1225
|
+
responseType: "json",
|
|
1226
|
+
}))
|
|
1227
1227
|
);
|
|
1228
1228
|
}
|
|
1229
1229
|
|
package/lib/inventory/spdx.js
CHANGED
|
@@ -343,12 +343,113 @@ export function findLicenseId(name) {
|
|
|
343
343
|
: name;
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Identify a license from the title and opening of its full text. A license
|
|
348
|
+
* text quotes other licenses (the GNU GPL's text names the GNU Lesser GPL),
|
|
349
|
+
* so the substring scan of `guessLicenseId` alone can name the wrong one.
|
|
350
|
+
*
|
|
351
|
+
* @param {string} content License file contents
|
|
352
|
+
* @returns {string|undefined} SPDX license id or expression
|
|
353
|
+
*/
|
|
354
|
+
export function licenseIdFromText(content) {
|
|
355
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
const head = content.slice(0, 4000).replace(/\s+/g, " ").trim().toUpperCase();
|
|
359
|
+
const version = (prefix) => {
|
|
360
|
+
const at = head.indexOf(prefix);
|
|
361
|
+
if (at < 0) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
const tail = head.slice(at + prefix.length, at + prefix.length + 60);
|
|
365
|
+
const match = tail.match(/VERSION (\d(?:\.\d)?)/);
|
|
366
|
+
return match?.[1];
|
|
367
|
+
};
|
|
368
|
+
// the first lines name the license; quotations of others come later, so
|
|
369
|
+
// of the GNU licenses the one named first is the one the text is
|
|
370
|
+
const title = head.slice(0, 200);
|
|
371
|
+
const gnu = [
|
|
372
|
+
["GNU AFFERO GENERAL PUBLIC LICENSE", { 3: "AGPL-3.0-only" }],
|
|
373
|
+
[
|
|
374
|
+
"GNU LESSER GENERAL PUBLIC LICENSE",
|
|
375
|
+
{ 3: "LGPL-3.0-only", 2.1: "LGPL-2.1-only", 2: "LGPL-2.0-only" },
|
|
376
|
+
],
|
|
377
|
+
[
|
|
378
|
+
"GNU LIBRARY GENERAL PUBLIC LICENSE",
|
|
379
|
+
{ 2.1: "LGPL-2.1-only", 2: "LGPL-2.0-only" },
|
|
380
|
+
],
|
|
381
|
+
[
|
|
382
|
+
"GNU GENERAL PUBLIC LICENSE",
|
|
383
|
+
{ 3: "GPL-3.0-only", 2: "GPL-2.0-only", 1: "GPL-1.0-only" },
|
|
384
|
+
],
|
|
385
|
+
]
|
|
386
|
+
.map(([name, ids]) => ({ name, ids, at: title.indexOf(name) }))
|
|
387
|
+
.filter((t) => t.at >= 0)
|
|
388
|
+
.sort((a, b) => a.at - b.at)[0];
|
|
389
|
+
if (gnu) {
|
|
390
|
+
return gnu.ids[version(gnu.name)];
|
|
391
|
+
}
|
|
392
|
+
if (head.includes("APACHE LICENSE") && head.includes("VERSION 2.0")) {
|
|
393
|
+
return head.includes("LLVM EXCEPTION")
|
|
394
|
+
? "Apache-2.0 WITH LLVM-exception"
|
|
395
|
+
: "Apache-2.0";
|
|
396
|
+
}
|
|
397
|
+
if (
|
|
398
|
+
title.includes("MOZILLA PUBLIC LICENSE") &&
|
|
399
|
+
head.includes("VERSION 2.0")
|
|
400
|
+
) {
|
|
401
|
+
return "MPL-2.0";
|
|
402
|
+
}
|
|
403
|
+
if (title.includes("BOOST SOFTWARE LICENSE - VERSION 1.0")) {
|
|
404
|
+
return "BSL-1.0";
|
|
405
|
+
}
|
|
406
|
+
if (
|
|
407
|
+
head.includes(
|
|
408
|
+
"THIS IS FREE AND UNENCUMBERED SOFTWARE RELEASED INTO THE PUBLIC DOMAIN",
|
|
409
|
+
)
|
|
410
|
+
) {
|
|
411
|
+
return "Unlicense";
|
|
412
|
+
}
|
|
413
|
+
if (head.includes("PERMISSION IS HEREBY GRANTED, FREE OF CHARGE")) {
|
|
414
|
+
return "MIT";
|
|
415
|
+
}
|
|
416
|
+
if (
|
|
417
|
+
head.includes(
|
|
418
|
+
"PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED",
|
|
419
|
+
)
|
|
420
|
+
) {
|
|
421
|
+
return "ISC";
|
|
422
|
+
}
|
|
423
|
+
if (
|
|
424
|
+
head.includes("THIS SOFTWARE IS PROVIDED 'AS-IS'") &&
|
|
425
|
+
head.includes(
|
|
426
|
+
"PERMISSION IS GRANTED TO ANYONE TO USE THIS SOFTWARE FOR ANY PURPOSE",
|
|
427
|
+
)
|
|
428
|
+
) {
|
|
429
|
+
return "Zlib";
|
|
430
|
+
}
|
|
431
|
+
if (head.includes("REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS")) {
|
|
432
|
+
if (head.includes("ALL ADVERTISING MATERIALS MENTIONING")) {
|
|
433
|
+
return "BSD-4-Clause";
|
|
434
|
+
}
|
|
435
|
+
return head.includes("NEITHER THE NAME") ||
|
|
436
|
+
head.includes("THE NAME OF THE AUTHOR MAY NOT BE USED")
|
|
437
|
+
? "BSD-3-Clause"
|
|
438
|
+
: "BSD-2-Clause";
|
|
439
|
+
}
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
346
443
|
/**
|
|
347
444
|
* Method to guess the spdx license id from license contents
|
|
348
445
|
*
|
|
349
446
|
* @param {string} content License file contents
|
|
350
447
|
*/
|
|
351
448
|
export function guessLicenseId(content) {
|
|
449
|
+
const fromText = licenseIdFromText(content);
|
|
450
|
+
if (fromText) {
|
|
451
|
+
return fromText;
|
|
452
|
+
}
|
|
352
453
|
content = content.replace(/\n/g, " ");
|
|
353
454
|
for (const l of licenseMapping) {
|
|
354
455
|
for (const j in l.names) {
|
|
@@ -77,6 +77,7 @@ export function parseCommand(cmdStr) {
|
|
|
77
77
|
export function resolveSaferExecBinary() {
|
|
78
78
|
const currentPlatform = platform();
|
|
79
79
|
const currentArch = arch();
|
|
80
|
+
const isWindows = currentPlatform === "win32";
|
|
80
81
|
let pkgName = "";
|
|
81
82
|
|
|
82
83
|
if (currentPlatform === "darwin") {
|
|
@@ -91,6 +92,14 @@ export function resolveSaferExecBinary() {
|
|
|
91
92
|
} else if (currentArch === "arm64") {
|
|
92
93
|
pkgName = "@cdxgen/safer-exec-linux-arm64";
|
|
93
94
|
}
|
|
95
|
+
} else if (isWindows) {
|
|
96
|
+
// The Windows packages ship the MXC executor (wxc-exec.exe) next to the
|
|
97
|
+
// engine, which is where the engine looks for it.
|
|
98
|
+
if (currentArch === "x64") {
|
|
99
|
+
pkgName = "@cdxgen/safer-exec-windows-amd64";
|
|
100
|
+
} else if (currentArch === "arm64") {
|
|
101
|
+
pkgName = "@cdxgen/safer-exec-windows-arm64";
|
|
102
|
+
}
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
if (!pkgName) {
|
|
@@ -117,9 +126,14 @@ export function resolveSaferExecBinary() {
|
|
|
117
126
|
|
|
118
127
|
for (const modulesDir of searchDirs) {
|
|
119
128
|
// Direct structure under node_modules. The Go runtime inside the
|
|
120
|
-
// platform package is bin/safer-exec-rt; "safer-exec" is the JS
|
|
129
|
+
// platform package is bin/safer-exec-rt(.exe); "safer-exec" is the JS
|
|
121
130
|
// dispatcher's npm bin name, not a file on disk.
|
|
122
|
-
const directPath = join(
|
|
131
|
+
const directPath = join(
|
|
132
|
+
modulesDir,
|
|
133
|
+
pkgName,
|
|
134
|
+
"bin",
|
|
135
|
+
isWindows ? "safer-exec-rt.exe" : "safer-exec-rt",
|
|
136
|
+
);
|
|
123
137
|
let realDirectPath;
|
|
124
138
|
try {
|
|
125
139
|
realDirectPath = realpathSync(directPath);
|
|
@@ -127,10 +141,12 @@ export function resolveSaferExecBinary() {
|
|
|
127
141
|
realDirectPath = directPath;
|
|
128
142
|
}
|
|
129
143
|
if (existsSync(realDirectPath)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
if (!isWindows) {
|
|
145
|
+
try {
|
|
146
|
+
chmodSync(realDirectPath, 0o755);
|
|
147
|
+
} catch (_err) {
|
|
148
|
+
// ignore
|
|
149
|
+
}
|
|
134
150
|
}
|
|
135
151
|
return realDirectPath;
|
|
136
152
|
}
|
|
@@ -252,6 +268,14 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
252
268
|
if (options.dryRun) {
|
|
253
269
|
exec.enableDryRun();
|
|
254
270
|
}
|
|
271
|
+
if (platform() === "win32" && options.blockInterpreters) {
|
|
272
|
+
// safer-exec reports --private-tmp and --protect-home on Windows when a
|
|
273
|
+
// grant defeats them, but --block-interpreters is macOS-only and stays
|
|
274
|
+
// silent (cdxgen/safer-exec#4).
|
|
275
|
+
console.warn(
|
|
276
|
+
"[cdxgen trace] --block-interpreters has no effect on Windows.",
|
|
277
|
+
);
|
|
278
|
+
}
|
|
255
279
|
if (options.blockInterpreters) {
|
|
256
280
|
exec.blockInterpreters();
|
|
257
281
|
}
|
|
@@ -376,8 +400,41 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
376
400
|
|
|
377
401
|
const result = await exec.run(cmd, args);
|
|
378
402
|
const stderr = result?.stderr || "";
|
|
403
|
+
const { warnings, error: engineError } = engineMessages(stderr);
|
|
404
|
+
// Windows records DLL loads through an ETW session, which only an
|
|
405
|
+
// elevated process (or a Performance Log Users member) may start.
|
|
406
|
+
const etwDenied =
|
|
407
|
+
platform() === "win32" &&
|
|
408
|
+
warnings.some(
|
|
409
|
+
(line) =>
|
|
410
|
+
line.includes("DLL tracing:") &&
|
|
411
|
+
line.includes("administrator rights"),
|
|
412
|
+
);
|
|
413
|
+
// run() captures the engine's stderr, so a control the sandbox could not
|
|
414
|
+
// apply would otherwise read like one it did.
|
|
415
|
+
for (const line of warnings) {
|
|
416
|
+
if (etwDenied && line.includes("DLL tracing:")) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
console.warn(line);
|
|
420
|
+
}
|
|
421
|
+
if (etwDenied) {
|
|
422
|
+
console.warn(
|
|
423
|
+
"[cdxgen trace] library tracing on Windows needs an elevated prompt (or membership in the Performance Log Users group); the BOM has no library components. Egress services from --proxy-egress are still recorded.",
|
|
424
|
+
);
|
|
425
|
+
}
|
|
379
426
|
if (result && result.exitCode !== 0) {
|
|
380
|
-
if (
|
|
427
|
+
if (engineError) {
|
|
428
|
+
console.error(engineError);
|
|
429
|
+
if (options.strict) {
|
|
430
|
+
// Under --strict the sandbox refused the run; an empty BOM would
|
|
431
|
+
// read as a command that loaded nothing.
|
|
432
|
+
throw Object.assign(
|
|
433
|
+
new Error(`Tracing refused under --strict: ${engineError}`),
|
|
434
|
+
{ strictRefusal: true },
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
} else if (stderr.includes("[safer-exec] Error:")) {
|
|
381
438
|
console.error("Tracing launcher execution failed:", stderr.trim());
|
|
382
439
|
} else {
|
|
383
440
|
// A failing command usually means a partial trace (for example a
|
|
@@ -488,11 +545,35 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
488
545
|
dryRunSummary,
|
|
489
546
|
};
|
|
490
547
|
} catch (err) {
|
|
548
|
+
if (err?.strictRefusal) {
|
|
549
|
+
throw err;
|
|
550
|
+
}
|
|
491
551
|
console.error("Tracing command execution failed:", err);
|
|
492
552
|
}
|
|
493
553
|
return emptyResult;
|
|
494
554
|
}
|
|
495
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Picks the safer-exec engine's own lines out of the stderr it shares with the
|
|
558
|
+
* traced command. Warnings start with "safer-exec: warning:"; when the engine
|
|
559
|
+
* stops a run, its error is the last other "safer-exec: " line it prints.
|
|
560
|
+
*
|
|
561
|
+
* @param {string} stderr Captured stderr of a safer-exec run
|
|
562
|
+
* @returns {{warnings: string[], error: string|undefined}} Unique engine warnings, and the engine's error if any
|
|
563
|
+
*/
|
|
564
|
+
export function engineMessages(stderr) {
|
|
565
|
+
const lines = String(stderr || "")
|
|
566
|
+
.split(/\r?\n/)
|
|
567
|
+
.filter((line) => line.startsWith("safer-exec: "));
|
|
568
|
+
const warnings = [
|
|
569
|
+
...new Set(lines.filter((line) => line.startsWith("safer-exec: warning:"))),
|
|
570
|
+
];
|
|
571
|
+
const others = lines.filter(
|
|
572
|
+
(line) => !line.startsWith("safer-exec: warning:"),
|
|
573
|
+
);
|
|
574
|
+
return { warnings, error: others.at(-1) };
|
|
575
|
+
}
|
|
576
|
+
|
|
496
577
|
/**
|
|
497
578
|
* Returns the sorted, de-duplicated parameter names of a query string.
|
|
498
579
|
*
|