@cdxgen/cdxgen 13.2.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 +33 -14
- package/bin/cdxgen.js +310 -138
- package/bin/evinse.js +22 -0
- package/bin/repl.js +86 -0
- package/bin/sign.js +119 -15
- package/bin/tracebom.js +69 -5
- package/bin/validate.js +107 -19
- package/bin/verify.js +172 -63
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +85 -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/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +156 -58
- package/lib/cli/jsBom.js +99 -53
- package/lib/cli/jvmBom.js +708 -147
- package/lib/cli/managedBom.js +415 -224
- package/lib/cli/nativeBom.js +264 -44
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +22 -15
- package/lib/core/fs.js +176 -7
- package/lib/core/httpClient.js +159 -24
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +31 -4
- 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 +274 -108
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +76 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +399 -51
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1983 -793
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +356 -37
- package/lib/ecosystems/jsEvidence.js +104 -44
- package/lib/ecosystems/kosi.js +31 -7
- package/lib/ecosystems/npmutils.js +174 -9
- package/lib/ecosystems/parsers-dotnet.js +616 -159
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +371 -70
- package/lib/ecosystems/parsers-js.js +263 -76
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +929 -216
- package/lib/ecosystems/parsers-python.js +167 -24
- package/lib/ecosystems/parsers-rust.js +28 -22
- 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 +1492 -182
- package/lib/ecosystems/rusi.js +54 -10
- 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 +1008 -191
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +1101 -222
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +703 -43
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +798 -57
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +90 -6
- package/lib/inventory/evidenceUtils.js +166 -113
- package/lib/inventory/fetchBatch.js +690 -55
- 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/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +45 -27
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +295 -25
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +467 -46
- package/lib/managers/oci.js +63 -3
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +466 -0
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- 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 +83 -16
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +64 -11
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +2 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +23 -23
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- 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.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 +55 -0
- 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/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- 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/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- 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 +12 -5
- 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/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +28 -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 +17 -1
- 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 +70 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +57 -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 +54 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +16 -5
- 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 +70 -4
- 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-rust.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 +164 -3
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- 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 +132 -15
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +182 -13
- 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 +219 -5
- 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/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.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/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +121 -9
- 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/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.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 +59 -1
- 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/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +17 -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 +77 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- 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/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- 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/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +18 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
|
@@ -4,13 +4,13 @@ import process from "node:process";
|
|
|
4
4
|
import { URL } from "node:url";
|
|
5
5
|
|
|
6
6
|
import { build } from "@cdxgen/cdx-purl";
|
|
7
|
-
import * as toml from "smol-toml";
|
|
8
7
|
import { parse as _load } from "yaml";
|
|
9
8
|
|
|
10
9
|
import { DEBUG_MODE } from "../core/activity.js";
|
|
11
10
|
import { recordDegradation } from "../core/buildLedger.js";
|
|
12
11
|
import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
|
|
13
12
|
import { PYTHON_STD_MODULES } from "../core/state.js";
|
|
13
|
+
import { parseToml } from "../core/toml.js";
|
|
14
14
|
import { tryBuildPurl } from "../inventory/purl.js";
|
|
15
15
|
import {
|
|
16
16
|
createExternalReferenceKey,
|
|
@@ -95,8 +95,11 @@ export function parsePyRequiresDist(dist_string) {
|
|
|
95
95
|
* Method to parse pipfile.lock data
|
|
96
96
|
*
|
|
97
97
|
* @param {Object} lockData JSON data from Pipfile.lock
|
|
98
|
+
* @param {Object[]} [installedMetadata] Components parsed from the installed
|
|
99
|
+
* `*.dist-info/METADATA` files of this machine, which answer before PyPI
|
|
100
|
+
* is asked
|
|
98
101
|
*/
|
|
99
|
-
export async function parsePiplockData(lockData) {
|
|
102
|
+
export async function parsePiplockData(lockData, installedMetadata) {
|
|
100
103
|
const pkgList = [];
|
|
101
104
|
Object.keys(lockData)
|
|
102
105
|
.filter((i) => i !== "_meta")
|
|
@@ -105,11 +108,16 @@ export async function parsePiplockData(lockData) {
|
|
|
105
108
|
Object.keys(depBlock).forEach((p) => {
|
|
106
109
|
const pkg = depBlock[p];
|
|
107
110
|
if (Object.hasOwn(pkg, "version")) {
|
|
108
|
-
|
|
111
|
+
// `===` (arbitrary equality) pins as tightly as `==`, so both are
|
|
112
|
+
// stripped whole rather than leaving a stray `=` behind.
|
|
113
|
+
const versionStr = pkg.version.replace(/^\s*={2,3}\s*/, "");
|
|
109
114
|
pkgList.push({ name: p, version: versionStr });
|
|
110
115
|
}
|
|
111
116
|
});
|
|
112
117
|
});
|
|
118
|
+
// The installed distributions answer first, so PyPI is asked only about
|
|
119
|
+
// the packages they do not cover.
|
|
120
|
+
enrichFromInstalledMetadata(pkgList, installedMetadata);
|
|
113
121
|
return await getPyMetadata(pkgList, false);
|
|
114
122
|
}
|
|
115
123
|
|
|
@@ -272,8 +280,12 @@ function collectPythonManifestSource(pkg) {
|
|
|
272
280
|
{ kind: "url", value: pkg?.vcs?.url },
|
|
273
281
|
{ kind: "url", value: pkg?.source?.url },
|
|
274
282
|
{ kind: "path", value: pkg?.source?.path },
|
|
283
|
+
{ kind: "path", value: pkg?.source?.directory },
|
|
275
284
|
{ kind: "path", value: pkg?.source?.editable },
|
|
276
285
|
{ kind: "path", value: pkg?.source?.virtual },
|
|
286
|
+
{ kind: "path", value: pkg?.directory?.path },
|
|
287
|
+
{ kind: "url", value: pkg?.archive?.url },
|
|
288
|
+
{ kind: "path", value: pkg?.archive?.path },
|
|
277
289
|
];
|
|
278
290
|
for (const candidate of sourceCandidates) {
|
|
279
291
|
if (typeof candidate.value !== "string" || !candidate.value.trim()) {
|
|
@@ -393,7 +405,7 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
393
405
|
const groupDepsKeys = {};
|
|
394
406
|
const dependencySourceMap = {};
|
|
395
407
|
try {
|
|
396
|
-
tomlData =
|
|
408
|
+
tomlData = parseToml(readFileSync(tomlFile, { encoding: "utf-8" }));
|
|
397
409
|
} catch (err) {
|
|
398
410
|
console.log(`Error while parsing the pyproject file ${tomlFile}.`, err);
|
|
399
411
|
}
|
|
@@ -786,14 +798,114 @@ function collectPythonLockMetadataDistributionReferences(fileEntries) {
|
|
|
786
798
|
return distributionReferences;
|
|
787
799
|
}
|
|
788
800
|
|
|
801
|
+
/**
|
|
802
|
+
* Normalise a Python distribution name the way PEP 503 does, so an installed
|
|
803
|
+
* distribution and the lockfile entry it belongs to compare equal whatever
|
|
804
|
+
* separator and case each of them used.
|
|
805
|
+
*
|
|
806
|
+
* @param {string} value Distribution name
|
|
807
|
+
* @returns {string|undefined} The normalised name, or undefined for no name
|
|
808
|
+
*/
|
|
809
|
+
export function normalizePep503Name(value) {
|
|
810
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
811
|
+
return undefined;
|
|
812
|
+
}
|
|
813
|
+
return value
|
|
814
|
+
.trim()
|
|
815
|
+
.toLowerCase()
|
|
816
|
+
.replaceAll(/[-_.]+/g, "-");
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* The key an installed distribution and a lockfile entry are matched by: the
|
|
821
|
+
* PEP 503 normalised name and the version.
|
|
822
|
+
*
|
|
823
|
+
* @param {Object} p Component with `name` and `version`
|
|
824
|
+
* @returns {string|undefined} The key, or undefined without a name or version
|
|
825
|
+
*/
|
|
826
|
+
export function installedMetadataKey(p) {
|
|
827
|
+
const name = normalizePep503Name(p?.name);
|
|
828
|
+
const version = typeof p?.version === "string" ? p.version.trim() : "";
|
|
829
|
+
return name && version ? `${name}@${version}` : undefined;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Enrich lockfile entries from the installed distributions on this machine,
|
|
834
|
+
* before PyPI is asked.
|
|
835
|
+
*
|
|
836
|
+
* Each entry of `installedMetadata` is a component as
|
|
837
|
+
* {@link parseBdistMetadata} builds it from an installed `*.dist-info/METADATA`
|
|
838
|
+
* file. An entry is matched to the lockfile component with the same PEP 503
|
|
839
|
+
* normalised name and the same version, and fills the fields it lacks, so the
|
|
840
|
+
* registry is left only what the installed distributions do not answer. An
|
|
841
|
+
* installed release of another version describes a different release, so it
|
|
842
|
+
* enriches nothing. The distributions enrich the entries in place; no new
|
|
843
|
+
* components are created.
|
|
844
|
+
*
|
|
845
|
+
* @param {Object[]} pkgList Lockfile components, enriched in place
|
|
846
|
+
* @param {Object[]} installedMetadata Components parsed from installed
|
|
847
|
+
* `*.dist-info/METADATA` files
|
|
848
|
+
* @returns {Set<string>} The keys of the installations that enriched an entry,
|
|
849
|
+
* as {@link installedMetadataKey} builds them, so a caller that also walks
|
|
850
|
+
* the METADATA files can skip those as components
|
|
851
|
+
*/
|
|
852
|
+
export function enrichFromInstalledMetadata(pkgList, installedMetadata) {
|
|
853
|
+
const byKey = new Map();
|
|
854
|
+
for (const p of pkgList || []) {
|
|
855
|
+
const key = installedMetadataKey(p);
|
|
856
|
+
if (key && !byKey.has(key)) {
|
|
857
|
+
byKey.set(key, p);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
const enriched = new Set();
|
|
861
|
+
for (const md of installedMetadata || []) {
|
|
862
|
+
const key = installedMetadataKey(md);
|
|
863
|
+
const p = key ? byKey.get(key) : undefined;
|
|
864
|
+
if (!p) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
enriched.add(key);
|
|
868
|
+
if (!p.description?.trim() && md.description?.trim()) {
|
|
869
|
+
p.description = md.description;
|
|
870
|
+
}
|
|
871
|
+
if (!p.license && md.licenses?.length) {
|
|
872
|
+
// The licence field holds the names a lockfile consumer expects; the
|
|
873
|
+
// CycloneDX licence objects travel in `licenses`.
|
|
874
|
+
p.license = md.licenses
|
|
875
|
+
.map((alicense) => alicense.expression || alicense.license?.name)
|
|
876
|
+
.filter((aname) => aname?.length);
|
|
877
|
+
p.licenses = md.licenses;
|
|
878
|
+
}
|
|
879
|
+
if (!p.homepage && md.homepage) {
|
|
880
|
+
p.homepage = md.homepage;
|
|
881
|
+
}
|
|
882
|
+
if (!p.repository && md.repository) {
|
|
883
|
+
p.repository = md.repository;
|
|
884
|
+
}
|
|
885
|
+
if (!p.author && md.author) {
|
|
886
|
+
p.author = md.author;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return enriched;
|
|
890
|
+
}
|
|
891
|
+
|
|
789
892
|
/**
|
|
790
893
|
* Method to parse python lock files such as poetry.lock, pdm.lock, uv.lock, and pylock.toml.
|
|
791
894
|
*
|
|
792
895
|
* @param {string} lockData Raw TOML text from poetry.lock, pdm.lock, uv.lock, or pylock.toml
|
|
793
896
|
* @param {string} lockFile Lock file name for evidence
|
|
794
897
|
* @param {string} pyProjectFile pyproject.toml file
|
|
898
|
+
* @param {Object} [options] Options
|
|
899
|
+
* @param {Object[]} [options.installedMetadata] Components parsed from the
|
|
900
|
+
* installed `*.dist-info/METADATA` files of this machine, which answer
|
|
901
|
+
* before PyPI is asked
|
|
795
902
|
*/
|
|
796
|
-
export async function parsePyLockData(
|
|
903
|
+
export async function parsePyLockData(
|
|
904
|
+
lockData,
|
|
905
|
+
lockFile,
|
|
906
|
+
pyProjectFile,
|
|
907
|
+
options = {},
|
|
908
|
+
) {
|
|
797
909
|
let pkgList = [];
|
|
798
910
|
const rootList = [];
|
|
799
911
|
const dependenciesList = [];
|
|
@@ -940,7 +1052,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
940
1052
|
}
|
|
941
1053
|
let lockTomlObj;
|
|
942
1054
|
try {
|
|
943
|
-
lockTomlObj =
|
|
1055
|
+
lockTomlObj = parseToml(lockData);
|
|
944
1056
|
} catch (err) {
|
|
945
1057
|
if (lockFile) {
|
|
946
1058
|
// The failing file names the manager whose lock command regenerates
|
|
@@ -1416,6 +1528,9 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
1416
1528
|
dependsOn: [...dependsOnList].sort(),
|
|
1417
1529
|
});
|
|
1418
1530
|
}
|
|
1531
|
+
// The installed distributions answer first, so PyPI is asked only about the
|
|
1532
|
+
// packages they do not cover.
|
|
1533
|
+
enrichFromInstalledMetadata(pkgList, options.installedMetadata);
|
|
1419
1534
|
pkgList = await getPyMetadata(pkgList, false);
|
|
1420
1535
|
return {
|
|
1421
1536
|
parentComponent,
|
|
@@ -1427,16 +1542,33 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
1427
1542
|
};
|
|
1428
1543
|
}
|
|
1429
1544
|
|
|
1545
|
+
/**
|
|
1546
|
+
* First version clause of a PEP 508 requirement: an operator and the version
|
|
1547
|
+
* that follows it.
|
|
1548
|
+
*
|
|
1549
|
+
* `===` (arbitrary equality) is tried before `==`, whitespace may separate the
|
|
1550
|
+
* operator from the version, and a version may carry a PEP 440 epoch (`1!2.0`)
|
|
1551
|
+
* or a local version label (`2.1.0+cpu`).
|
|
1552
|
+
*/
|
|
1553
|
+
const PEP440_VERSION_CLAUSE = /(===|==|!=|<=|>=|~=|<|>)\s*([0-9A-Za-z.*_+!-]*)/;
|
|
1554
|
+
|
|
1430
1555
|
/**
|
|
1431
1556
|
* Method to parse requirements.txt file. This must be replaced with atom parsedeps.
|
|
1432
1557
|
*
|
|
1433
1558
|
* @param {String} reqFile Requirements.txt file
|
|
1434
1559
|
* @param {Boolean} fetchDepsInfo Fetch dependencies info from pypi
|
|
1560
|
+
* @param {Object[]} [installedMetadata] Components parsed from the installed
|
|
1561
|
+
* `*.dist-info/METADATA` files of this machine, which answer before PyPI
|
|
1562
|
+
* is asked
|
|
1435
1563
|
*
|
|
1436
1564
|
* @returns {Promise[Array<Object>]} List of direct dependencies from the requirements file
|
|
1437
1565
|
*/
|
|
1438
|
-
export async function parseReqFile(
|
|
1439
|
-
|
|
1566
|
+
export async function parseReqFile(
|
|
1567
|
+
reqFile,
|
|
1568
|
+
fetchDepsInfo = false,
|
|
1569
|
+
installedMetadata = undefined,
|
|
1570
|
+
) {
|
|
1571
|
+
return await parseReqData(reqFile, null, fetchDepsInfo, installedMetadata);
|
|
1440
1572
|
}
|
|
1441
1573
|
|
|
1442
1574
|
const LICENSE_ID_COMMENTS_PATTERN =
|
|
@@ -1465,9 +1597,17 @@ function parseLicenseComment(comment) {
|
|
|
1465
1597
|
* @param {String} reqFile Requirements.txt file
|
|
1466
1598
|
* @param {Object} reqData Requirements.txt data for internal invocations from setup.py file etc.
|
|
1467
1599
|
* @param {Boolean} fetchDepsInfo Fetch dependencies info from pypi
|
|
1600
|
+
* @param {Object[]} [installedMetadata] Components parsed from the installed
|
|
1601
|
+
* `*.dist-info/METADATA` files of this machine, which answer before PyPI
|
|
1602
|
+
* is asked
|
|
1468
1603
|
* @returns {Promise<Array<Object>>} List of direct dependencies from the requirements file
|
|
1469
1604
|
*/
|
|
1470
|
-
async function parseReqData(
|
|
1605
|
+
async function parseReqData(
|
|
1606
|
+
reqFile,
|
|
1607
|
+
reqData = null,
|
|
1608
|
+
fetchDepsInfo = false,
|
|
1609
|
+
installedMetadata = undefined,
|
|
1610
|
+
) {
|
|
1471
1611
|
const pkgList = [];
|
|
1472
1612
|
let compScope;
|
|
1473
1613
|
if (!reqFile && !reqData) {
|
|
@@ -1513,10 +1653,13 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
1513
1653
|
continue;
|
|
1514
1654
|
}
|
|
1515
1655
|
let comment = null;
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1656
|
+
// Equivalent to /\s+#(.*)$/ but linear: an unanchored `\s+` backtracks
|
|
1657
|
+
// quadratically on long whitespace runs without a '#', which a crafted
|
|
1658
|
+
// requirements.txt used to burn seconds of CPU per line.
|
|
1659
|
+
const hashIndex = l.indexOf("#");
|
|
1660
|
+
if (hashIndex > 0 && /\s/.test(l[hashIndex - 1])) {
|
|
1661
|
+
comment = l.substring(hashIndex + 1).trim();
|
|
1662
|
+
l = l.substring(0, hashIndex).trim();
|
|
1520
1663
|
}
|
|
1521
1664
|
const properties = reqFile
|
|
1522
1665
|
? [
|
|
@@ -1607,9 +1750,7 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
1607
1750
|
if (PYTHON_STD_MODULES.includes(name)) {
|
|
1608
1751
|
continue;
|
|
1609
1752
|
}
|
|
1610
|
-
const versionMatch = versionSpecifiers.match(
|
|
1611
|
-
/(==|!=|<=|>=|<|>|~=)([0-9.a-zA-Z_*\-+]*)/,
|
|
1612
|
-
);
|
|
1753
|
+
const versionMatch = versionSpecifiers.match(PEP440_VERSION_CLAUSE);
|
|
1613
1754
|
let version = null;
|
|
1614
1755
|
if (versionMatch) {
|
|
1615
1756
|
version = versionMatch[2].replaceAll("*", "0") || null;
|
|
@@ -1636,7 +1777,7 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
1636
1777
|
value: extras.join(","),
|
|
1637
1778
|
});
|
|
1638
1779
|
}
|
|
1639
|
-
if (versionSpecifiers && !versionSpecifiers.startsWith("==")) {
|
|
1780
|
+
if (versionSpecifiers && !versionSpecifiers.trim().startsWith("==")) {
|
|
1640
1781
|
properties.push({
|
|
1641
1782
|
name: "cdx:pypi:versionSpecifiers",
|
|
1642
1783
|
value: versionSpecifiers.trim(),
|
|
@@ -1673,9 +1814,7 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
1673
1814
|
if (PYTHON_STD_MODULES.includes(name)) {
|
|
1674
1815
|
continue;
|
|
1675
1816
|
}
|
|
1676
|
-
const versionMatch = versionSpecifiers.match(
|
|
1677
|
-
/(==|!=|<=|>=|<|>|~=)([0-9.a-zA-Z_*\-+]*)/,
|
|
1678
|
-
);
|
|
1817
|
+
const versionMatch = versionSpecifiers.match(PEP440_VERSION_CLAUSE);
|
|
1679
1818
|
let version = null;
|
|
1680
1819
|
if (versionMatch) {
|
|
1681
1820
|
version = versionMatch[2].replaceAll("*", "0") || null;
|
|
@@ -1691,7 +1830,7 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
1691
1830
|
if (licenses) {
|
|
1692
1831
|
apkg.licenses = licenses;
|
|
1693
1832
|
}
|
|
1694
|
-
if (versionSpecifiers && !versionSpecifiers.startsWith("==")) {
|
|
1833
|
+
if (versionSpecifiers && !versionSpecifiers.trim().startsWith("==")) {
|
|
1695
1834
|
properties.push({
|
|
1696
1835
|
name: "cdx:pypi:versionSpecifiers",
|
|
1697
1836
|
value: versionSpecifiers.trim(),
|
|
@@ -1721,6 +1860,9 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
1721
1860
|
pkgList.push(apkg);
|
|
1722
1861
|
}
|
|
1723
1862
|
}
|
|
1863
|
+
// The installed distributions answer first, so PyPI is asked only about
|
|
1864
|
+
// the packages they do not cover.
|
|
1865
|
+
enrichFromInstalledMetadata(pkgList, installedMetadata);
|
|
1724
1866
|
return await getPyMetadata(pkgList, fetchDepsInfo);
|
|
1725
1867
|
}
|
|
1726
1868
|
|
|
@@ -2031,7 +2173,7 @@ export function parsePixiTomlFile(pixiToml) {
|
|
|
2031
2173
|
const pixiTomlFile = readFileSync(pixiToml, { encoding: "utf-8" });
|
|
2032
2174
|
let tomlData;
|
|
2033
2175
|
try {
|
|
2034
|
-
tomlData =
|
|
2176
|
+
tomlData = parseToml(pixiTomlFile);
|
|
2035
2177
|
} catch (err) {
|
|
2036
2178
|
console.log(`Error while parsing the pixi file ${pixiToml}.`, err);
|
|
2037
2179
|
return {};
|
|
@@ -2107,7 +2249,7 @@ export function generatePixiLockFile(_path) {
|
|
|
2107
2249
|
export function parseMojoProject(mojoProjectFile) {
|
|
2108
2250
|
let manifest;
|
|
2109
2251
|
try {
|
|
2110
|
-
manifest =
|
|
2252
|
+
manifest = parseToml(readFileSync(mojoProjectFile, "utf-8"));
|
|
2111
2253
|
} catch (error) {
|
|
2112
2254
|
console.warn(`Failed to parse ${mojoProjectFile}: ${error.message}`);
|
|
2113
2255
|
return { pkgList: [], parentComponent: {} };
|
|
@@ -2201,6 +2343,7 @@ function normaliseMojoVersion(spec) {
|
|
|
2201
2343
|
const trimmed = spec.trim();
|
|
2202
2344
|
if (!trimmed) return undefined;
|
|
2203
2345
|
// Strip common comparison operators and take the first concrete version.
|
|
2204
|
-
|
|
2346
|
+
// A PEP 440 epoch (`1!2.0`) is part of the version, not a separator.
|
|
2347
|
+
const match = trimmed.match(/((?:\d+!)?\d+(?:\.\d+)*(?:[+-][A-Za-z0-9.]+)?)/);
|
|
2205
2348
|
return match ? match[1] : undefined;
|
|
2206
2349
|
}
|
|
@@ -3,12 +3,12 @@ import { sep as _sep, basename, dirname, join, resolve } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { build } from "@cdxgen/cdx-purl";
|
|
5
5
|
import { globSync } from "glob";
|
|
6
|
-
import * as toml from "smol-toml";
|
|
7
6
|
|
|
8
7
|
import { DEBUG_MODE } from "../core/activity.js";
|
|
9
8
|
import { shouldFetchLicense } from "../core/env.js";
|
|
10
9
|
import { safeExistsSync } from "../core/fs.js";
|
|
11
10
|
import { traceLog } from "../core/logger.js";
|
|
11
|
+
import { parseToml } from "../core/toml.js";
|
|
12
12
|
import { getCratesMetadata, normalizeCargoIntegrity } from "./ecosystems.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -204,7 +204,7 @@ function readCargoTomlData(cargoTomlFile) {
|
|
|
204
204
|
return undefined;
|
|
205
205
|
}
|
|
206
206
|
try {
|
|
207
|
-
return
|
|
207
|
+
return parseToml(readFileSync(cargoTomlFile, { encoding: "utf-8" }));
|
|
208
208
|
} catch (error) {
|
|
209
209
|
traceLog("cargo", {
|
|
210
210
|
cargoTomlFile,
|
|
@@ -1082,7 +1082,7 @@ export async function parseCargoData(
|
|
|
1082
1082
|
return pkgList;
|
|
1083
1083
|
}
|
|
1084
1084
|
|
|
1085
|
-
const cargoData =
|
|
1085
|
+
const cargoData = parseToml(
|
|
1086
1086
|
readFileSync(cargoLockFile, { encoding: "utf-8" }),
|
|
1087
1087
|
);
|
|
1088
1088
|
if (!cargoData) {
|
|
@@ -1290,7 +1290,7 @@ export function parseCargoManifestDependencyData(cargoTomlFile, context = {}) {
|
|
|
1290
1290
|
*/
|
|
1291
1291
|
export function parseCargoDependencyData(cargoLockData) {
|
|
1292
1292
|
const purlFromPackageInfo = (pkg) => decodeURIComponent(buildCargoPurl(pkg));
|
|
1293
|
-
const cargoData =
|
|
1293
|
+
const cargoData = parseToml(cargoLockData);
|
|
1294
1294
|
const packageNode = cargoData?.package;
|
|
1295
1295
|
if (!packageNode || !Array.isArray(packageNode)) {
|
|
1296
1296
|
return [];
|
|
@@ -1309,15 +1309,20 @@ export function parseCargoDependencyData(cargoLockData) {
|
|
|
1309
1309
|
);
|
|
1310
1310
|
}
|
|
1311
1311
|
});
|
|
1312
|
-
//
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1312
|
+
// A lock file can hold several versions of one crate. Cargo then names the
|
|
1313
|
+
// version in every reference to it (`getrandom 0.3.4`), and a bare name is
|
|
1314
|
+
// left only for crates with a single version.
|
|
1315
|
+
const pkgsByName = new Map();
|
|
1316
|
+
for (const pkg of pkgList) {
|
|
1317
|
+
pkgsByName.set(pkg.name, [...(pkgsByName.get(pkg.name) || []), pkg]);
|
|
1318
|
+
}
|
|
1319
|
+
// Every package gets its own entry, so each version keeps its edges. The
|
|
1320
|
+
// same name and version from two sources share a purl; their edges merge.
|
|
1321
|
+
const edgesByRef = new Map();
|
|
1322
|
+
for (const pkg of pkgList) {
|
|
1323
|
+
const ref = purlFromPackageInfo(pkg);
|
|
1324
|
+
const dependsOn = edgesByRef.get(ref) || new Set();
|
|
1325
|
+
edgesByRef.set(ref, dependsOn);
|
|
1321
1326
|
pkg.dependencies?.forEach((dep) => {
|
|
1322
1327
|
if (dep.indexOf(" ") !== -1) {
|
|
1323
1328
|
// fill version in dependency definition like `libc 0.2.79`
|
|
@@ -1328,20 +1333,21 @@ export function parseCargoDependencyData(cargoLockData) {
|
|
|
1328
1333
|
version: depSplit[1].trim(),
|
|
1329
1334
|
}),
|
|
1330
1335
|
);
|
|
1331
|
-
} else if (
|
|
1332
|
-
dependsOn.add(purlFromPackageInfo(
|
|
1336
|
+
} else if (pkgsByName.get(dep)?.length === 1) {
|
|
1337
|
+
dependsOn.add(purlFromPackageInfo(pkgsByName.get(dep)[0]));
|
|
1333
1338
|
} else if (DEBUG_MODE) {
|
|
1334
1339
|
console.warn(
|
|
1335
|
-
|
|
1340
|
+
pkgsByName.has(dep)
|
|
1341
|
+
? `The package "${dep}" appears as a dependency to "${pkg.name}" without a version, but the Cargo.lock file lists more than one version of it. The produced SBOM will not list ${dep} as a dependency.`
|
|
1342
|
+
: `The package "${dep}" appears as a dependency to "${pkg.name}" but is not itself listed in the Cargo.lock file. The Cargo.lock file is invalid! The produced SBOM will not list ${dep} as a dependency.`,
|
|
1336
1343
|
);
|
|
1337
1344
|
}
|
|
1338
1345
|
});
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
});
|
|
1344
|
-
return result;
|
|
1346
|
+
}
|
|
1347
|
+
return [...edgesByRef].map(([ref, dependsOn]) => ({
|
|
1348
|
+
ref,
|
|
1349
|
+
dependsOn: [...dependsOn],
|
|
1350
|
+
}));
|
|
1345
1351
|
}
|
|
1346
1352
|
|
|
1347
1353
|
/**
|