@cdxgen/cdxgen 13.4.0 → 13.6.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 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +29 -5
- package/bin/evinse.js +8 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/bin/validate.js +6 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/license-aliases.json +24 -2
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +407 -1
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +120 -6
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +182 -6
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +363 -66
- package/lib/cli/managedBom.js +853 -87
- package/lib/cli/nativeBom.js +267 -33
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +38 -8
- package/lib/core/fs.js +40 -19
- package/lib/core/httpClient.js +172 -46
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -0
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/core/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +858 -244
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +438 -64
- package/lib/ecosystems/parsers-python.js +100 -12
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +544 -34
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +4 -1
- package/lib/ecosystems/scalasem.js +798 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +472 -61
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +110 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +41 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +639 -63
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +475 -18
- package/lib/inventory/evidenceUtils.js +447 -23
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +32 -3
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +271 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +438 -0
- package/lib/inventory/phpHostProbe.js +185 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +256 -5
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +866 -186
- package/lib/managers/docker.js +245 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +62 -13
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +198 -2
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +71 -6
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +231 -62
- package/lib/validator/bomValidator.js +32 -6
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +27 -8
- package/lib/validator/generated/validate-1.7.mjs +32 -13
- package/lib/validator/generated/validate-2.0.mjs +31 -12
- package/lib/validator/index.js +1 -1
- package/package.json +19 -18
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +22 -3
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- 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/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -2
- 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 +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +57 -21
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +14 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +88 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +33 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +162 -7
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +92 -8
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +44 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +38 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +10 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +24 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +17 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +8 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +26 -9
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Bounded Node.js build-version reader for unpackaged `node` binaries.
|
|
2
|
+
//
|
|
3
|
+
// The Node.js the Docker Official images, the release tarballs, nvm, fnm and
|
|
4
|
+
// volta install is not owned by any package manager, so a BOM sees it only as
|
|
5
|
+
// a file. Its version decides whether the runtime is affected by advisories
|
|
6
|
+
// such as CVE-2026-111497 (fixed in 22.23.4, 24.22.1 and 26.12.1).
|
|
7
|
+
//
|
|
8
|
+
// Two markers are compiled into the binary:
|
|
9
|
+
// - the release URLs, `https://nodejs.org/download/<channel>/v<version>/`,
|
|
10
|
+
// which a build configured with release URLs (every nodejs.org build)
|
|
11
|
+
// carries for process.release.sourceUrl and headersUrl
|
|
12
|
+
// https://github.com/nodejs/node/blob/v24.22.0/src/node_metadata.cc#L191-L197
|
|
13
|
+
// - `node.js/v<version>`, NUL-terminated, the inspector's /json/version
|
|
14
|
+
// Browser string, which every build with the inspector carries; it is the
|
|
15
|
+
// fallback for a build without release URLs.
|
|
16
|
+
// https://github.com/nodejs/node/blob/v24.22.0/src/inspector_socket_server.cc#L117
|
|
17
|
+
//
|
|
18
|
+
// process.versions.brotli is not a usable signal: Node.js backports the
|
|
19
|
+
// decoder fix without changing it (24.22.1 still reports 1.2.0). The decoder
|
|
20
|
+
// state is read separately, by brotliDecoder.js.
|
|
21
|
+
|
|
22
|
+
import { basename } from "node:path";
|
|
23
|
+
|
|
24
|
+
import { scanFileMarkers } from "../core/boundedFile.js";
|
|
25
|
+
import { applyPurl, tryBuildPurl } from "./purl.js";
|
|
26
|
+
|
|
27
|
+
const RELEASE_URL_MARKER = Buffer.from("https://nodejs.org/download/");
|
|
28
|
+
const USER_AGENT_MARKER = Buffer.from("node.js/v");
|
|
29
|
+
const MAX_VERSION_LENGTH = 64;
|
|
30
|
+
const VERSION_RE = /^(\d+\.\d+\.\d+)(?:-([0-9A-Za-z][0-9A-Za-z.]*))?$/;
|
|
31
|
+
const CHANNELS = new Set(["release", "rc", "nightly", "test", "v8-canary"]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The most node-named files one scan opens in search of a build version.
|
|
35
|
+
*
|
|
36
|
+
* @type {number}
|
|
37
|
+
*/
|
|
38
|
+
export const MAX_NODE_PROBES = 64;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether a file name is one the Node.js runtime is installed under.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} fileName basename or path of the candidate
|
|
44
|
+
* @returns {boolean} true for `node`, `nodejs` and `node.exe`
|
|
45
|
+
*/
|
|
46
|
+
export function isNodeBinaryName(fileName) {
|
|
47
|
+
if (typeof fileName !== "string" || !fileName) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const name = basename(fileName.replaceAll("\\", "/"));
|
|
51
|
+
return name === "node" || name === "nodejs" || name === "node.exe";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse a Node.js version in the grammar the release tooling produces:
|
|
56
|
+
* `24.22.1`, `25.0.0-rc.1`, `26.0.0-nightly20260301abcdef`.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} text candidate version, without the leading `v`
|
|
59
|
+
* @returns {string|undefined} the version, or undefined
|
|
60
|
+
*/
|
|
61
|
+
export function parseNodeVersion(text) {
|
|
62
|
+
if (typeof text !== "string" || !text || text.length > MAX_VERSION_LENGTH) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return VERSION_RE.test(text) ? text : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function versionFromReleaseUrl(after) {
|
|
69
|
+
// <channel>/v<version>/...
|
|
70
|
+
const text = after.toString("latin1");
|
|
71
|
+
const slash = text.indexOf("/");
|
|
72
|
+
if (slash <= 0) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const channel = text.slice(0, slash);
|
|
76
|
+
if (!CHANNELS.has(channel) || text[slash + 1] !== "v") {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const end = text.indexOf("/", slash + 2);
|
|
80
|
+
if (end === -1) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const version = parseNodeVersion(text.slice(slash + 2, end));
|
|
84
|
+
return version ? { version, channel } : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function versionFromUserAgent(after) {
|
|
88
|
+
const end = after.indexOf(0);
|
|
89
|
+
if (end <= 0) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
return parseNodeVersion(after.toString("latin1", 0, end));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Read the Node.js version a `node` binary was built as.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} absPath absolute path of the binary
|
|
99
|
+
* @param {string|undefined} rootDir extracted root, when the file came from one
|
|
100
|
+
* @returns {{version: string, source: string, channel?: string}|undefined}
|
|
101
|
+
* source is `release-url` or `binary`
|
|
102
|
+
*/
|
|
103
|
+
export function readNodeBuildVersion(absPath, rootDir) {
|
|
104
|
+
const scan = scanFileMarkers(
|
|
105
|
+
absPath,
|
|
106
|
+
rootDir,
|
|
107
|
+
[RELEASE_URL_MARKER, USER_AGENT_MARKER],
|
|
108
|
+
{ contextBytes: MAX_VERSION_LENGTH + 32, maxHits: 8 },
|
|
109
|
+
);
|
|
110
|
+
if (!scan || scan.skipped) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const [releaseHits, userAgentHits] = scan.hits;
|
|
114
|
+
const fromBinary = userAgentHits
|
|
115
|
+
.map((hit) => versionFromUserAgent(hit.after))
|
|
116
|
+
.find(Boolean);
|
|
117
|
+
for (const hit of releaseHits) {
|
|
118
|
+
const release = versionFromReleaseUrl(hit.after);
|
|
119
|
+
// The headers URL names the release a binary was published as. A binary
|
|
120
|
+
// whose own version string disagrees is not that release, so the URL is
|
|
121
|
+
// used only when the two agree or the version string is missing.
|
|
122
|
+
if (release && (!fromBinary || fromBinary === release.version)) {
|
|
123
|
+
return {
|
|
124
|
+
version: release.version,
|
|
125
|
+
source: "release-url",
|
|
126
|
+
channel: release.channel,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return fromBinary ? { version: fromBinary, source: "binary" } : undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The CPE that lets a downstream scanner match a Node.js version. NVD names the
|
|
135
|
+
* product node.js and keeps a pre-release in the update field.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} version Node.js version
|
|
138
|
+
* @returns {string} CPE 2.3 string
|
|
139
|
+
*/
|
|
140
|
+
export function nodeCpe(version) {
|
|
141
|
+
const match = VERSION_RE.exec(version || "");
|
|
142
|
+
if (!match) {
|
|
143
|
+
return `cpe:2.3:a:nodejs:node.js:${version}:*:*:*:*:*:*:*`;
|
|
144
|
+
}
|
|
145
|
+
const update = match[2] ? match[2].replaceAll(".", "").toLowerCase() : "*";
|
|
146
|
+
return `cpe:2.3:a:nodejs:node.js:${match[1]}:${update}:*:*:*:*:*:*`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A Node.js runtime is one `platform` component per distinct version, like the
|
|
151
|
+
* PHP and Go stdlib components. Its identity is `pkg:generic/node`: it is only
|
|
152
|
+
* used where no package manager owns the runtime.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} version Node.js version
|
|
155
|
+
* @returns {Object} component with `purl`, `bom-ref` and `cpe`
|
|
156
|
+
*/
|
|
157
|
+
export function createNodeRuntimeComponent(version) {
|
|
158
|
+
return applyPurl(
|
|
159
|
+
{ type: "platform", name: "node", version, cpe: nodeCpe(version) },
|
|
160
|
+
tryBuildPurl({ type: "generic", name: "node", version }),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The properties a node file component carries once its build is known.
|
|
166
|
+
*
|
|
167
|
+
* @param {{version: string, source: string, channel?: string}} build result of readNodeBuildVersion
|
|
168
|
+
* @returns {{name: string, value: string}[]} properties to append
|
|
169
|
+
*/
|
|
170
|
+
export function nodeBuildProperties(build) {
|
|
171
|
+
const properties = [
|
|
172
|
+
{ name: "cdx:node:buildVersion", value: build.version },
|
|
173
|
+
{ name: "cdx:node:buildVersionSource", value: build.source },
|
|
174
|
+
];
|
|
175
|
+
if (build.channel) {
|
|
176
|
+
properties.push({ name: "cdx:node:releaseChannel", value: build.channel });
|
|
177
|
+
}
|
|
178
|
+
return properties;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Start collecting the runtime components and edges of one scan.
|
|
183
|
+
*
|
|
184
|
+
* @returns {{runtimeComponents: Object[], dependencies: Object[], runtimeRefs: Set<string>}}
|
|
185
|
+
*/
|
|
186
|
+
export function createNodeRuntimeInventory() {
|
|
187
|
+
return { runtimeComponents: [], dependencies: [], runtimeRefs: new Set() };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Tag a node file component with its build facts and link it to the runtime
|
|
192
|
+
* component of its version, creating that component on first use. Mutates the
|
|
193
|
+
* component in place.
|
|
194
|
+
*
|
|
195
|
+
* @param {{runtimeComponents: Object[], dependencies: Object[], runtimeRefs: Set<string>}} inventory
|
|
196
|
+
* accumulator from createNodeRuntimeInventory
|
|
197
|
+
* @param {Object} component file component, with a `properties` array
|
|
198
|
+
* @param {{version: string, source: string, channel?: string}} build result of readNodeBuildVersion
|
|
199
|
+
*/
|
|
200
|
+
export function attachNodeBuild(inventory, component, build) {
|
|
201
|
+
component.properties.push(...nodeBuildProperties(build));
|
|
202
|
+
const runtime = createNodeRuntimeComponent(build.version);
|
|
203
|
+
const runtimeRef = runtime["bom-ref"];
|
|
204
|
+
if (!runtimeRef) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!inventory.runtimeRefs.has(runtimeRef)) {
|
|
208
|
+
inventory.runtimeRefs.add(runtimeRef);
|
|
209
|
+
inventory.runtimeComponents.push(runtime);
|
|
210
|
+
inventory.dependencies.push({ ref: runtimeRef, dependsOn: [] });
|
|
211
|
+
}
|
|
212
|
+
if (component["bom-ref"]) {
|
|
213
|
+
inventory.dependencies.push({
|
|
214
|
+
ref: component["bom-ref"],
|
|
215
|
+
dependsOn: [runtimeRef],
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Bounded, read-only probe for the Node.js no package manager owns on a live
|
|
2
|
+
// host, for `-t os` / obom. The release tarballs, the nodejs.org installer and
|
|
3
|
+
// the version managers (nvm, fnm, volta, asdf, mise, n) put Node.js where no
|
|
4
|
+
// package database records it, so an OBOM sees neither the runtime nor the
|
|
5
|
+
// Brotli decoder compiled into it.
|
|
6
|
+
//
|
|
7
|
+
// Candidates come from fixed directories and from each version manager's
|
|
8
|
+
// tree under the home directories, one wildcard level at a time, never from a
|
|
9
|
+
// walk of the file system. The ownership rules, caps and degradations are the
|
|
10
|
+
// shared host-runtime probe's.
|
|
11
|
+
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
isDryRun,
|
|
16
|
+
readEnvironmentVariable,
|
|
17
|
+
recordActivity,
|
|
18
|
+
} from "../core/activity.js";
|
|
19
|
+
import { recordDegradation } from "../core/buildLedger.js";
|
|
20
|
+
import {
|
|
21
|
+
attachBrotliDecoder,
|
|
22
|
+
createBrotliInventory,
|
|
23
|
+
finishBrotliInventory,
|
|
24
|
+
readBrotliDecoder,
|
|
25
|
+
} from "./brotliDecoder.js";
|
|
26
|
+
import {
|
|
27
|
+
isDirectory,
|
|
28
|
+
listDirectory,
|
|
29
|
+
probeHostRuntime,
|
|
30
|
+
} from "./hostRuntimeProbe.js";
|
|
31
|
+
import {
|
|
32
|
+
attachNodeBuild,
|
|
33
|
+
createNodeRuntimeInventory,
|
|
34
|
+
isNodeBinaryName,
|
|
35
|
+
MAX_NODE_PROBES,
|
|
36
|
+
readNodeBuildVersion,
|
|
37
|
+
} from "./nodeBuildInfo.js";
|
|
38
|
+
|
|
39
|
+
// The most directories one wildcard pattern may expand to.
|
|
40
|
+
const MAX_EXPANDED_DIRECTORIES = 256;
|
|
41
|
+
|
|
42
|
+
// Relative to the root.
|
|
43
|
+
const POSIX_DIRECTORIES = {
|
|
44
|
+
linux: ["usr/local/bin", "opt/*/bin", "usr/local/n/versions/node/*/bin"],
|
|
45
|
+
darwin: ["usr/local/bin", "opt/homebrew/bin", "opt/*/bin"],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Relative to a home directory.
|
|
49
|
+
const VERSION_MANAGER_DIRECTORIES = [
|
|
50
|
+
".nvm/versions/node/*/bin",
|
|
51
|
+
".local/share/fnm/node-versions/*/installation/bin",
|
|
52
|
+
".fnm/node-versions/*/installation/bin",
|
|
53
|
+
"Library/Application Support/fnm/node-versions/*/installation/bin",
|
|
54
|
+
".volta/tools/image/node/*/bin",
|
|
55
|
+
".asdf/installs/nodejs/*/bin",
|
|
56
|
+
".local/share/mise/installs/node/*/bin",
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const HOME_PARENTS = {
|
|
60
|
+
linux: ["home"],
|
|
61
|
+
darwin: ["Users"],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Expand a relative directory pattern whose `*` segments match one directory
|
|
66
|
+
* name each.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} base absolute directory the pattern hangs off
|
|
69
|
+
* @param {string} pattern `/`-separated relative pattern
|
|
70
|
+
* @returns {string[]} existing directories, at most MAX_EXPANDED_DIRECTORIES
|
|
71
|
+
*/
|
|
72
|
+
function expandDirectories(base, pattern) {
|
|
73
|
+
let current = [base];
|
|
74
|
+
for (const segment of pattern.split("/")) {
|
|
75
|
+
const next = [];
|
|
76
|
+
for (const directory of current) {
|
|
77
|
+
if (segment !== "*") {
|
|
78
|
+
next.push(join(directory, segment));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (const name of listDirectory(directory)) {
|
|
82
|
+
const child = join(directory, name);
|
|
83
|
+
if (isDirectory(child)) {
|
|
84
|
+
next.push(child);
|
|
85
|
+
}
|
|
86
|
+
if (next.length >= MAX_EXPANDED_DIRECTORIES) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
current = next.slice(0, MAX_EXPANDED_DIRECTORIES);
|
|
92
|
+
}
|
|
93
|
+
return current.filter(isDirectory);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function homeDirectories(context) {
|
|
97
|
+
const { rootPath, platform } = context;
|
|
98
|
+
const homes = [];
|
|
99
|
+
if (platform === "linux") {
|
|
100
|
+
homes.push(join(rootPath, "root"));
|
|
101
|
+
}
|
|
102
|
+
for (const parent of HOME_PARENTS[platform] || []) {
|
|
103
|
+
for (const name of listDirectory(join(rootPath, parent))) {
|
|
104
|
+
const home = join(rootPath, parent, name);
|
|
105
|
+
if (isDirectory(home)) {
|
|
106
|
+
homes.push(home);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return homes.slice(0, MAX_EXPANDED_DIRECTORIES);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Where the Node.js MSI installs, by itself or for Chocolatey's nodejs.install.
|
|
114
|
+
function windowsInstallDirectory(context) {
|
|
115
|
+
const programFiles =
|
|
116
|
+
context.readEnv("ProgramFiles") || join(context.rootPath, "Program Files");
|
|
117
|
+
return join(programFiles, "nodejs");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function candidateDirectories(context) {
|
|
121
|
+
const { rootPath, platform } = context;
|
|
122
|
+
const directories = [];
|
|
123
|
+
if (platform === "win32") {
|
|
124
|
+
directories.push({ directory: windowsInstallDirectory(context) });
|
|
125
|
+
const appData = context.readEnv("APPDATA");
|
|
126
|
+
if (appData) {
|
|
127
|
+
for (const directory of expandDirectories(appData, "nvm/*")) {
|
|
128
|
+
directories.push({ directory });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return directories;
|
|
132
|
+
}
|
|
133
|
+
for (const pattern of POSIX_DIRECTORIES[platform] || []) {
|
|
134
|
+
for (const directory of expandDirectories(rootPath, pattern)) {
|
|
135
|
+
directories.push({ directory });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const home of homeDirectories(context)) {
|
|
139
|
+
for (const pattern of VERSION_MANAGER_DIRECTORIES) {
|
|
140
|
+
for (const directory of expandDirectories(home, pattern)) {
|
|
141
|
+
directories.push({ directory });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return directories;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readBuild(context, candidate) {
|
|
149
|
+
const node = readNodeBuildVersion(candidate.real, context.realRoot);
|
|
150
|
+
if (!node) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return { node, brotli: readBrotliDecoder(candidate.real, context.realRoot) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Look for Node.js no package manager owns on the host. Components come back
|
|
158
|
+
* only for the files nothing owns: a `file` component for each `node` binary
|
|
159
|
+
* with the cdx:node and cdx:brotli properties, one `platform` component per
|
|
160
|
+
* Node.js version with its CPE, and the brotli component when a binary embeds
|
|
161
|
+
* a decoder.
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} [options] probe options, as probeHostPhp takes them
|
|
164
|
+
* @returns {{components: Object[], dependencies: Object[]}}
|
|
165
|
+
*/
|
|
166
|
+
export function probeHostNode(options = {}) {
|
|
167
|
+
return probeHostRuntime(options, {
|
|
168
|
+
label: "Node.js",
|
|
169
|
+
ecosystem: "npm",
|
|
170
|
+
capability: "node-host-probe",
|
|
171
|
+
remediationId: "npm.host-probe.package-db-unreadable",
|
|
172
|
+
maxProbes: MAX_NODE_PROBES,
|
|
173
|
+
chocolateyPackages: ["nodejs", "nodejs-lts", "nodejs.install"],
|
|
174
|
+
chocolateyDirectories: (context) => [windowsInstallDirectory(context)],
|
|
175
|
+
directories: candidateDirectories,
|
|
176
|
+
classify: (name) => (isNodeBinaryName(name) ? "node" : undefined),
|
|
177
|
+
readBuild,
|
|
178
|
+
createInventory: () => ({
|
|
179
|
+
node: createNodeRuntimeInventory(),
|
|
180
|
+
brotli: createBrotliInventory(),
|
|
181
|
+
}),
|
|
182
|
+
attach: (inventory, component, build) => {
|
|
183
|
+
attachNodeBuild(inventory.node, component, build.node);
|
|
184
|
+
if (build.brotli) {
|
|
185
|
+
attachBrotliDecoder(inventory.brotli, component, build.brotli);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
finish: (inventory) => {
|
|
189
|
+
const brotli = finishBrotliInventory(inventory.brotli);
|
|
190
|
+
return {
|
|
191
|
+
components: [...inventory.node.runtimeComponents, ...brotli.components],
|
|
192
|
+
dependencies: [...inventory.node.dependencies, ...brotli.dependencies],
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
hooks: {
|
|
196
|
+
isDryRun,
|
|
197
|
+
readEnvironmentVariable,
|
|
198
|
+
recordActivity,
|
|
199
|
+
recordDegradation,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// The one place that decides what purl an operating-system package gets.
|
|
2
|
+
//
|
|
3
|
+
// A package found by `cdxgen -t docker <image>` and by `obom` run inside that
|
|
4
|
+
// image must be the same package, so the OBOM rows, the file-to-package
|
|
5
|
+
// resolver and the container scan all name a deb, an rpm or an apk package with
|
|
6
|
+
// the vendor and the qualifiers set here and in getDistroInfo(): `arch`,
|
|
7
|
+
// `distro`, `distro_name` and, when the package has one, `epoch`. A pacman
|
|
8
|
+
// package has the vendor and only the `arch` qualifier the alpm type defines.
|
|
9
|
+
// Homebrew, MacPorts and snap packages have no distro, so their purl is the
|
|
10
|
+
// name and the version; a flatpak adds the `arch` and `branch` of its ref.
|
|
11
|
+
//
|
|
12
|
+
// None of these purls has a subpath. A subpath names a path inside a package;
|
|
13
|
+
// the dpkg directory or the source rpm of a row is not one.
|
|
14
|
+
|
|
15
|
+
import { getDistroInfo, isCleanDistroQualifier } from "./osinfo.js";
|
|
16
|
+
import { tryBuildPurl } from "./purl.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The purl types an OS scan builds from what a package manager reports.
|
|
20
|
+
* deb, rpm, apk and alpm belong to a distro; brew, macports, snap and flatpak
|
|
21
|
+
* belong to none. brew, macports, snap and flatpak are not purl-spec types;
|
|
22
|
+
* cdx-purl builds them as it builds `pkg:chocolatey`.
|
|
23
|
+
*/
|
|
24
|
+
export const OS_PACKAGE_PURL_TYPES = new Set([
|
|
25
|
+
"deb",
|
|
26
|
+
"rpm",
|
|
27
|
+
"apk",
|
|
28
|
+
"alpm",
|
|
29
|
+
"brew",
|
|
30
|
+
"macports",
|
|
31
|
+
"snap",
|
|
32
|
+
"flatpak",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether a purl type is one of the OS package managers handled here.
|
|
37
|
+
*
|
|
38
|
+
* @param {string|undefined} type Purl type
|
|
39
|
+
* @returns {boolean} true for the types in OS_PACKAGE_PURL_TYPES
|
|
40
|
+
*/
|
|
41
|
+
export function isOsPackagePurlType(type) {
|
|
42
|
+
return OS_PACKAGE_PURL_TYPES.has(type);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether a value names an architecture. rpm says `(none)` and trivy says
|
|
47
|
+
* `None` for a package that has none, such as an rpm's `gpg-pubkey` key record;
|
|
48
|
+
* neither is an architecture, and neither belongs in a purl.
|
|
49
|
+
*
|
|
50
|
+
* @param {string|undefined|null} arch Architecture as a package manager reports it
|
|
51
|
+
* @returns {boolean} true for a real architecture
|
|
52
|
+
*/
|
|
53
|
+
export function isRealArchitecture(arch) {
|
|
54
|
+
const value = `${arch ?? ""}`.trim().toLowerCase();
|
|
55
|
+
return value !== "" && value !== "(none)" && value !== "none";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isDigits(text) {
|
|
59
|
+
if (!text) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
for (let i = 0; i < text.length; i++) {
|
|
63
|
+
const code = text.charCodeAt(i);
|
|
64
|
+
if (code < 48 || code > 57) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// An epoch is a non-negative integer, and 0 is the same as none. Anything else
|
|
72
|
+
// ("", "(none)", "null") is no epoch.
|
|
73
|
+
function normaliseEpoch(value) {
|
|
74
|
+
const text = `${value ?? ""}`.trim();
|
|
75
|
+
if (!isDigits(text)) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const digits = text.replace(/^0+/, "");
|
|
79
|
+
return digits || undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// dpkg writes a version as [epoch:]upstream[-revision]. A colon is only ever an
|
|
83
|
+
// epoch's separator when what comes before it is all digits.
|
|
84
|
+
function debVersionEpoch(version) {
|
|
85
|
+
const colon = version.indexOf(":");
|
|
86
|
+
if (colon <= 0) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const prefix = version.slice(0, colon);
|
|
90
|
+
return isDigits(prefix) ? prefix : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The epoch of an OS package, as the `epoch` qualifier carries it.
|
|
95
|
+
*
|
|
96
|
+
* An rpm keeps its epoch in a field of its own. A deb keeps it in its version
|
|
97
|
+
* (`2:93`), so the version is read first. Epoch 0 and no epoch are the same,
|
|
98
|
+
* and apk and Homebrew packages have none.
|
|
99
|
+
*
|
|
100
|
+
* @param {{type: string, version?: string, epoch?: string|number}} pkg Package
|
|
101
|
+
* @returns {string|undefined} The epoch without leading zeros, or undefined
|
|
102
|
+
*/
|
|
103
|
+
export function osPackageEpoch(pkg) {
|
|
104
|
+
switch (pkg?.type) {
|
|
105
|
+
case "rpm":
|
|
106
|
+
return normaliseEpoch(pkg.epoch);
|
|
107
|
+
case "deb": {
|
|
108
|
+
const fromVersion = debVersionEpoch(`${pkg.version ?? ""}`);
|
|
109
|
+
return fromVersion === undefined
|
|
110
|
+
? normaliseEpoch(pkg.epoch)
|
|
111
|
+
: normaliseEpoch(fromVersion);
|
|
112
|
+
}
|
|
113
|
+
default:
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The version an OS package carries on its component and in its purl.
|
|
120
|
+
*
|
|
121
|
+
* - rpm: `[epoch:]version-release`, the form the container scan writes. The
|
|
122
|
+
* purl spec keeps the epoch out of an rpm version, but the container scan
|
|
123
|
+
* puts it in the version as well as in the `epoch` qualifier, and an OBOM must
|
|
124
|
+
* name the package the same way.
|
|
125
|
+
* - deb: the version exactly as dpkg reports it, epoch included.
|
|
126
|
+
* - macports: `version_revision`, the form `port installed` prints.
|
|
127
|
+
* - apk, alpm, brew, snap and flatpak: the version as it is. A pacman version
|
|
128
|
+
* already holds its epoch and release (`1:1.3.2-4`).
|
|
129
|
+
*
|
|
130
|
+
* @param {{type: string, version?: string, release?: string, epoch?: string|number, revision?: string|number}} pkg Package
|
|
131
|
+
* @returns {string} The version, or an empty string when the package has none
|
|
132
|
+
*/
|
|
133
|
+
export function osPackageVersion(pkg) {
|
|
134
|
+
const version = `${pkg?.version ?? ""}`;
|
|
135
|
+
if (!version) {
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
138
|
+
switch (pkg.type) {
|
|
139
|
+
case "rpm": {
|
|
140
|
+
const release = `${pkg.release ?? ""}`;
|
|
141
|
+
const evr = release ? `${version}-${release}` : version;
|
|
142
|
+
const epoch = osPackageEpoch(pkg);
|
|
143
|
+
return epoch ? `${epoch}:${evr}` : evr;
|
|
144
|
+
}
|
|
145
|
+
case "deb": {
|
|
146
|
+
if (debVersionEpoch(version) !== undefined) {
|
|
147
|
+
return version;
|
|
148
|
+
}
|
|
149
|
+
const epoch = normaliseEpoch(pkg.epoch);
|
|
150
|
+
return epoch ? `${epoch}:${version}` : version;
|
|
151
|
+
}
|
|
152
|
+
case "macports": {
|
|
153
|
+
// Revision 0 is a real revision: `port installed` prints `@3.6.0_0`.
|
|
154
|
+
const revision = `${pkg.revision ?? ""}`.trim();
|
|
155
|
+
return isDigits(revision) ? `${version}_${revision}` : version;
|
|
156
|
+
}
|
|
157
|
+
default:
|
|
158
|
+
return version;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Build the purl of an OS package.
|
|
164
|
+
*
|
|
165
|
+
* deb, rpm and apk purls take their vendor (`ubuntu`, `almalinux`, `alpine`,
|
|
166
|
+
* `wolfi`) and the `distro` and `distro_name` qualifiers from the distro, plus
|
|
167
|
+
* `arch` and `epoch` from the package. alpm purls take the vendor (`arch`,
|
|
168
|
+
* `manjaro`) and `arch` only: that is all the alpm type defines, and an Arch
|
|
169
|
+
* system is rolling, with no release to name. A qualifier with no value is
|
|
170
|
+
* left out. brew, macports and snap purls carry neither vendor nor qualifiers;
|
|
171
|
+
* a flatpak purl carries the `arch` and `branch` of the flatpak ref.
|
|
172
|
+
*
|
|
173
|
+
* @param {Object} pkg The package
|
|
174
|
+
* @param {string} pkg.type One of OS_PACKAGE_PURL_TYPES
|
|
175
|
+
* @param {string} pkg.name Package name; a flatpak's application ID
|
|
176
|
+
* @param {string} [pkg.version] deb: the dpkg version. rpm: the version tag only. apk: version-rN. macports: the version without its revision.
|
|
177
|
+
* @param {string} [pkg.release] rpm release tag
|
|
178
|
+
* @param {string|number} [pkg.epoch] rpm epoch tag
|
|
179
|
+
* @param {string|number} [pkg.revision] MacPorts revision
|
|
180
|
+
* @param {string} [pkg.arch] Package architecture
|
|
181
|
+
* @param {string} [pkg.branch] flatpak branch (`stable`, `48`)
|
|
182
|
+
* @param {{namespace?: string, distroId?: string, distroName?: string}} [distro]
|
|
183
|
+
* What getDistroInfo() reports; read from the host's os-release when left out
|
|
184
|
+
* @returns {string|null} The purl, or null when the parts do not form a valid one
|
|
185
|
+
*/
|
|
186
|
+
export function buildOsPackagePurl(pkg, distro) {
|
|
187
|
+
const type = pkg?.type;
|
|
188
|
+
const name = `${pkg?.name ?? ""}`;
|
|
189
|
+
if (!isOsPackagePurlType(type) || !name) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const version = osPackageVersion(pkg) || null;
|
|
193
|
+
switch (type) {
|
|
194
|
+
case "brew":
|
|
195
|
+
case "macports":
|
|
196
|
+
case "snap":
|
|
197
|
+
return tryBuildPurl({ type, name, version });
|
|
198
|
+
case "flatpak": {
|
|
199
|
+
const qualifiers = {};
|
|
200
|
+
if (isRealArchitecture(pkg.arch)) {
|
|
201
|
+
qualifiers.arch = `${pkg.arch}`.trim();
|
|
202
|
+
}
|
|
203
|
+
const branch = `${pkg.branch ?? ""}`.trim();
|
|
204
|
+
if (branch) {
|
|
205
|
+
qualifiers.branch = branch;
|
|
206
|
+
}
|
|
207
|
+
return tryBuildPurl({
|
|
208
|
+
type,
|
|
209
|
+
name,
|
|
210
|
+
version,
|
|
211
|
+
qualifiers: Object.keys(qualifiers).length ? qualifiers : null,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
default:
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
const { namespace, distroId, distroName } = distro || getDistroInfo();
|
|
218
|
+
if (!namespace) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const qualifiers = {};
|
|
222
|
+
if (isRealArchitecture(pkg.arch)) {
|
|
223
|
+
qualifiers.arch = `${pkg.arch}`.trim();
|
|
224
|
+
}
|
|
225
|
+
if (type !== "alpm") {
|
|
226
|
+
if (isCleanDistroQualifier(distroId)) {
|
|
227
|
+
qualifiers.distro = distroId;
|
|
228
|
+
}
|
|
229
|
+
if (isCleanDistroQualifier(distroName)) {
|
|
230
|
+
qualifiers.distro_name = distroName;
|
|
231
|
+
}
|
|
232
|
+
const epoch = osPackageEpoch(pkg);
|
|
233
|
+
if (epoch) {
|
|
234
|
+
qualifiers.epoch = epoch;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return tryBuildPurl({
|
|
238
|
+
type,
|
|
239
|
+
namespace,
|
|
240
|
+
name,
|
|
241
|
+
version,
|
|
242
|
+
qualifiers: Object.keys(qualifiers).length ? qualifiers : null,
|
|
243
|
+
});
|
|
244
|
+
}
|