@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,686 @@
|
|
|
1
|
+
// Bounded, read-only probe for a runtime no package manager owns on a live
|
|
2
|
+
// host, for `-t os` / obom. osquery's package tables list what dpkg, rpm and
|
|
3
|
+
// the like installed, so a runtime built from source or unpacked from a
|
|
4
|
+
// tarball (a Docker Official image, a /usr/local or /opt build, a version
|
|
5
|
+
// manager's tree) is invisible to them, and with it the version an advisory is
|
|
6
|
+
// read against.
|
|
7
|
+
//
|
|
8
|
+
// A runtime spec names the directories to look in, a few fixed ones and never
|
|
9
|
+
// a walk of the file system, and how to read a build from a file. A candidate
|
|
10
|
+
// is opened only after the package databases have said they do not own it: a
|
|
11
|
+
// package manager's file is identified by its package, and distros backport
|
|
12
|
+
// fixes under old version numbers. Only files nothing owns are read, for their
|
|
13
|
+
// bytes; nothing is executed.
|
|
14
|
+
//
|
|
15
|
+
// A package database that exists but cannot be read leaves ownership unknown.
|
|
16
|
+
// Unknown is not unpackaged: it emits nothing and records a degradation.
|
|
17
|
+
//
|
|
18
|
+
// Logging, activity and degradation calls go through the spec's hooks, so the
|
|
19
|
+
// caller's module owns them and its tests can stub them.
|
|
20
|
+
|
|
21
|
+
import { readdirSync, realpathSync, statSync } from "node:fs";
|
|
22
|
+
import {
|
|
23
|
+
basename,
|
|
24
|
+
isAbsolute,
|
|
25
|
+
join,
|
|
26
|
+
posix,
|
|
27
|
+
relative,
|
|
28
|
+
resolve,
|
|
29
|
+
sep,
|
|
30
|
+
win32,
|
|
31
|
+
} from "node:path";
|
|
32
|
+
import process from "node:process";
|
|
33
|
+
|
|
34
|
+
import { safeExistsSync, safeReadFileSync } from "../core/fs.js";
|
|
35
|
+
import { thoughtLog } from "../core/logger.js";
|
|
36
|
+
import {
|
|
37
|
+
APK_INSTALLED_DB_PATHS,
|
|
38
|
+
readApkInstalledDb,
|
|
39
|
+
} from "./apkInstalledDb.js";
|
|
40
|
+
import { packageStoreOf } from "./phpBuildInfo.js";
|
|
41
|
+
import { tryBuildPurl } from "./purl.js";
|
|
42
|
+
|
|
43
|
+
// One dpkg list, and every dpkg list together. A database past these caps
|
|
44
|
+
// counts as unreadable instead of being read in part; the apk database has the
|
|
45
|
+
// same cap in its reader.
|
|
46
|
+
const MAX_DATABASE_FILE_BYTES = 64 * 1024 * 1024;
|
|
47
|
+
const MAX_DATABASE_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
48
|
+
const MAX_DPKG_LISTS = 100000;
|
|
49
|
+
// Paths per rpm_package_files query.
|
|
50
|
+
const RPM_QUERY_CHUNK = 200;
|
|
51
|
+
|
|
52
|
+
export function listDirectory(directory) {
|
|
53
|
+
try {
|
|
54
|
+
return readdirSync(directory).sort();
|
|
55
|
+
} catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isDirectory(path) {
|
|
61
|
+
try {
|
|
62
|
+
return statSync(path).isDirectory();
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function realPathOf(path) {
|
|
69
|
+
try {
|
|
70
|
+
return realpathSync(path);
|
|
71
|
+
} catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function quoteForOsquery(value) {
|
|
77
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function byBomRef(a, b) {
|
|
81
|
+
const left = a["bom-ref"];
|
|
82
|
+
const right = b["bom-ref"];
|
|
83
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function readBounded(file, maxBytes) {
|
|
87
|
+
try {
|
|
88
|
+
const stats = statSync(file);
|
|
89
|
+
if (!stats.isFile() || stats.size > maxBytes) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const text = safeReadFileSync(file, "utf-8");
|
|
96
|
+
return typeof text === "string" ? text : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// A package database: its name and path, `absent` when the host has none,
|
|
100
|
+
// `unreadable` when it has one that could not be read in full, and the paths of
|
|
101
|
+
// the candidates it lists.
|
|
102
|
+
function database(name, path, state = "ok") {
|
|
103
|
+
return { name, path, state, owned: new Set(), ownerOf: new Map() };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// dpkg keeps one <package>.list per package, naming each path as it is on the
|
|
107
|
+
// system.
|
|
108
|
+
function scanDpkg(infoDirectory, wanted) {
|
|
109
|
+
const result = database("dpkg", infoDirectory);
|
|
110
|
+
if (!safeExistsSync(infoDirectory)) {
|
|
111
|
+
result.state = "absent";
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
const lists = listDirectory(infoDirectory).filter((name) =>
|
|
115
|
+
name.endsWith(".list"),
|
|
116
|
+
);
|
|
117
|
+
if (!isDirectory(infoDirectory) || lists.length > MAX_DPKG_LISTS) {
|
|
118
|
+
result.state = "unreadable";
|
|
119
|
+
}
|
|
120
|
+
let total = 0;
|
|
121
|
+
for (const name of lists.slice(0, MAX_DPKG_LISTS)) {
|
|
122
|
+
const text = readBounded(
|
|
123
|
+
join(infoDirectory, name),
|
|
124
|
+
Math.min(MAX_DATABASE_FILE_BYTES, MAX_DATABASE_TOTAL_BYTES - total),
|
|
125
|
+
);
|
|
126
|
+
if (text === undefined) {
|
|
127
|
+
result.state = "unreadable";
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
total += text.length;
|
|
131
|
+
// libruby3.2:amd64.list names the package libruby3.2
|
|
132
|
+
const packageName = name.slice(0, -".list".length).split(":")[0];
|
|
133
|
+
for (const line of text.split("\n")) {
|
|
134
|
+
if (wanted.has(line)) {
|
|
135
|
+
result.owned.add(line);
|
|
136
|
+
result.ownerOf.set(line, packageName);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// apk's installed database is read by the same reader the OS inventory uses, so
|
|
144
|
+
// the packages an OBOM lists and the files this probe treats as owned follow one
|
|
145
|
+
// parser. A dry run never gets here: probeHostRuntime returns before it.
|
|
146
|
+
function scanApk(context, wanted) {
|
|
147
|
+
const { apkInstalledFile } = context.databases;
|
|
148
|
+
const apk = readApkInstalledDb({
|
|
149
|
+
rootPath: context.rootPath,
|
|
150
|
+
databaseFile: apkInstalledFile,
|
|
151
|
+
wantedFiles: wanted,
|
|
152
|
+
platform: context.platform,
|
|
153
|
+
});
|
|
154
|
+
const result = database(
|
|
155
|
+
"apk",
|
|
156
|
+
apk.path ||
|
|
157
|
+
apkInstalledFile ||
|
|
158
|
+
join(context.rootPath, APK_INSTALLED_DB_PATHS[0]),
|
|
159
|
+
);
|
|
160
|
+
result.state = { ok: "ok", unreadable: "unreadable" }[apk.state] || "absent";
|
|
161
|
+
for (const pkg of apk.packages) {
|
|
162
|
+
for (const file of pkg.files) {
|
|
163
|
+
result.owned.add(file);
|
|
164
|
+
if (pkg.name) {
|
|
165
|
+
result.ownerOf.set(file, pkg.name);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// osquery's rpm_package_files answers for a path without being told the
|
|
173
|
+
// package.
|
|
174
|
+
function queryRpm(databaseDirectories, wanted, executeOsQuery) {
|
|
175
|
+
const result = database("rpm", databaseDirectories[0]);
|
|
176
|
+
if (!databaseDirectories.some((directory) => safeExistsSync(directory))) {
|
|
177
|
+
result.state = "absent";
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
if (!wanted.size) {
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
if (typeof executeOsQuery !== "function") {
|
|
184
|
+
result.state = "unreadable";
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
// The query travels as one command-line argument, which Linux caps at
|
|
188
|
+
// 128 KiB, so a long list of paths is asked in chunks.
|
|
189
|
+
const paths = [...wanted];
|
|
190
|
+
const rows = [];
|
|
191
|
+
for (let start = 0; start < paths.length; start += RPM_QUERY_CHUNK) {
|
|
192
|
+
const chunk = executeOsQuery(
|
|
193
|
+
`SELECT package, path FROM rpm_package_files WHERE path IN (${paths
|
|
194
|
+
.slice(start, start + RPM_QUERY_CHUNK)
|
|
195
|
+
.map(quoteForOsquery)
|
|
196
|
+
.join(", ")})`,
|
|
197
|
+
);
|
|
198
|
+
if (!Array.isArray(chunk)) {
|
|
199
|
+
result.state = "unreadable";
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
rows.push(...chunk);
|
|
203
|
+
}
|
|
204
|
+
for (const row of rows) {
|
|
205
|
+
if (typeof row?.path === "string") {
|
|
206
|
+
result.owned.add(row.path);
|
|
207
|
+
if (typeof row.package === "string" && row.package) {
|
|
208
|
+
result.ownerOf.set(row.path, row.package);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Chocolatey's database is the list of packages the OS scan has already made.
|
|
216
|
+
// The client is itself a package, so a Chocolatey with none listed was not
|
|
217
|
+
// listed: the query failed.
|
|
218
|
+
function readChocolatey(context) {
|
|
219
|
+
const configured =
|
|
220
|
+
context.readEnv("ChocolateyInstall") ||
|
|
221
|
+
join(context.rootPath, "ProgramData", "chocolatey");
|
|
222
|
+
const result = database("chocolatey", configured);
|
|
223
|
+
if (!safeExistsSync(configured)) {
|
|
224
|
+
result.state = "absent";
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
const packages = (context.packageComponents || []).filter((component) =>
|
|
228
|
+
component?.purl?.startsWith("pkg:chocolatey/"),
|
|
229
|
+
);
|
|
230
|
+
if (!packages.length) {
|
|
231
|
+
result.state = "unreadable";
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
result.directory = realPathOf(configured) || resolve(configured);
|
|
235
|
+
result.packageNames = new Set(
|
|
236
|
+
packages.map((component) => `${component.name}`.toLowerCase()),
|
|
237
|
+
);
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// A Chocolatey package can unpack outside the package's own folder: php under
|
|
242
|
+
// C:\tools, nodejs.install through the Node.js MSI into Program Files. What
|
|
243
|
+
// makes a file Chocolatey's is the runtime's package being installed and the
|
|
244
|
+
// file being where Chocolatey keeps it or where the spec says the package puts
|
|
245
|
+
// it.
|
|
246
|
+
function chocolateyOwns(chocolatey, context, candidate) {
|
|
247
|
+
const names = context.spec.chocolateyPackages || [];
|
|
248
|
+
if (!names.some((name) => chocolatey.packageNames?.has(name))) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const directories = [
|
|
252
|
+
chocolatey.directory,
|
|
253
|
+
...(context.spec.chocolateyDirectories?.(context) || []).map(
|
|
254
|
+
(directory) => realPathOf(directory) || resolve(directory),
|
|
255
|
+
),
|
|
256
|
+
];
|
|
257
|
+
const real = candidate.real.toLowerCase();
|
|
258
|
+
return directories.some((directory) =>
|
|
259
|
+
real.startsWith(`${directory.toLowerCase().replace(/[\\/]+$/, "")}${sep}`),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Folders that hold many products. An installer that names one of them as its
|
|
264
|
+
// own install location would otherwise claim every file below it.
|
|
265
|
+
const SHARED_WINDOWS_FOLDERS = new Set([
|
|
266
|
+
"program files",
|
|
267
|
+
"program files (x86)",
|
|
268
|
+
"programdata",
|
|
269
|
+
"users",
|
|
270
|
+
"windows",
|
|
271
|
+
]);
|
|
272
|
+
|
|
273
|
+
// Windows Installer records each product's install folder; the OBOM's
|
|
274
|
+
// windows_programs rows carry it as install_location. A host always has some
|
|
275
|
+
// installed products, so with no rows at all the table was not read, and the
|
|
276
|
+
// installer cannot be asked.
|
|
277
|
+
function readWindowsInstaller(context) {
|
|
278
|
+
const result = database("windows installer", "programs");
|
|
279
|
+
const programs = (context.packageComponents || []).filter((component) =>
|
|
280
|
+
component?.properties?.some(
|
|
281
|
+
(property) =>
|
|
282
|
+
property?.name === "cdx:osquery:category" &&
|
|
283
|
+
property.value === "windows_programs",
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
if (!programs.length) {
|
|
287
|
+
result.state = "absent";
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
result.locations = [];
|
|
291
|
+
for (const component of programs) {
|
|
292
|
+
const location = component.properties.find(
|
|
293
|
+
(property) => property?.name === "install_location",
|
|
294
|
+
)?.value;
|
|
295
|
+
if (typeof location !== "string" || !win32.isAbsolute(location)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const normalized = win32
|
|
299
|
+
.normalize(location)
|
|
300
|
+
.replace(/\\+$/, "")
|
|
301
|
+
.toLowerCase();
|
|
302
|
+
const parts = normalized.split("\\").slice(1);
|
|
303
|
+
if (
|
|
304
|
+
!parts.length ||
|
|
305
|
+
(parts.length === 1 && SHARED_WINDOWS_FOLDERS.has(parts[0]))
|
|
306
|
+
) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
result.locations.push(`${normalized}\\`);
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function windowsInstallerOwns(installer, candidate) {
|
|
315
|
+
return [candidate.logical, candidate.listedLogical].some(
|
|
316
|
+
(path) =>
|
|
317
|
+
typeof path === "string" &&
|
|
318
|
+
installer.locations.some((location) =>
|
|
319
|
+
path.toLowerCase().startsWith(location),
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// A path as the system being described names it, and as a purl subpath. `base`
|
|
325
|
+
// is the directory the path hangs off, so that a symlinked root, as a temporary
|
|
326
|
+
// directory often is, does not matter.
|
|
327
|
+
function locate(context, base, absolutePath) {
|
|
328
|
+
const parts = relative(base, absolutePath).split(sep);
|
|
329
|
+
return {
|
|
330
|
+
logical: context.pathApi.join(context.logicalRoot, ...parts),
|
|
331
|
+
subpath: parts.join("/"),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* The distinct runtime files of the spec's directories, at most `maxProbes`.
|
|
337
|
+
* A symlink is read through to the file it names, which must itself be a
|
|
338
|
+
* runtime file under the root, and two names for one file are one candidate.
|
|
339
|
+
*
|
|
340
|
+
* @param {Object} context probe context
|
|
341
|
+
* @returns {Object[]} candidates with their real path, kind and locations
|
|
342
|
+
*/
|
|
343
|
+
function collectCandidates(context) {
|
|
344
|
+
const { realRoot, platform, maxProbes, spec } = context;
|
|
345
|
+
const classify = (name) => spec.classify(name, platform);
|
|
346
|
+
const candidates = [];
|
|
347
|
+
const seen = new Set();
|
|
348
|
+
for (const { directory, only } of spec.directories(context)) {
|
|
349
|
+
for (const name of listDirectory(directory)) {
|
|
350
|
+
if (!classify(name)) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const listed = join(directory, name);
|
|
354
|
+
const real = realPathOf(listed);
|
|
355
|
+
if (!real || seen.has(real)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const kind = classify(basename(real));
|
|
359
|
+
const inside = relative(realRoot, real);
|
|
360
|
+
if (
|
|
361
|
+
!kind ||
|
|
362
|
+
(only && kind !== only) ||
|
|
363
|
+
!inside ||
|
|
364
|
+
inside.startsWith("..") ||
|
|
365
|
+
isAbsolute(inside)
|
|
366
|
+
) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
if (!statSync(real).isFile()) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
} catch {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (candidates.length >= maxProbes) {
|
|
377
|
+
thoughtLog(
|
|
378
|
+
`The ${spec.label} host probe looked at ${maxProbes} files and left any others unread.`,
|
|
379
|
+
);
|
|
380
|
+
return candidates;
|
|
381
|
+
}
|
|
382
|
+
seen.add(real);
|
|
383
|
+
candidates.push({
|
|
384
|
+
real,
|
|
385
|
+
kind,
|
|
386
|
+
name: basename(real),
|
|
387
|
+
...locate(context, realRoot, real),
|
|
388
|
+
listedLogical: locate(context, context.rootPath, listed).logical,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return candidates;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function recordUnreadable(unreadable, recorded, context) {
|
|
396
|
+
if (recorded.has(unreadable.name)) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
recorded.add(unreadable.name);
|
|
400
|
+
const { spec } = context;
|
|
401
|
+
spec.hooks.recordDegradation(spec.remediationId, {
|
|
402
|
+
ecosystem: spec.ecosystem,
|
|
403
|
+
tool: unreadable.name,
|
|
404
|
+
impact: "components",
|
|
405
|
+
path: unreadable.path,
|
|
406
|
+
detail: `The ${unreadable.name} package database could not be read in full, so ${spec.label} files it may own were not identified as unpackaged ${spec.label}.`,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Sort the candidates into those a package owns, those whose ownership could
|
|
412
|
+
* not be settled, and the unpackaged rest.
|
|
413
|
+
*
|
|
414
|
+
* @param {Object[]} candidates candidates from collectCandidates
|
|
415
|
+
* @param {Object} context probe context
|
|
416
|
+
* @returns {{unpackaged: Object[], owned: number, unknown: number}}
|
|
417
|
+
*/
|
|
418
|
+
function classifyOwnership(candidates, context) {
|
|
419
|
+
const unpackaged = [];
|
|
420
|
+
const recorded = new Set();
|
|
421
|
+
let owned = 0;
|
|
422
|
+
let unknown = 0;
|
|
423
|
+
const pending = candidates.filter((candidate) => {
|
|
424
|
+
if (packageStoreOf(candidate.logical)) {
|
|
425
|
+
owned++;
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
return true;
|
|
429
|
+
});
|
|
430
|
+
if (!pending.length) {
|
|
431
|
+
return { unpackaged, owned, unknown };
|
|
432
|
+
}
|
|
433
|
+
const wanted = new Set(
|
|
434
|
+
pending.flatMap((candidate) => [
|
|
435
|
+
candidate.logical,
|
|
436
|
+
candidate.listedLogical,
|
|
437
|
+
]),
|
|
438
|
+
);
|
|
439
|
+
const databases = [];
|
|
440
|
+
if (context.platform === "win32") {
|
|
441
|
+
databases.push(readChocolatey(context), readWindowsInstaller(context));
|
|
442
|
+
} else {
|
|
443
|
+
const { dpkgInfoDirectory, rpmDatabaseDirectories } = context.databases;
|
|
444
|
+
databases.push(
|
|
445
|
+
scanDpkg(dpkgInfoDirectory, wanted),
|
|
446
|
+
scanApk(context, wanted),
|
|
447
|
+
);
|
|
448
|
+
// Only what dpkg and apk did not claim is asked of osquery.
|
|
449
|
+
const claimed = new Set(databases.flatMap((db) => [...db.owned]));
|
|
450
|
+
databases.push(
|
|
451
|
+
queryRpm(
|
|
452
|
+
rpmDatabaseDirectories,
|
|
453
|
+
new Set([...wanted].filter((path) => !claimed.has(path))),
|
|
454
|
+
context.executeOsQuery,
|
|
455
|
+
),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
for (const candidate of pending) {
|
|
459
|
+
const paths = [candidate.logical, candidate.listedLogical];
|
|
460
|
+
const isOwned = databases.some(
|
|
461
|
+
(db) =>
|
|
462
|
+
db.state === "ok" &&
|
|
463
|
+
(db.name === "chocolatey"
|
|
464
|
+
? chocolateyOwns(db, context, candidate)
|
|
465
|
+
: db.name === "windows installer"
|
|
466
|
+
? windowsInstallerOwns(db, candidate)
|
|
467
|
+
: paths.some((path) => db.owned.has(path))),
|
|
468
|
+
);
|
|
469
|
+
if (isOwned) {
|
|
470
|
+
owned++;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const unreadable = databases.filter((db) => db.state === "unreadable");
|
|
474
|
+
if (!unreadable.length) {
|
|
475
|
+
unpackaged.push(candidate);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
unknown++;
|
|
479
|
+
for (const db of unreadable) {
|
|
480
|
+
recordUnreadable(db, recorded, context);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return { unpackaged, owned, unknown };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Like a container's unpackaged files, but named by where they are: a host can
|
|
487
|
+
// hold several binaries of one name.
|
|
488
|
+
function fileComponent(candidate) {
|
|
489
|
+
const purl = tryBuildPurl({
|
|
490
|
+
type: "generic",
|
|
491
|
+
name: candidate.name,
|
|
492
|
+
subpath: candidate.subpath,
|
|
493
|
+
});
|
|
494
|
+
return {
|
|
495
|
+
name: candidate.name,
|
|
496
|
+
type: "file",
|
|
497
|
+
...(purl ? { purl } : {}),
|
|
498
|
+
"bom-ref": purl ? decodeURIComponent(purl) : `file:${candidate.logical}`,
|
|
499
|
+
properties: [{ name: "internal:SrcFile", value: candidate.logical }],
|
|
500
|
+
evidence: {
|
|
501
|
+
identity: [
|
|
502
|
+
{
|
|
503
|
+
field: "purl",
|
|
504
|
+
confidence: 0,
|
|
505
|
+
methods: [
|
|
506
|
+
{ technique: "filename", confidence: 0, value: candidate.logical },
|
|
507
|
+
],
|
|
508
|
+
concludedValue: candidate.logical,
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function createContext(options, platform, readEnv, logicalRoot, spec) {
|
|
516
|
+
const rootPath = options.rootPath || logicalRoot;
|
|
517
|
+
const realRoot = realPathOf(rootPath);
|
|
518
|
+
if (!realRoot) {
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
const databases = options.packageDatabases || {};
|
|
522
|
+
return {
|
|
523
|
+
platform,
|
|
524
|
+
readEnv,
|
|
525
|
+
rootPath,
|
|
526
|
+
realRoot,
|
|
527
|
+
logicalRoot,
|
|
528
|
+
pathApi: platform === "win32" ? win32 : posix,
|
|
529
|
+
maxProbes: options.maxProbes || spec.maxProbes,
|
|
530
|
+
spec,
|
|
531
|
+
executeOsQuery: options.executeOsQuery,
|
|
532
|
+
packageComponents: options.packageComponents,
|
|
533
|
+
databases: {
|
|
534
|
+
dpkgInfoDirectory:
|
|
535
|
+
databases.dpkgInfoDirectory || join(rootPath, "var/lib/dpkg/info"),
|
|
536
|
+
// Unset, the reader looks where apk keeps the database under the root.
|
|
537
|
+
apkInstalledFile: databases.apkInstalledFile,
|
|
538
|
+
rpmDatabaseDirectories: databases.rpmDatabaseDirectories || [
|
|
539
|
+
join(rootPath, "var/lib/rpm"),
|
|
540
|
+
join(rootPath, "usr/lib/sysimage/rpm"),
|
|
541
|
+
],
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Look for a runtime no package manager owns, in the directories a spec names.
|
|
548
|
+
* Components come back only for the files nothing owns: a `file` component for
|
|
549
|
+
* each runtime binary or library the spec could read a build from, the
|
|
550
|
+
* components the spec's inventory builds from them, and the edges between them.
|
|
551
|
+
*
|
|
552
|
+
* @param {Object} options probe options, as probeHostPhp documents them
|
|
553
|
+
* @param {Object} spec the runtime: `label`, `ecosystem`, `capability`,
|
|
554
|
+
* `remediationId` (a remediation catalog id, spelled out by the caller),
|
|
555
|
+
* `maxProbes`, `directories(context)`, `classify(name, platform)`,
|
|
556
|
+
* `readBuild(context, candidate)`, `createInventory()`,
|
|
557
|
+
* `attach(inventory, component, build, candidate)`, `finish(inventory)`,
|
|
558
|
+
* optional `chocolateyPackages` and `chocolateyDirectories(context)` (where
|
|
559
|
+
* those packages unpack outside Chocolatey's folder), and `hooks` carrying
|
|
560
|
+
* the caller's
|
|
561
|
+
* `isDryRun`, `readEnvironmentVariable`, `recordActivity` and
|
|
562
|
+
* `recordDegradation`
|
|
563
|
+
* @returns {{components: Object[], dependencies: Object[]}}
|
|
564
|
+
*/
|
|
565
|
+
export function probeHostRuntime(options, spec) {
|
|
566
|
+
const empty = { components: [], dependencies: [] };
|
|
567
|
+
const { hooks } = spec;
|
|
568
|
+
const platform = options.platform || process.platform;
|
|
569
|
+
const readEnv = (name) =>
|
|
570
|
+
options.env ? options.env[name] : hooks.readEnvironmentVariable(name);
|
|
571
|
+
const logicalRoot =
|
|
572
|
+
platform === "win32" ? `${readEnv("SystemDrive") || "C:"}\\` : "/";
|
|
573
|
+
if (hooks.isDryRun) {
|
|
574
|
+
hooks.recordActivity({
|
|
575
|
+
capability: spec.capability,
|
|
576
|
+
kind: "read",
|
|
577
|
+
reason: `Dry run mode blocks the ${spec.label} host probe, which opens executables and package databases outside the project.`,
|
|
578
|
+
status: "blocked",
|
|
579
|
+
target: options.rootPath || logicalRoot,
|
|
580
|
+
});
|
|
581
|
+
return empty;
|
|
582
|
+
}
|
|
583
|
+
const context = createContext(options, platform, readEnv, logicalRoot, spec);
|
|
584
|
+
if (!context) {
|
|
585
|
+
return empty;
|
|
586
|
+
}
|
|
587
|
+
const candidates = collectCandidates(context);
|
|
588
|
+
if (!candidates.length) {
|
|
589
|
+
return empty;
|
|
590
|
+
}
|
|
591
|
+
const { unpackaged, owned, unknown } = classifyOwnership(candidates, context);
|
|
592
|
+
const inventory = spec.createInventory();
|
|
593
|
+
const files = [];
|
|
594
|
+
for (const candidate of unpackaged) {
|
|
595
|
+
const build = spec.readBuild(context, candidate);
|
|
596
|
+
if (build) {
|
|
597
|
+
const component = fileComponent(candidate);
|
|
598
|
+
spec.attach(inventory, component, build, candidate);
|
|
599
|
+
files.push(component);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
hooks.recordActivity({
|
|
603
|
+
capability: spec.capability,
|
|
604
|
+
kind: "read",
|
|
605
|
+
reason: `Looked at ${candidates.length} ${spec.label} file(s) in the fixed directories: ${owned} owned by a package, ${unknown} of unknown ownership, ${files.length} unpackaged ${spec.label} identified.`,
|
|
606
|
+
status: "completed",
|
|
607
|
+
target: context.rootPath,
|
|
608
|
+
});
|
|
609
|
+
const finished = spec.finish(inventory);
|
|
610
|
+
if (files.length) {
|
|
611
|
+
thoughtLog(
|
|
612
|
+
`I found ${files.length} unpackaged ${spec.label} file(s) on this host.`,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
components: [
|
|
617
|
+
...files.sort(byBomRef),
|
|
618
|
+
...finished.components.sort(byBomRef),
|
|
619
|
+
],
|
|
620
|
+
dependencies: finished.dependencies,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Ask the package databases of a POSIX host which package owns each path.
|
|
626
|
+
* The paths are named as the host names them (`/usr/lib/ruby/...`). A
|
|
627
|
+
* database that exists but cannot be read is reported, so the caller can tell
|
|
628
|
+
* "no owner" from "unknown".
|
|
629
|
+
*
|
|
630
|
+
* @param {Object} options probe options, as probeHostRuntime takes them
|
|
631
|
+
* @param {string[]} paths logical paths to look up
|
|
632
|
+
* @returns {{owners: Map<string, {database: string, packageName: string}>,
|
|
633
|
+
* unreadable: string[]}}
|
|
634
|
+
*/
|
|
635
|
+
export function findHostPathOwners(options, paths) {
|
|
636
|
+
const owners = new Map();
|
|
637
|
+
const platform = options.platform || process.platform;
|
|
638
|
+
const rootPath = options.rootPath || "/";
|
|
639
|
+
if (platform === "win32" || !paths?.length || !realPathOf(rootPath)) {
|
|
640
|
+
return { owners, unreadable: [] };
|
|
641
|
+
}
|
|
642
|
+
const configured = options.packageDatabases || {};
|
|
643
|
+
const context = {
|
|
644
|
+
platform,
|
|
645
|
+
rootPath,
|
|
646
|
+
executeOsQuery: options.executeOsQuery,
|
|
647
|
+
databases: {
|
|
648
|
+
apkInstalledFile: configured.apkInstalledFile,
|
|
649
|
+
},
|
|
650
|
+
};
|
|
651
|
+
const wanted = new Set(paths);
|
|
652
|
+
const databases = [
|
|
653
|
+
scanDpkg(
|
|
654
|
+
configured.dpkgInfoDirectory || join(rootPath, "var/lib/dpkg/info"),
|
|
655
|
+
wanted,
|
|
656
|
+
),
|
|
657
|
+
scanApk(context, wanted),
|
|
658
|
+
];
|
|
659
|
+
const claimed = new Set(databases.flatMap((db) => [...db.owned]));
|
|
660
|
+
databases.push(
|
|
661
|
+
queryRpm(
|
|
662
|
+
configured.rpmDatabaseDirectories || [
|
|
663
|
+
join(rootPath, "var/lib/rpm"),
|
|
664
|
+
join(rootPath, "usr/lib/sysimage/rpm"),
|
|
665
|
+
],
|
|
666
|
+
new Set([...wanted].filter((path) => !claimed.has(path))),
|
|
667
|
+
options.executeOsQuery,
|
|
668
|
+
),
|
|
669
|
+
);
|
|
670
|
+
for (const db of databases) {
|
|
671
|
+
if (db.state !== "ok") {
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
for (const [path, packageName] of db.ownerOf) {
|
|
675
|
+
if (!owners.has(path)) {
|
|
676
|
+
owners.set(path, { database: db.name, packageName });
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return {
|
|
681
|
+
owners,
|
|
682
|
+
unreadable: databases
|
|
683
|
+
.filter((db) => db.state === "unreadable")
|
|
684
|
+
.map((db) => db.name),
|
|
685
|
+
};
|
|
686
|
+
}
|