@cyclonedx/cdxgen 9.8.5 → 9.8.7
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 +25 -25
- package/bin/cdxgen.js +9 -9
- package/bin/evinse.js +4 -4
- package/bin/repl.js +11 -11
- package/bin/verify.js +1 -1
- package/evinser.js +3 -3
- package/index.js +38 -22
- package/package.json +12 -10
- package/server.js +15 -4
- package/utils.js +206 -111
- package/utils.test.js +38 -4
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
cdxgen is a cli tool, library, [REPL](./ADVANCED.md), and server to create a valid and compliant [CycloneDX][cyclonedx-homepage] Software Bill
|
|
5
|
+
cdxgen is a cli tool, library, [REPL](./ADVANCED.md), and server to create a valid and compliant [CycloneDX][cyclonedx-homepage] Software Bill of Materials (SBOM) containing an aggregate of all project dependencies for c/c++, node.js, php, python, ruby, rust, java, .Net, dart, haskell, elixir, and Go projects in JSON format. CycloneDX 1.5 is a lightweight SBOM specification that is easily created, human and machine-readable, and simple to parse.
|
|
6
6
|
|
|
7
|
-
When used with plugins, cdxgen could generate an OBoM for Linux docker images and even VMs running Linux or Windows operating systems. cdxgen also includes an evinse tool to generate component evidence and
|
|
7
|
+
When used with plugins, cdxgen could generate an OBoM for Linux docker images and even VMs running Linux or Windows operating systems. cdxgen also includes an evinse tool to generate component evidence and SaaSBOM for some languages.
|
|
8
8
|
|
|
9
9
|
NOTE:
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ CycloneDX 1.5 specification is new and unsupported by many downstream tools. Use
|
|
|
12
12
|
|
|
13
13
|
## Why cdxgen?
|
|
14
14
|
|
|
15
|
-
A typical application might have several repos, components, and libraries. Traditional techniques to generate a single
|
|
15
|
+
A typical application might have several repos, components, and libraries. Traditional techniques to generate a single SBOM per language or package manifest do not work in enterprise environments. So we built cdxgen - the universal polyglot SBOM generator!
|
|
16
16
|
|
|
17
17
|
<img src="./docs/why-cdxgen.jpg" alt="why cdxgen" width="256">
|
|
18
18
|
|
|
@@ -27,7 +27,7 @@ A typical application might have several repos, components, and libraries. Tradi
|
|
|
27
27
|
| go | binary, go.mod, go.sum, Gopkg.lock | Yes except binary |
|
|
28
28
|
| ruby | Gemfile.lock, gemspec | Only for Gemfile.lock |
|
|
29
29
|
| rust | binary, Cargo.toml, Cargo.lock | Only for Cargo.lock |
|
|
30
|
-
| .Net | .csproj, packages.config, project.assets.json [3], packages.lock.json, .nupkg
|
|
30
|
+
| .Net | .csproj, packages.config, project.assets.json [3], packages.lock.json, .nupkg, paket.lock | Only for project.assets.json, packages.lock.json, paket.lock |
|
|
31
31
|
| dart | pubspec.lock, pubspec.yaml | Only for pubspec.lock |
|
|
32
32
|
| haskell | cabal.project.freeze | Yes |
|
|
33
33
|
| elixir | mix.lock | Yes |
|
|
@@ -71,11 +71,11 @@ Footnotes:
|
|
|
71
71
|
|
|
72
72
|
### Automatic usage detection
|
|
73
73
|
|
|
74
|
-
For node.js projects, lock files are parsed initially, so the
|
|
74
|
+
For node.js projects, lock files are parsed initially, so the SBOM would include all dependencies, including dev ones. An AST parser powered by babel-parser is then used to detect packages that are imported and used by non-test code. Such imported packages would automatically set their scope property to `required` in the resulting SBOM. You can turn off this analysis by passing the argument `--no-babel`. Scope property would then be set based on the `dev` attribute in the lock file.
|
|
75
75
|
|
|
76
76
|
This attribute can be later used for various purposes. For example, [dep-scan](https://github.com/cyclonedx/dep-scan) uses this attribute to prioritize vulnerabilities. Unfortunately, tools such as dependency track, do not include this feature and might over-report the CVEs.
|
|
77
77
|
|
|
78
|
-
By passing the argument `--required-only`, you can limit the
|
|
78
|
+
By passing the argument `--required-only`, you can limit the SBOM only to include packages with the scope "required", commonly called production or non-dev dependencies. Combine with `--no-babel` to limit this list to only non-dev dependencies based on the `dev` attribute being false in the lock files.
|
|
79
79
|
|
|
80
80
|
For go, `go mod why` command is used to identify required packages. For php, composer lock file is parsed to distinguish required (packages) from optional (packages-dev).
|
|
81
81
|
|
|
@@ -132,7 +132,7 @@ $ cdxgen -h
|
|
|
132
132
|
-r, --recurse Recurse mode suitable for mono-repos. Defaults to
|
|
133
133
|
true. Pass --no-recurse to disable.
|
|
134
134
|
[boolean] [default: true]
|
|
135
|
-
-p, --print Print the
|
|
135
|
+
-p, --print Print the SBOM as a table with tree. [boolean]
|
|
136
136
|
-c, --resolve-class Resolve class names for packages. jars only for n
|
|
137
137
|
ow. [boolean]
|
|
138
138
|
--deep Perform deep searches for components. Useful whil
|
|
@@ -149,12 +149,12 @@ $ cdxgen -h
|
|
|
149
149
|
d or the project name and version together
|
|
150
150
|
--parent-project-id Dependency track parent project id
|
|
151
151
|
--required-only Include only the packages with required scope on
|
|
152
|
-
the
|
|
152
|
+
the SBOM. [boolean]
|
|
153
153
|
--fail-on-error Fail if any dependency extractor fails. [boolean]
|
|
154
154
|
--no-babel Do not use babel to perform usage analysis for Ja
|
|
155
155
|
vaScript/TypeScript projects. [boolean]
|
|
156
156
|
--generate-key-and-sign Generate an RSA public/private key pair and then
|
|
157
|
-
sign the generated
|
|
157
|
+
sign the generated SBOM using JSON Web Signatures
|
|
158
158
|
. [boolean]
|
|
159
159
|
--server Run cdxgen as a server [boolean]
|
|
160
160
|
--server-host Listen address [default: "127.0.0.1"]
|
|
@@ -163,7 +163,7 @@ $ cdxgen -h
|
|
|
163
163
|
cts. Defaults to true but disabled for containers
|
|
164
164
|
and oci scans. Use --no-install-deps to disable
|
|
165
165
|
this feature. [boolean] [default: true]
|
|
166
|
-
--validate Validate the generated
|
|
166
|
+
--validate Validate the generated SBOM using json schema. De
|
|
167
167
|
faults to true. Pass --no-validate to disable.
|
|
168
168
|
[boolean] [default: true]
|
|
169
169
|
--usages-slices-file Path for the usages slice file created by atom.
|
|
@@ -191,7 +191,7 @@ For a java project. cdxgen would automatically detect maven, gradle, or sbt and
|
|
|
191
191
|
cdxgen -t java -o bom.json
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
To print the
|
|
194
|
+
To print the SBOM as a table pass `-p` argument.
|
|
195
195
|
|
|
196
196
|
```shell
|
|
197
197
|
cdxgen -t java -o bom.json -p
|
|
@@ -203,13 +203,13 @@ To recursively generate a single BoM for all languages pass `-r` argument.
|
|
|
203
203
|
cdxgen -r -o bom.json
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
-
To generate
|
|
206
|
+
To generate SBOM for an older specification version, such as 1.4, pass the version number using the `--spec-version` argument.
|
|
207
207
|
|
|
208
208
|
```shell
|
|
209
209
|
cdxgen -r -o bom.json --spec-version 1.4
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
-
To generate
|
|
212
|
+
To generate SBOM for C or Python, ensure Java >= 17 is installed.
|
|
213
213
|
|
|
214
214
|
```shell
|
|
215
215
|
# Install java >= 17
|
|
@@ -218,11 +218,11 @@ cdxgen -t c -o bom.json
|
|
|
218
218
|
|
|
219
219
|
NOTE: cdxgen is known to freeze with Java 8 or 11, so ensure >= 17 is installed and JAVA_HOME environment variable is configured correctly. If in doubt, use the cdxgen container image.
|
|
220
220
|
|
|
221
|
-
## Universal
|
|
221
|
+
## Universal SBOM
|
|
222
222
|
|
|
223
|
-
By passing the type argument `-t universal`, cdxgen could be forced to opportunistically collect as many components and services as possible by scanning all package, container, and Kubernetes manifests. The resulting
|
|
223
|
+
By passing the type argument `-t universal`, cdxgen could be forced to opportunistically collect as many components and services as possible by scanning all package, container, and Kubernetes manifests. The resulting SBOM could have over a thousand components, thus requiring additional triaging before use with traditional SCA tools.
|
|
224
224
|
|
|
225
|
-
##
|
|
225
|
+
## SBOM server
|
|
226
226
|
|
|
227
227
|
Invoke cdxgen with `--server` argument to run it in server mode. By default, it listens to port `9090`, which can be customized with the arguments `--server-host` and `--server-port`.
|
|
228
228
|
|
|
@@ -246,7 +246,7 @@ Arguments can be passed either via the query string or as a JSON body. The follo
|
|
|
246
246
|
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
247
247
|
| type | Project type |
|
|
248
248
|
| multiProject | [boolean] |
|
|
249
|
-
| requiredOnly | Include only the packages with required scope on the
|
|
249
|
+
| requiredOnly | Include only the packages with required scope on the SBOM. [boolean] |
|
|
250
250
|
| noBabel | Do not use babel to perform usage analysis for JavaScript/TypeScript projects. [boolean] |
|
|
251
251
|
| installDeps | Install dependencies automatically for some projects. Defaults to true but disabled for containers and oci scans. [boolean] [default: true] |
|
|
252
252
|
| project | |
|
|
@@ -349,7 +349,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
|
|
|
349
349
|
| LEIN_CMD | Set to override the leiningen command |
|
|
350
350
|
| SBOM_SIGN_ALGORITHM | Signature algorithm. Some valid values are RS256, RS384, RS512, PS256, PS384, PS512, ES256 etc |
|
|
351
351
|
| SBOM_SIGN_PRIVATE_KEY | Private key to use for signing |
|
|
352
|
-
| SBOM_SIGN_PUBLIC_KEY | Optional. Public key to include in the
|
|
352
|
+
| SBOM_SIGN_PUBLIC_KEY | Optional. Public key to include in the SBOM signature |
|
|
353
353
|
| CDX_MAVEN_PLUGIN | CycloneDX Maven plugin to use. Default "org.cyclonedx:cyclonedx-maven-plugin:2.7.8" |
|
|
354
354
|
| CDX_MAVEN_GOAL | CycloneDX Maven plugin goal to use. Default makeAggregateBom. Other options: makeBom, makePackageBom |
|
|
355
355
|
| CDX_MAVEN_INCLUDE_TEST_SCOPE | Whether test scoped dependencies should be included from Maven projects, Default: true |
|
|
@@ -358,7 +358,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
|
|
|
358
358
|
|
|
359
359
|
## Plugins
|
|
360
360
|
|
|
361
|
-
cdxgen could be extended with external binary plugins to support more
|
|
361
|
+
cdxgen could be extended with external binary plugins to support more SBOM use cases. These are now installed as an optional dependency.
|
|
362
362
|
|
|
363
363
|
```shell
|
|
364
364
|
sudo npm install -g @cyclonedx/cdxgen-plugins-bin
|
|
@@ -409,9 +409,9 @@ obom
|
|
|
409
409
|
# cdxgen -t os
|
|
410
410
|
```
|
|
411
411
|
|
|
412
|
-
This feature is powered by osquery, which is [installed](https://github.com/cyclonedx/cdxgen-plugins-bin/blob/main/build.sh#L8) along with the binary plugins. cdxgen would opportunistically try to detect as many components, apps, and extensions as possible using the [default queries](queries.json). The process would take several minutes and result in an
|
|
412
|
+
This feature is powered by osquery, which is [installed](https://github.com/cyclonedx/cdxgen-plugins-bin/blob/main/build.sh#L8) along with the binary plugins. cdxgen would opportunistically try to detect as many components, apps, and extensions as possible using the [default queries](queries.json). The process would take several minutes and result in an SBOM file with thousands of components of various types, such as operating-system, device-drivers, files, and data.
|
|
413
413
|
|
|
414
|
-
## Generating
|
|
414
|
+
## Generating SaaSBOM and component evidences
|
|
415
415
|
|
|
416
416
|
See [evinse mode](./ADVANCED.md) in the advanced documentation.
|
|
417
417
|
|
|
@@ -425,7 +425,7 @@ cdxgen can sign the generated BoM json file to increase authenticity and non-rep
|
|
|
425
425
|
|
|
426
426
|
To generate test public/private key pairs, you can run cdxgen by passing the argument `--generate-key-and-sign`. The generated json file would have an attribute called `signature`, which could be used for validation. [jwt.io](https://jwt.io) is a known site that could be used for such signature validation.
|
|
427
427
|
|
|
428
|
-

|
|
429
429
|
|
|
430
430
|
### Verifying the signature
|
|
431
431
|
|
|
@@ -444,7 +444,7 @@ There are many [libraries](https://jwt.io/#libraries-io) available to validate J
|
|
|
444
444
|
# npm install jws
|
|
445
445
|
const jws = require("jws");
|
|
446
446
|
const fs = require("fs");
|
|
447
|
-
// Location of the
|
|
447
|
+
// Location of the SBOM json file
|
|
448
448
|
const bomJsonFile = "bom.json";
|
|
449
449
|
// Location of the public key
|
|
450
450
|
const publicKeyFile = "public.key";
|
|
@@ -455,13 +455,13 @@ const validationResult = jws.verify(bomSignature, bomJson.signature.algorithm, f
|
|
|
455
455
|
if (validationResult) {
|
|
456
456
|
console.log("Signature is valid!");
|
|
457
457
|
} else {
|
|
458
|
-
console.log("
|
|
458
|
+
console.log("SBOM signature is invalid :(");
|
|
459
459
|
}
|
|
460
460
|
```
|
|
461
461
|
|
|
462
462
|
## Automatic services detection
|
|
463
463
|
|
|
464
|
-
cdxgen can automatically detect names of services from YAML manifests such as docker-compose, Kubernetes, or Skaffold manifests. These would be populated under the `services` attribute in the generated
|
|
464
|
+
cdxgen can automatically detect names of services from YAML manifests such as docker-compose, Kubernetes, or Skaffold manifests. These would be populated under the `services` attribute in the generated SBOM. With [evinse](./ADVANCED.md), additional services could be detected by parsing common annotations from the source code.
|
|
465
465
|
|
|
466
466
|
## Conversion to SPDX format
|
|
467
467
|
|
package/bin/cdxgen.js
CHANGED
|
@@ -41,7 +41,7 @@ const args = yargs(hideBin(process.argv))
|
|
|
41
41
|
.option("print", {
|
|
42
42
|
alias: "p",
|
|
43
43
|
type: "boolean",
|
|
44
|
-
description: "Print the
|
|
44
|
+
description: "Print the SBOM as a table with tree."
|
|
45
45
|
})
|
|
46
46
|
.option("resolve-class", {
|
|
47
47
|
alias: "c",
|
|
@@ -78,7 +78,7 @@ const args = yargs(hideBin(process.argv))
|
|
|
78
78
|
})
|
|
79
79
|
.option("required-only", {
|
|
80
80
|
type: "boolean",
|
|
81
|
-
description: "Include only the packages with required scope on the
|
|
81
|
+
description: "Include only the packages with required scope on the SBOM."
|
|
82
82
|
})
|
|
83
83
|
.option("fail-on-error", {
|
|
84
84
|
type: "boolean",
|
|
@@ -92,7 +92,7 @@ const args = yargs(hideBin(process.argv))
|
|
|
92
92
|
.option("generate-key-and-sign", {
|
|
93
93
|
type: "boolean",
|
|
94
94
|
description:
|
|
95
|
-
"Generate an RSA public/private key pair and then sign the generated
|
|
95
|
+
"Generate an RSA public/private key pair and then sign the generated SBOM using JSON Web Signatures."
|
|
96
96
|
})
|
|
97
97
|
.option("server", {
|
|
98
98
|
type: "boolean",
|
|
@@ -116,12 +116,12 @@ const args = yargs(hideBin(process.argv))
|
|
|
116
116
|
type: "boolean",
|
|
117
117
|
default: true,
|
|
118
118
|
description:
|
|
119
|
-
"Validate the generated
|
|
119
|
+
"Validate the generated SBOM using json schema. Defaults to true. Pass --no-validate to disable."
|
|
120
120
|
})
|
|
121
121
|
.option("evidence", {
|
|
122
122
|
type: "boolean",
|
|
123
123
|
default: false,
|
|
124
|
-
description: "Generate
|
|
124
|
+
description: "Generate SBOM with evidence for supported languages. WIP"
|
|
125
125
|
})
|
|
126
126
|
.option("usages-slices-file", {
|
|
127
127
|
description: "Path for the usages slice file created by atom."
|
|
@@ -241,7 +241,7 @@ const checkPermissions = (filePath) => {
|
|
|
241
241
|
* Method to start the bom creation process
|
|
242
242
|
*/
|
|
243
243
|
(async () => {
|
|
244
|
-
// Start
|
|
244
|
+
// Start SBOM server
|
|
245
245
|
if (args.server) {
|
|
246
246
|
return await _serverStart(options);
|
|
247
247
|
}
|
|
@@ -384,12 +384,12 @@ const checkPermissions = (filePath) => {
|
|
|
384
384
|
);
|
|
385
385
|
if (signatureVerification) {
|
|
386
386
|
console.log(
|
|
387
|
-
"
|
|
387
|
+
"SBOM signature is verifiable with the public key and the algorithm",
|
|
388
388
|
publicKeyFile,
|
|
389
389
|
alg
|
|
390
390
|
);
|
|
391
391
|
} else {
|
|
392
|
-
console.log("
|
|
392
|
+
console.log("SBOM signature verification was unsuccessful");
|
|
393
393
|
console.log(
|
|
394
394
|
"Check if the public key was exported in PEM format"
|
|
395
395
|
);
|
|
@@ -397,7 +397,7 @@ const checkPermissions = (filePath) => {
|
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
399
|
} catch (ex) {
|
|
400
|
-
console.log("
|
|
400
|
+
console.log("SBOM signing was unsuccessful", ex);
|
|
401
401
|
console.log("Check if the private key was exported in PEM format");
|
|
402
402
|
}
|
|
403
403
|
}
|
package/bin/evinse.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// Evinse (Evinse Verification Is Nearly
|
|
3
|
+
// Evinse (Evinse Verification Is Nearly SBOM Evidence)
|
|
4
4
|
import yargs from "yargs";
|
|
5
5
|
import { hideBin } from "yargs/helpers";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -30,7 +30,7 @@ if (!process.env.ATOM_DB && !fs.existsSync(ATOM_DB)) {
|
|
|
30
30
|
const args = yargs(hideBin(process.argv))
|
|
31
31
|
.option("input", {
|
|
32
32
|
alias: "i",
|
|
33
|
-
description: "Input
|
|
33
|
+
description: "Input SBOM file. Default bom.json",
|
|
34
34
|
default: "bom.json"
|
|
35
35
|
})
|
|
36
36
|
.option("output", {
|
|
@@ -108,9 +108,9 @@ console.log(evinseArt);
|
|
|
108
108
|
if (dbObjMap) {
|
|
109
109
|
// Analyze the project using atom. Convert package namespaces to purl using the db
|
|
110
110
|
const sliceArtefacts = await analyzeProject(dbObjMap, args);
|
|
111
|
-
// Create the
|
|
111
|
+
// Create the SBOM with Evidence
|
|
112
112
|
const bomJson = createEvinseFile(sliceArtefacts, args);
|
|
113
|
-
// Validate our final
|
|
113
|
+
// Validate our final SBOM
|
|
114
114
|
if (!validateBom(bomJson)) {
|
|
115
115
|
process.exit(1);
|
|
116
116
|
}
|
package/bin/repl.js
CHANGED
|
@@ -61,10 +61,10 @@ export const importSbom = (sbomOrPath) => {
|
|
|
61
61
|
if (sbomOrPath && sbomOrPath.endsWith(".json") && fs.existsSync(sbomOrPath)) {
|
|
62
62
|
try {
|
|
63
63
|
sbom = JSON.parse(fs.readFileSync(sbomOrPath, "utf-8"));
|
|
64
|
-
console.log(`✅
|
|
64
|
+
console.log(`✅ SBOM imported successfully from ${sbomOrPath}`);
|
|
65
65
|
} catch (e) {
|
|
66
66
|
console.log(
|
|
67
|
-
`⚠ Unable to import the
|
|
67
|
+
`⚠ Unable to import the SBOM from ${sbomOrPath} due to ${e}`
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
} else {
|
|
@@ -74,13 +74,13 @@ export const importSbom = (sbomOrPath) => {
|
|
|
74
74
|
// Load any sbom passed from the command line
|
|
75
75
|
if (process.argv.length > 2) {
|
|
76
76
|
importSbom(process.argv[process.argv.length - 1]);
|
|
77
|
-
console.log("💭 Type .print to view the
|
|
77
|
+
console.log("💭 Type .print to view the SBOM as a table");
|
|
78
78
|
} else if (fs.existsSync("bom.json")) {
|
|
79
79
|
// If the current directory has a bom.json load it
|
|
80
80
|
importSbom("bom.json");
|
|
81
81
|
} else {
|
|
82
|
-
console.log("💭 Use .create <path> to create an
|
|
83
|
-
console.log("💭 Use .import <json> to import an existing
|
|
82
|
+
console.log("💭 Use .create <path> to create an SBOM for the given path.");
|
|
83
|
+
console.log("💭 Use .import <json> to import an existing SBOM.");
|
|
84
84
|
console.log("💭 Type .exit or press ctrl+d to close.");
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -98,7 +98,7 @@ if (historyFile) {
|
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
100
|
cdxgenRepl.defineCommand("create", {
|
|
101
|
-
help: "create an
|
|
101
|
+
help: "create an SBOM for the given path",
|
|
102
102
|
async action(sbomOrPath) {
|
|
103
103
|
this.clearBufferedCommand();
|
|
104
104
|
const tempDir = fs.mkdtempSync(join(tmpdir(), "cdxgen-repl-"));
|
|
@@ -267,7 +267,7 @@ cdxgenRepl.defineCommand("validate", {
|
|
|
267
267
|
if (sbom) {
|
|
268
268
|
const result = validateBom(sbom);
|
|
269
269
|
if (result) {
|
|
270
|
-
console.log("
|
|
270
|
+
console.log("SBOM is valid!");
|
|
271
271
|
}
|
|
272
272
|
} else {
|
|
273
273
|
console.log(
|
|
@@ -379,7 +379,7 @@ cdxgenRepl.defineCommand("callstack", {
|
|
|
379
379
|
let components = await expression.evaluate(sbom);
|
|
380
380
|
if (!components) {
|
|
381
381
|
console.log(
|
|
382
|
-
"callstack evidence was not found. Use evinse command to generate an
|
|
382
|
+
"callstack evidence was not found. Use evinse command to generate an SBOM with evidence."
|
|
383
383
|
);
|
|
384
384
|
} else {
|
|
385
385
|
if (!Array.isArray(components)) {
|
|
@@ -392,7 +392,7 @@ cdxgenRepl.defineCommand("callstack", {
|
|
|
392
392
|
}
|
|
393
393
|
} else {
|
|
394
394
|
console.log(
|
|
395
|
-
"⚠ No
|
|
395
|
+
"⚠ No SBOM is loaded. Use .import command to import an evinse SBOM"
|
|
396
396
|
);
|
|
397
397
|
}
|
|
398
398
|
this.displayPrompt();
|
|
@@ -407,7 +407,7 @@ cdxgenRepl.defineCommand("services", {
|
|
|
407
407
|
let services = await expression.evaluate(sbom);
|
|
408
408
|
if (!services) {
|
|
409
409
|
console.log(
|
|
410
|
-
"No services found. Use evinse command to generate an
|
|
410
|
+
"No services found. Use evinse command to generate an SBOM with evidence."
|
|
411
411
|
);
|
|
412
412
|
} else {
|
|
413
413
|
if (!Array.isArray(services)) {
|
|
@@ -420,7 +420,7 @@ cdxgenRepl.defineCommand("services", {
|
|
|
420
420
|
}
|
|
421
421
|
} else {
|
|
422
422
|
console.log(
|
|
423
|
-
"⚠ No
|
|
423
|
+
"⚠ No SBOM is loaded. Use .import command to import an evinse SBOM"
|
|
424
424
|
);
|
|
425
425
|
}
|
|
426
426
|
this.displayPrompt();
|
package/bin/verify.js
CHANGED
package/evinser.js
CHANGED
|
@@ -31,7 +31,7 @@ export const prepareDB = async (options) => {
|
|
|
31
31
|
const bomJson = JSON.parse(fs.readFileSync(bomJsonFile, "utf8"));
|
|
32
32
|
if (bomJson.specVersion < 1.5) {
|
|
33
33
|
console.log(
|
|
34
|
-
"Evinse requires the input
|
|
34
|
+
"Evinse requires the input SBOM in CycloneDX 1.5 format or above. You can generate one by invoking cdxgen without any --spec-version argument."
|
|
35
35
|
);
|
|
36
36
|
process.exit(0);
|
|
37
37
|
}
|
|
@@ -741,7 +741,7 @@ export const isSlicingRequired = (purl) => {
|
|
|
741
741
|
};
|
|
742
742
|
|
|
743
743
|
/**
|
|
744
|
-
* Method to create the
|
|
744
|
+
* Method to create the SBOM with evidence file called evinse file.
|
|
745
745
|
*
|
|
746
746
|
* @param {object} sliceArtefacts Various artefacts from the slice operation
|
|
747
747
|
* @param {object} options Command line options
|
|
@@ -841,7 +841,7 @@ export const createEvinseFile = (sliceArtefacts, options) => {
|
|
|
841
841
|
console.log(evinseOutFile, "created successfully.");
|
|
842
842
|
} else {
|
|
843
843
|
console.log(
|
|
844
|
-
"Unable to identify component evidence for the input
|
|
844
|
+
"Unable to identify component evidence for the input SBOM. Only java, javascript and python projects are supported by evinse."
|
|
845
845
|
);
|
|
846
846
|
}
|
|
847
847
|
if (tempDir && tempDir.startsWith(tmpdir())) {
|
package/index.js
CHANGED
|
@@ -92,6 +92,7 @@ import {
|
|
|
92
92
|
parseCsPkgLockData,
|
|
93
93
|
parseCsPkgData,
|
|
94
94
|
parseCsProjData,
|
|
95
|
+
parsePaketLockData,
|
|
95
96
|
DEBUG_MODE,
|
|
96
97
|
parsePyProjectToml,
|
|
97
98
|
addEvidenceForImports,
|
|
@@ -1127,17 +1128,11 @@ export const createJavaBom = async (path, options) => {
|
|
|
1127
1128
|
console.log(`Retrieving packages from ${path}`);
|
|
1128
1129
|
}
|
|
1129
1130
|
const tempDir = mkdtempSync(join(tmpdir(), "war-deps-"));
|
|
1130
|
-
|
|
1131
|
+
jarNSMapping = collectJarNS(tempDir);
|
|
1132
|
+
pkgList = extractJarArchive(path, tempDir, jarNSMapping);
|
|
1131
1133
|
if (pkgList.length) {
|
|
1132
1134
|
pkgList = await getMvnMetadata(pkgList);
|
|
1133
1135
|
}
|
|
1134
|
-
// Should we attempt to resolve class names
|
|
1135
|
-
if (options.resolveClass) {
|
|
1136
|
-
console.log(
|
|
1137
|
-
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1138
|
-
);
|
|
1139
|
-
jarNSMapping = collectJarNS(tempDir);
|
|
1140
|
-
}
|
|
1141
1136
|
// Clean up
|
|
1142
1137
|
if (tempDir && tempDir.startsWith(tmpdir()) && rmSync) {
|
|
1143
1138
|
console.log(`Cleaning up ${tempDir}`);
|
|
@@ -1192,7 +1187,7 @@ export const createJavaBom = async (path, options) => {
|
|
|
1192
1187
|
}
|
|
1193
1188
|
const mavenCmd = getMavenCommand(basePath, path);
|
|
1194
1189
|
// Should we attempt to resolve class names
|
|
1195
|
-
if (options.resolveClass) {
|
|
1190
|
+
if (options.resolveClass || options.deep) {
|
|
1196
1191
|
console.log(
|
|
1197
1192
|
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1198
1193
|
);
|
|
@@ -1351,7 +1346,7 @@ export const createJavaBom = async (path, options) => {
|
|
|
1351
1346
|
}
|
|
1352
1347
|
if (pkgList) {
|
|
1353
1348
|
pkgList = trimComponents(pkgList, "json");
|
|
1354
|
-
pkgList = await getMvnMetadata(pkgList);
|
|
1349
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping);
|
|
1355
1350
|
return buildBomNSData(options, pkgList, "maven", {
|
|
1356
1351
|
src: path,
|
|
1357
1352
|
filename: pomFiles.join(", "),
|
|
@@ -1551,15 +1546,14 @@ export const createJavaBom = async (path, options) => {
|
|
|
1551
1546
|
);
|
|
1552
1547
|
options.failOnError && process.exit(1);
|
|
1553
1548
|
}
|
|
1554
|
-
|
|
1555
|
-
pkgList = await getMvnMetadata(pkgList);
|
|
1556
1549
|
// Should we attempt to resolve class names
|
|
1557
|
-
if (options.resolveClass) {
|
|
1550
|
+
if (options.resolveClass || options.deep) {
|
|
1558
1551
|
console.log(
|
|
1559
1552
|
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1560
1553
|
);
|
|
1561
1554
|
jarNSMapping = collectJarNS(GRADLE_CACHE_DIR);
|
|
1562
1555
|
}
|
|
1556
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping);
|
|
1563
1557
|
return buildBomNSData(options, pkgList, "maven", {
|
|
1564
1558
|
src: path,
|
|
1565
1559
|
filename: gradleFiles.join(", "),
|
|
@@ -1654,7 +1648,8 @@ export const createJavaBom = async (path, options) => {
|
|
|
1654
1648
|
console.log("Bazel unexpectedly didn't produce any output");
|
|
1655
1649
|
options.failOnError && process.exit(1);
|
|
1656
1650
|
}
|
|
1657
|
-
|
|
1651
|
+
// FIXME: How do we retrieve jarNSMapping for bazel projects?
|
|
1652
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping);
|
|
1658
1653
|
return buildBomNSData(options, pkgList, "maven", {
|
|
1659
1654
|
src: path,
|
|
1660
1655
|
filename: "BUILD",
|
|
@@ -1835,14 +1830,14 @@ export const createJavaBom = async (path, options) => {
|
|
|
1835
1830
|
if (DEBUG_MODE) {
|
|
1836
1831
|
console.log(`Found ${pkgList.length} packages`);
|
|
1837
1832
|
}
|
|
1838
|
-
pkgList = await getMvnMetadata(pkgList);
|
|
1839
1833
|
// Should we attempt to resolve class names
|
|
1840
|
-
if (options.resolveClass) {
|
|
1834
|
+
if (options.resolveClass || options.deep) {
|
|
1841
1835
|
console.log(
|
|
1842
1836
|
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1843
1837
|
);
|
|
1844
1838
|
jarNSMapping = collectJarNS(SBT_CACHE_DIR);
|
|
1845
1839
|
}
|
|
1840
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping);
|
|
1846
1841
|
return buildBomNSData(options, pkgList, "maven", {
|
|
1847
1842
|
src: path,
|
|
1848
1843
|
filename: sbtProjects.join(", "),
|
|
@@ -3583,7 +3578,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
|
|
|
3583
3578
|
// img could have .service, .ociSpec or .image
|
|
3584
3579
|
if (img.ociSpec) {
|
|
3585
3580
|
console.log(
|
|
3586
|
-
`NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this
|
|
3581
|
+
`NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this SBOM.`
|
|
3587
3582
|
);
|
|
3588
3583
|
ociSpecs.push({
|
|
3589
3584
|
group: "",
|
|
@@ -3705,7 +3700,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
|
|
|
3705
3700
|
// Parse privado files
|
|
3706
3701
|
if (privadoFiles.length) {
|
|
3707
3702
|
console.log(
|
|
3708
|
-
"Enriching your
|
|
3703
|
+
"Enriching your SBOM with information from privado.ai scan reports"
|
|
3709
3704
|
);
|
|
3710
3705
|
let rows = [["Classification", "Flow"]];
|
|
3711
3706
|
const config = {
|
|
@@ -3969,6 +3964,10 @@ export const createCsharpBom = async (
|
|
|
3969
3964
|
path,
|
|
3970
3965
|
(options.multiProject ? "**/" : "") + "packages.lock.json"
|
|
3971
3966
|
);
|
|
3967
|
+
const paketLockFiles = getAllFiles(
|
|
3968
|
+
path,
|
|
3969
|
+
(options.multiProject ? "**/" : "") + "paket.lock"
|
|
3970
|
+
);
|
|
3972
3971
|
const nupkgFiles = getAllFiles(
|
|
3973
3972
|
path,
|
|
3974
3973
|
(options.multiProject ? "**/" : "") + "*.nupkg"
|
|
@@ -4052,6 +4051,20 @@ export const createCsharpBom = async (
|
|
|
4052
4051
|
}
|
|
4053
4052
|
}
|
|
4054
4053
|
}
|
|
4054
|
+
if (paketLockFiles.length) {
|
|
4055
|
+
manifestFiles = manifestFiles.concat(paketLockFiles);
|
|
4056
|
+
// paket.lock parsing
|
|
4057
|
+
for (const f of paketLockFiles) {
|
|
4058
|
+
if (DEBUG_MODE) {
|
|
4059
|
+
console.log(`Parsing ${f}`);
|
|
4060
|
+
}
|
|
4061
|
+
pkgData = readFileSync(f, { encoding: "utf-8" });
|
|
4062
|
+
const dlist = await parsePaketLockData(pkgData);
|
|
4063
|
+
if (dlist && dlist.length) {
|
|
4064
|
+
pkgList = pkgList.concat(dlist);
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4055
4068
|
if (!parentComponent) {
|
|
4056
4069
|
parentComponent = createDefaultParentComponent(path, options.type, options);
|
|
4057
4070
|
}
|
|
@@ -4116,13 +4129,16 @@ export const trimComponents = (components, format) => {
|
|
|
4116
4129
|
const filteredComponents = [];
|
|
4117
4130
|
for (const comp of components) {
|
|
4118
4131
|
if (format === "xml" && comp.component) {
|
|
4119
|
-
const key =
|
|
4132
|
+
const key =
|
|
4133
|
+
comp.component.purl ||
|
|
4134
|
+
comp.component["bom-ref"] ||
|
|
4135
|
+
comp.name + comp.version;
|
|
4120
4136
|
if (!keyCache[key]) {
|
|
4121
4137
|
keyCache[key] = true;
|
|
4122
4138
|
filteredComponents.push(comp);
|
|
4123
4139
|
}
|
|
4124
4140
|
} else {
|
|
4125
|
-
const key = comp.purl || comp["bom-ref"];
|
|
4141
|
+
const key = comp.purl || comp["bom-ref"] || comp.name + comp.version;
|
|
4126
4142
|
if (!keyCache[key]) {
|
|
4127
4143
|
keyCache[key] = true;
|
|
4128
4144
|
filteredComponents.push(comp);
|
|
@@ -5293,12 +5309,12 @@ export async function submitBom(args, bomContents) {
|
|
|
5293
5309
|
}).json();
|
|
5294
5310
|
} catch (error) {
|
|
5295
5311
|
console.log(
|
|
5296
|
-
"Unable to submit the
|
|
5312
|
+
"Unable to submit the SBOM to the Dependency-Track server using POST method"
|
|
5297
5313
|
);
|
|
5298
5314
|
console.log(error);
|
|
5299
5315
|
}
|
|
5300
5316
|
} else {
|
|
5301
|
-
console.log("Unable to submit the
|
|
5317
|
+
console.log("Unable to submit the SBOM to the Dependency-Track server");
|
|
5302
5318
|
console.log(error);
|
|
5303
5319
|
}
|
|
5304
5320
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyclonedx/cdxgen",
|
|
3
|
-
"version": "9.8.
|
|
4
|
-
"description": "Creates CycloneDX Software Bill
|
|
3
|
+
"version": "9.8.7",
|
|
4
|
+
"description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
|
|
5
5
|
"homepage": "http://github.com/cyclonedx/cdxgen",
|
|
6
6
|
"author": "Prabhu Subramanian <prabhu@appthreat.com>",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --inject-globals false docker.test.js utils.test.js display.test.js",
|
|
43
43
|
"watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --inject-globals false",
|
|
44
44
|
"lint": "eslint *.js *.test.js bin/*.js",
|
|
45
|
-
"pretty": "prettier --write *.js data/*.json bin/*.js
|
|
45
|
+
"pretty": "prettier --write *.js data/*.json bin/*.js"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|
|
48
48
|
"node": ">=16"
|
|
@@ -55,21 +55,21 @@
|
|
|
55
55
|
"url": "https://github.com/cyclonedx/cdxgen/issues"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@babel/parser": "^7.
|
|
59
|
-
"@babel/traverse": "^7.
|
|
58
|
+
"@babel/parser": "^7.23.0",
|
|
59
|
+
"@babel/traverse": "^7.23.0",
|
|
60
60
|
"@npmcli/arborist": "^7.1.0",
|
|
61
61
|
"ajv": "^8.12.0",
|
|
62
62
|
"ajv-formats": "^2.1.1",
|
|
63
63
|
"cheerio": "^1.0.0-rc.12",
|
|
64
64
|
"edn-data": "^1.0.0",
|
|
65
|
-
"glob": "^10.3.
|
|
65
|
+
"glob": "^10.3.10",
|
|
66
66
|
"global-agent": "^3.0.0",
|
|
67
67
|
"got": "^13.0.0",
|
|
68
68
|
"iconv-lite": "^0.6.3",
|
|
69
69
|
"js-yaml": "^4.1.0",
|
|
70
70
|
"jws": "^4.0.0",
|
|
71
71
|
"node-stream-zip": "^1.15.0",
|
|
72
|
-
"packageurl-js": "
|
|
72
|
+
"packageurl-js": "1.0.2",
|
|
73
73
|
"prettify-xml": "^1.2.0",
|
|
74
74
|
"properties-reader": "^2.3.0",
|
|
75
75
|
"semver": "^7.5.3",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"yargs": "^17.7.2"
|
|
83
83
|
},
|
|
84
84
|
"optionalDependencies": {
|
|
85
|
-
"@appthreat/atom": "
|
|
85
|
+
"@appthreat/atom": "1.2.5",
|
|
86
86
|
"@cyclonedx/cdxgen-plugins-bin": "^1.4.0",
|
|
87
87
|
"@cyclonedx/cdxgen-plugins-bin-arm64": "^1.4.0",
|
|
88
88
|
"@cyclonedx/cdxgen-plugins-bin-ppc64": "^1.4.0",
|
|
@@ -101,8 +101,10 @@
|
|
|
101
101
|
"devDependencies": {
|
|
102
102
|
"caxa": "^3.0.1",
|
|
103
103
|
"docsify-cli": "^4.4.4",
|
|
104
|
-
"eslint": "^8.
|
|
104
|
+
"eslint": "^8.50.0",
|
|
105
|
+
"eslint-config-prettier": "^9.0.0",
|
|
106
|
+
"eslint-plugin-prettier": "^5.0.0",
|
|
105
107
|
"jest": "^29.7.0",
|
|
106
108
|
"prettier": "3.0.3"
|
|
107
109
|
}
|
|
108
|
-
}
|
|
110
|
+
}
|
package/server.js
CHANGED
|
@@ -8,6 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { createBom, submitBom } from "./index.js";
|
|
10
10
|
import compression from "compression";
|
|
11
|
+
import { URL } from "url";
|
|
11
12
|
|
|
12
13
|
// Timeout milliseconds. Default 10 mins
|
|
13
14
|
const TIMEOUT_MS =
|
|
@@ -24,10 +25,14 @@ app.use(
|
|
|
24
25
|
app.use(compression());
|
|
25
26
|
|
|
26
27
|
const gitClone = (repoUrl) => {
|
|
28
|
+
const parsedUrl = new URL(repoUrl);
|
|
29
|
+
|
|
30
|
+
const sanitizedRepoUrl = `${parsedUrl.protocol}//${parsedUrl.host}${parsedUrl.pathname}`;
|
|
31
|
+
|
|
27
32
|
const tempDir = fs.mkdtempSync(
|
|
28
|
-
path.join(os.tmpdir(), path.basename(
|
|
33
|
+
path.join(os.tmpdir(), path.basename(parsedUrl.pathname))
|
|
29
34
|
);
|
|
30
|
-
console.log("Cloning",
|
|
35
|
+
console.log("Cloning", sanitizedRepoUrl, "to", tempDir);
|
|
31
36
|
const result = spawnSync("git", ["clone", repoUrl, "--depth", "1", tempDir], {
|
|
32
37
|
encoding: "utf-8",
|
|
33
38
|
shell: false
|
|
@@ -89,6 +94,12 @@ const start = (options) => {
|
|
|
89
94
|
.createServer(app)
|
|
90
95
|
.listen(options.serverPort, options.serverHost);
|
|
91
96
|
configureServer(cdxgenServer);
|
|
97
|
+
|
|
98
|
+
app.use("/health", async function (req, res) {
|
|
99
|
+
res.setHeader("Content-Type", "application/json");
|
|
100
|
+
res.end(JSON.stringify({ status: "OK" }, null, 2));
|
|
101
|
+
});
|
|
102
|
+
|
|
92
103
|
app.use("/sbom", async function (req, res) {
|
|
93
104
|
const q = url.parse(req.url, true).query;
|
|
94
105
|
let cleanup = false;
|
|
@@ -106,7 +117,7 @@ const start = (options) => {
|
|
|
106
117
|
srcDir = gitClone(filePath);
|
|
107
118
|
cleanup = true;
|
|
108
119
|
}
|
|
109
|
-
console.log("Generating
|
|
120
|
+
console.log("Generating SBOM for", srcDir);
|
|
110
121
|
const bomNSData = (await createBom(srcDir, options)) || {};
|
|
111
122
|
if (bomNSData.bomJson) {
|
|
112
123
|
if (
|
|
@@ -119,7 +130,7 @@ const start = (options) => {
|
|
|
119
130
|
}
|
|
120
131
|
}
|
|
121
132
|
if (options.serverUrl && options.apiKey) {
|
|
122
|
-
console.log("Publishing
|
|
133
|
+
console.log("Publishing SBOM to Dependency Track");
|
|
123
134
|
submitBom(options, bomNSData.bomJson);
|
|
124
135
|
}
|
|
125
136
|
res.end("\n");
|
package/utils.js
CHANGED
|
@@ -1886,7 +1886,7 @@ export const executeGradleProperties = function (dir, rootPath, subProject) {
|
|
|
1886
1886
|
}
|
|
1887
1887
|
if (result.stderr.includes("not get unknown property")) {
|
|
1888
1888
|
console.log(
|
|
1889
|
-
"2. Check if the
|
|
1889
|
+
"2. Check if the SBOM is generated for the correct root project for your application."
|
|
1890
1890
|
);
|
|
1891
1891
|
}
|
|
1892
1892
|
}
|
|
@@ -2104,8 +2104,9 @@ export const guessLicenseId = function (content) {
|
|
|
2104
2104
|
* Method to retrieve metadata for maven packages by querying maven central
|
|
2105
2105
|
*
|
|
2106
2106
|
* @param {Array} pkgList Package list
|
|
2107
|
+
* @param {Object} jarNSMapping Jar Namespace mapping object
|
|
2107
2108
|
*/
|
|
2108
|
-
export const getMvnMetadata = async function (pkgList) {
|
|
2109
|
+
export const getMvnMetadata = async function (pkgList, jarNSMapping = {}) {
|
|
2109
2110
|
const MAVEN_CENTRAL_URL =
|
|
2110
2111
|
process.env.MAVEN_CENTRAL_URL || "https://repo1.maven.org/maven2/";
|
|
2111
2112
|
const ANDROID_MAVEN = "https://maven.google.com/";
|
|
@@ -2117,6 +2118,36 @@ export const getMvnMetadata = async function (pkgList) {
|
|
|
2117
2118
|
console.log(`About to query maven for ${pkgList.length} packages`);
|
|
2118
2119
|
}
|
|
2119
2120
|
for (const p of pkgList) {
|
|
2121
|
+
// Reuse any namespace data from jarNSMapping
|
|
2122
|
+
if (jarNSMapping && p.purl && jarNSMapping[p.purl]) {
|
|
2123
|
+
if (jarNSMapping[p.purl].jarFile) {
|
|
2124
|
+
p.evidence = {
|
|
2125
|
+
identity: {
|
|
2126
|
+
field: "purl",
|
|
2127
|
+
confidence: 0.8,
|
|
2128
|
+
methods: [
|
|
2129
|
+
{
|
|
2130
|
+
technique: "binary-analysis",
|
|
2131
|
+
confidence: 0.8,
|
|
2132
|
+
value: jarNSMapping[p.purl].jarFile
|
|
2133
|
+
}
|
|
2134
|
+
]
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
if (
|
|
2139
|
+
jarNSMapping[p.purl].namespaces &&
|
|
2140
|
+
jarNSMapping[p.purl].namespaces.length
|
|
2141
|
+
) {
|
|
2142
|
+
if (!p.properties) {
|
|
2143
|
+
p.properties = [];
|
|
2144
|
+
}
|
|
2145
|
+
p.properties.push({
|
|
2146
|
+
name: "Namespaces",
|
|
2147
|
+
value: jarNSMapping[p.purl].namespaces.join("\n")
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2120
2151
|
let group = p.group || "";
|
|
2121
2152
|
// If the package already has key metadata skip querying maven
|
|
2122
2153
|
if (group && p.name && p.version && !FETCH_LICENSE) {
|
|
@@ -2577,89 +2608,66 @@ export const parsePoetrylockData = async function (lockData, lockFile) {
|
|
|
2577
2608
|
export async function parseReqFile(reqData, fetchDepsInfo) {
|
|
2578
2609
|
const pkgList = [];
|
|
2579
2610
|
let compScope = undefined;
|
|
2580
|
-
reqData
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
l = l.split(" ")[0];
|
|
2594
|
-
}
|
|
2595
|
-
if (l.indexOf("=") > -1) {
|
|
2596
|
-
let tmpA = l.split(/(==|<=|~=|>=)/);
|
|
2597
|
-
if (tmpA.includes("#")) {
|
|
2598
|
-
tmpA = tmpA.split("#")[0];
|
|
2599
|
-
}
|
|
2600
|
-
let versionStr = tmpA[tmpA.length - 1].trim().replace("*", "0");
|
|
2601
|
-
if (versionStr.indexOf(" ") > -1) {
|
|
2602
|
-
versionStr = versionStr.split(" ")[0];
|
|
2611
|
+
reqData
|
|
2612
|
+
.replace(/\r/g, "")
|
|
2613
|
+
.replace(/ [\\]\n/g, "")
|
|
2614
|
+
.replace(/ {4}/g, " ")
|
|
2615
|
+
.split("\n")
|
|
2616
|
+
.forEach((l) => {
|
|
2617
|
+
l = l.trim();
|
|
2618
|
+
let markers = undefined;
|
|
2619
|
+
if (l.includes(" ; ")) {
|
|
2620
|
+
const tmpA = l.split(" ; ");
|
|
2621
|
+
if (tmpA && tmpA.length === 2) {
|
|
2622
|
+
l = tmpA[0];
|
|
2623
|
+
markers = tmpA[1];
|
|
2603
2624
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2625
|
+
}
|
|
2626
|
+
if (l.startsWith("Skipping line") || l.startsWith("(add")) {
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
if (l.includes("# Basic requirements")) {
|
|
2630
|
+
compScope = "required";
|
|
2631
|
+
} else if (l.includes("added by pip freeze")) {
|
|
2632
|
+
compScope = undefined;
|
|
2633
|
+
}
|
|
2634
|
+
if (!l.startsWith("#") && !l.startsWith("-")) {
|
|
2635
|
+
if (l.includes(" ")) {
|
|
2636
|
+
l = l.split(" ")[0];
|
|
2606
2637
|
}
|
|
2607
|
-
if (
|
|
2608
|
-
|
|
2609
|
-
if (
|
|
2610
|
-
|
|
2611
|
-
name,
|
|
2612
|
-
version: versionStr,
|
|
2613
|
-
scope: compScope
|
|
2614
|
-
});
|
|
2638
|
+
if (l.indexOf("=") > -1) {
|
|
2639
|
+
let tmpA = l.split(/(==|<=|~=|>=)/);
|
|
2640
|
+
if (tmpA.includes("#")) {
|
|
2641
|
+
tmpA = tmpA.split("#")[0];
|
|
2615
2642
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2643
|
+
let versionStr = tmpA[tmpA.length - 1].trim().replace("*", "0");
|
|
2644
|
+
if (versionStr.indexOf(" ") > -1) {
|
|
2645
|
+
versionStr = versionStr.split(" ")[0];
|
|
2646
|
+
}
|
|
2647
|
+
if (versionStr === "0") {
|
|
2648
|
+
versionStr = null;
|
|
2649
|
+
}
|
|
2650
|
+
if (!tmpA[0].includes("=") && !tmpA[0].trim().includes(" ")) {
|
|
2651
|
+
const name = tmpA[0].trim().replace(";", "");
|
|
2652
|
+
if (!PYTHON_STD_MODULES.includes(name)) {
|
|
2653
|
+
const apkg = {
|
|
2654
|
+
name,
|
|
2655
|
+
version: versionStr,
|
|
2656
|
+
scope: compScope
|
|
2657
|
+
};
|
|
2658
|
+
if (markers) {
|
|
2659
|
+
apkg.properties = [
|
|
2660
|
+
{
|
|
2661
|
+
name: "cdx:pip:markers",
|
|
2662
|
+
value: markers
|
|
2663
|
+
}
|
|
2664
|
+
];
|
|
2630
2665
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
}
|
|
2634
|
-
} else if (/[>|[|@]/.test(l)) {
|
|
2635
|
-
let tmpA = l.split(/(>|\[|@)/);
|
|
2636
|
-
if (tmpA.includes("#")) {
|
|
2637
|
-
tmpA = tmpA.split("#")[0];
|
|
2638
|
-
}
|
|
2639
|
-
if (!tmpA[0].trim().includes(" ")) {
|
|
2640
|
-
const name = tmpA[0].trim().replace(";", "");
|
|
2641
|
-
const versionSpecifiers = l.replace(name, "");
|
|
2642
|
-
if (!PYTHON_STD_MODULES.includes(name)) {
|
|
2643
|
-
pkgList.push({
|
|
2644
|
-
name,
|
|
2645
|
-
version: undefined,
|
|
2646
|
-
scope: compScope,
|
|
2647
|
-
properties: [
|
|
2648
|
-
{
|
|
2649
|
-
name: "cdx:pypi:versionSpecifiers",
|
|
2650
|
-
value: versionSpecifiers
|
|
2651
|
-
}
|
|
2652
|
-
]
|
|
2653
|
-
});
|
|
2666
|
+
pkgList.push(apkg);
|
|
2667
|
+
}
|
|
2654
2668
|
}
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
if (l.includes("#")) {
|
|
2658
|
-
l = l.split("#")[0];
|
|
2659
|
-
}
|
|
2660
|
-
l = l.trim();
|
|
2661
|
-
const tmpA = l.split(/(<|>)/);
|
|
2662
|
-
if (tmpA && tmpA.length === 3) {
|
|
2669
|
+
} else if (l.includes("<") && l.includes(">")) {
|
|
2670
|
+
const tmpA = l.split(">");
|
|
2663
2671
|
const name = tmpA[0].trim().replace(";", "");
|
|
2664
2672
|
const versionSpecifiers = l.replace(name, "");
|
|
2665
2673
|
if (!PYTHON_STD_MODULES.includes(name)) {
|
|
@@ -2675,26 +2683,70 @@ export async function parseReqFile(reqData, fetchDepsInfo) {
|
|
|
2675
2683
|
]
|
|
2676
2684
|
});
|
|
2677
2685
|
}
|
|
2678
|
-
} else if (
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2686
|
+
} else if (/[>|[|@]/.test(l)) {
|
|
2687
|
+
let tmpA = l.split(/(>|\[|@)/);
|
|
2688
|
+
if (tmpA.includes("#")) {
|
|
2689
|
+
tmpA = tmpA.split("#")[0];
|
|
2690
|
+
}
|
|
2691
|
+
if (!tmpA[0].trim().includes(" ")) {
|
|
2692
|
+
const name = tmpA[0].trim().replace(";", "");
|
|
2693
|
+
const versionSpecifiers = l.replace(name, "");
|
|
2694
|
+
if (!PYTHON_STD_MODULES.includes(name)) {
|
|
2695
|
+
pkgList.push({
|
|
2696
|
+
name,
|
|
2697
|
+
version: undefined,
|
|
2698
|
+
scope: compScope,
|
|
2699
|
+
properties: [
|
|
2700
|
+
{
|
|
2701
|
+
name: "cdx:pypi:versionSpecifiers",
|
|
2702
|
+
value: versionSpecifiers
|
|
2703
|
+
}
|
|
2704
|
+
]
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
} else if (l) {
|
|
2709
|
+
if (l.includes("#")) {
|
|
2710
|
+
l = l.split("#")[0];
|
|
2711
|
+
}
|
|
2712
|
+
l = l.trim();
|
|
2713
|
+
const tmpA = l.split(/(<|>)/);
|
|
2714
|
+
if (tmpA && tmpA.length === 3) {
|
|
2715
|
+
const name = tmpA[0].trim().replace(";", "");
|
|
2716
|
+
const versionSpecifiers = l.replace(name, "");
|
|
2717
|
+
if (!PYTHON_STD_MODULES.includes(name)) {
|
|
2718
|
+
pkgList.push({
|
|
2719
|
+
name,
|
|
2720
|
+
version: undefined,
|
|
2721
|
+
scope: compScope,
|
|
2722
|
+
properties: [
|
|
2723
|
+
{
|
|
2724
|
+
name: "cdx:pypi:versionSpecifiers",
|
|
2725
|
+
value: versionSpecifiers
|
|
2726
|
+
}
|
|
2727
|
+
]
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
} else if (!l.includes(" ")) {
|
|
2731
|
+
const name = l.replace(";", "");
|
|
2732
|
+
const versionSpecifiers = l.replace(name, "");
|
|
2733
|
+
if (!PYTHON_STD_MODULES.includes(name)) {
|
|
2734
|
+
pkgList.push({
|
|
2735
|
+
name,
|
|
2736
|
+
version: null,
|
|
2737
|
+
scope: compScope,
|
|
2738
|
+
properties: [
|
|
2739
|
+
{
|
|
2740
|
+
name: "cdx:pypi:versionSpecifiers",
|
|
2741
|
+
value: versionSpecifiers
|
|
2742
|
+
}
|
|
2743
|
+
]
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2693
2746
|
}
|
|
2694
2747
|
}
|
|
2695
2748
|
}
|
|
2696
|
-
}
|
|
2697
|
-
});
|
|
2749
|
+
});
|
|
2698
2750
|
return await getPyMetadata(pkgList, fetchDepsInfo);
|
|
2699
2751
|
}
|
|
2700
2752
|
|
|
@@ -4587,6 +4639,28 @@ export const parseCsPkgLockData = async function (csLockData) {
|
|
|
4587
4639
|
return pkgList;
|
|
4588
4640
|
};
|
|
4589
4641
|
|
|
4642
|
+
export const parsePaketLockData = async function (paketLockData) {
|
|
4643
|
+
const pkgList = [];
|
|
4644
|
+
let pkg = null;
|
|
4645
|
+
if (!paketLockData) {
|
|
4646
|
+
return pkgList;
|
|
4647
|
+
}
|
|
4648
|
+
const pkgRegex = /\s+([a-zA-Z0-9-.]+) \(((?=.*?\.)[a-zA-Z0-9-.]+)\)/g;
|
|
4649
|
+
for (const [, name, version] of paketLockData.matchAll(pkgRegex)) {
|
|
4650
|
+
const purl = decodeURIComponent(
|
|
4651
|
+
new PackageURL("nuget", "", name, version, null, null).toString()
|
|
4652
|
+
);
|
|
4653
|
+
pkg = {
|
|
4654
|
+
group: "",
|
|
4655
|
+
name: name,
|
|
4656
|
+
version: version,
|
|
4657
|
+
purl: purl
|
|
4658
|
+
};
|
|
4659
|
+
pkgList.push(pkg);
|
|
4660
|
+
}
|
|
4661
|
+
return pkgList;
|
|
4662
|
+
};
|
|
4663
|
+
|
|
4590
4664
|
/**
|
|
4591
4665
|
* Parse composer lock file
|
|
4592
4666
|
*
|
|
@@ -5329,11 +5403,7 @@ export const collectMvnDependencies = function (
|
|
|
5329
5403
|
"-Dmdep.stripVersion=" + (process.env.MAVEN_STRIP_VERSION || "false")
|
|
5330
5404
|
];
|
|
5331
5405
|
if (basePath && basePath !== MAVEN_CACHE_DIR) {
|
|
5332
|
-
console.log(
|
|
5333
|
-
`Executing '${mavenCmd} dependency:copy-dependencies ${copyArgs.join(
|
|
5334
|
-
" "
|
|
5335
|
-
)}' in ${basePath}`
|
|
5336
|
-
);
|
|
5406
|
+
console.log(`Executing '${mavenCmd} ${copyArgs.join(" ")}' in ${basePath}`);
|
|
5337
5407
|
const result = spawnSync(mavenCmd, copyArgs, {
|
|
5338
5408
|
cwd: basePath,
|
|
5339
5409
|
encoding: "utf-8",
|
|
@@ -5710,10 +5780,15 @@ export const encodeForPurl = (s) => {
|
|
|
5710
5780
|
*
|
|
5711
5781
|
* @param {string} jarFile Path to jar file
|
|
5712
5782
|
* @param {string} tempDir Temporary directory to use for extraction
|
|
5783
|
+
* @param {object} jarNSMapping Jar class names mapping object
|
|
5713
5784
|
*
|
|
5714
5785
|
* @return pkgList Package list
|
|
5715
5786
|
*/
|
|
5716
|
-
export const extractJarArchive = function (
|
|
5787
|
+
export const extractJarArchive = function (
|
|
5788
|
+
jarFile,
|
|
5789
|
+
tempDir,
|
|
5790
|
+
jarNSMapping = {}
|
|
5791
|
+
) {
|
|
5717
5792
|
const pkgList = [];
|
|
5718
5793
|
let jarFiles = [];
|
|
5719
5794
|
const fname = basename(jarFile);
|
|
@@ -5873,10 +5948,19 @@ export const extractJarArchive = function (jarFile, tempDir) {
|
|
|
5873
5948
|
if (group == name) {
|
|
5874
5949
|
group = "";
|
|
5875
5950
|
}
|
|
5876
|
-
|
|
5877
|
-
|
|
5951
|
+
group = group === "." ? "" : encodeForPurl(group || "") || "";
|
|
5952
|
+
let apkg = {
|
|
5953
|
+
group,
|
|
5878
5954
|
name: name ? encodeForPurl(name) : "",
|
|
5879
5955
|
version,
|
|
5956
|
+
purl: new PackageURL(
|
|
5957
|
+
"maven",
|
|
5958
|
+
group,
|
|
5959
|
+
name,
|
|
5960
|
+
version,
|
|
5961
|
+
{ type: "jar" },
|
|
5962
|
+
null
|
|
5963
|
+
).toString(),
|
|
5880
5964
|
evidence: {
|
|
5881
5965
|
identity: {
|
|
5882
5966
|
field: "purl",
|
|
@@ -5896,7 +5980,18 @@ export const extractJarArchive = function (jarFile, tempDir) {
|
|
|
5896
5980
|
value: jarname
|
|
5897
5981
|
}
|
|
5898
5982
|
]
|
|
5899
|
-
}
|
|
5983
|
+
};
|
|
5984
|
+
if (
|
|
5985
|
+
jarNSMapping &&
|
|
5986
|
+
jarNSMapping[apkg.purl] &&
|
|
5987
|
+
jarNSMapping[apkg.purl].namespaces
|
|
5988
|
+
) {
|
|
5989
|
+
apkg.properties.push({
|
|
5990
|
+
name: "Namespaces",
|
|
5991
|
+
value: jarNSMapping[apkg.purl].namespaces.join("\n")
|
|
5992
|
+
});
|
|
5993
|
+
}
|
|
5994
|
+
pkgList.push(apkg);
|
|
5900
5995
|
} else {
|
|
5901
5996
|
if (DEBUG_MODE) {
|
|
5902
5997
|
console.log(`Ignored jar ${jarname}`, jarMetadata, name, version);
|
|
@@ -6225,7 +6320,7 @@ export const executeAtom = (src, args) => {
|
|
|
6225
6320
|
result.stderr.includes("Error: Could not create the Java Virtual Machine")
|
|
6226
6321
|
) {
|
|
6227
6322
|
console.log(
|
|
6228
|
-
"Atom requires Java 17 or above. To improve the
|
|
6323
|
+
"Atom requires Java 17 or above. To improve the SBOM accuracy, please install a suitable version, set the JAVA_HOME environment variable, and re-run cdxgen.\nAlternatively, use the cdxgen container image."
|
|
6229
6324
|
);
|
|
6230
6325
|
console.log(`Current JAVA_HOME: ${env["JAVA_HOME"] || ""}`);
|
|
6231
6326
|
} else if (result.stderr.includes("astgen")) {
|
|
@@ -6510,7 +6605,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
|
|
|
6510
6605
|
) {
|
|
6511
6606
|
versionRelatedError = true;
|
|
6512
6607
|
console.log(
|
|
6513
|
-
"The version or the version specifiers used for a dependency is invalid. Resolve the below error to improve
|
|
6608
|
+
"The version or the version specifiers used for a dependency is invalid. Resolve the below error to improve SBOM accuracy."
|
|
6514
6609
|
);
|
|
6515
6610
|
console.log(result.stderr);
|
|
6516
6611
|
}
|
|
@@ -6518,7 +6613,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
|
|
|
6518
6613
|
console.log("args used:", pipInstallArgs);
|
|
6519
6614
|
console.log(result.stdout, result.stderr);
|
|
6520
6615
|
console.log(
|
|
6521
|
-
"Possible build errors detected. The resulting list in the
|
|
6616
|
+
"Possible build errors detected. The resulting list in the SBOM would therefore be incomplete.\nTry installing any missing build tools or development libraries to improve the accuracy."
|
|
6522
6617
|
);
|
|
6523
6618
|
if (platform() === "win32") {
|
|
6524
6619
|
console.log(
|
|
@@ -6541,7 +6636,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
|
|
|
6541
6636
|
if (env.VIRTUAL_ENV && env.VIRTUAL_ENV.length) {
|
|
6542
6637
|
/**
|
|
6543
6638
|
* At this point, the previous attempt to do a pip install might have failed and we might have an unclean virtual environment with an incomplete list
|
|
6544
|
-
* The position taken by cdxgen is "Some
|
|
6639
|
+
* The position taken by cdxgen is "Some SBOM is better than no SBOM", so we proceed to collecting the dependencies that got installed with pip freeze
|
|
6545
6640
|
*/
|
|
6546
6641
|
if (DEBUG_MODE) {
|
|
6547
6642
|
console.log(
|
|
@@ -6597,7 +6692,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
|
|
|
6597
6692
|
} else {
|
|
6598
6693
|
if (DEBUG_MODE) {
|
|
6599
6694
|
console.log(
|
|
6600
|
-
"NOTE: Setup and activate a python virtual environment for this project prior to invoking cdxgen to improve
|
|
6695
|
+
"NOTE: Setup and activate a python virtual environment for this project prior to invoking cdxgen to improve SBOM accuracy."
|
|
6601
6696
|
);
|
|
6602
6697
|
}
|
|
6603
6698
|
}
|
package/utils.test.js
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
parseCsProjData,
|
|
33
33
|
parseCsProjAssetsData,
|
|
34
34
|
parseCsPkgLockData,
|
|
35
|
+
parsePaketLockData,
|
|
35
36
|
getNugetMetadata,
|
|
36
37
|
parsePom,
|
|
37
38
|
getMvnMetadata,
|
|
@@ -1246,6 +1247,20 @@ test("parse packages.lock.json", async () => {
|
|
|
1246
1247
|
});
|
|
1247
1248
|
});
|
|
1248
1249
|
|
|
1250
|
+
test("parse paket.lock", async () => {
|
|
1251
|
+
expect(await parsePaketLockData(null)).toEqual([]);
|
|
1252
|
+
const dep_list = await parsePaketLockData(
|
|
1253
|
+
readFileSync("./test/data/paket.lock", { encoding: "utf-8" })
|
|
1254
|
+
);
|
|
1255
|
+
expect(dep_list.length).toEqual(13);
|
|
1256
|
+
expect(dep_list[0]).toEqual({
|
|
1257
|
+
group: "",
|
|
1258
|
+
name: "0x53A.ReferenceAssemblies.Paket",
|
|
1259
|
+
version: "0.2",
|
|
1260
|
+
purl: "pkg:nuget/0x53A.ReferenceAssemblies.Paket@0.2"
|
|
1261
|
+
});
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1249
1264
|
test("parse .net cs proj", async () => {
|
|
1250
1265
|
expect(await parseCsProjData(null)).toEqual([]);
|
|
1251
1266
|
const dep_list = await parseCsProjData(
|
|
@@ -1270,7 +1285,7 @@ test("get nget metadata", async () => {
|
|
|
1270
1285
|
"pkg:nuget/NUnit.Console@3.11.1",
|
|
1271
1286
|
"pkg:nuget/NUnit3TestAdapter@3.16.1",
|
|
1272
1287
|
"pkg:nuget/NUnitLite@3.13.3",
|
|
1273
|
-
"pkg:nuget/Serilog@
|
|
1288
|
+
"pkg:nuget/Serilog@3.0.1",
|
|
1274
1289
|
"pkg:nuget/Serilog.Sinks.TextWriter@2.0.0",
|
|
1275
1290
|
"pkg:nuget/System.Security.Permissions@4.7.0",
|
|
1276
1291
|
"pkg:nuget/log4net@2.0.13",
|
|
@@ -1295,7 +1310,7 @@ test("get nget metadata", async () => {
|
|
|
1295
1310
|
"pkg:nuget/System.Text.RegularExpressions@4.1.0",
|
|
1296
1311
|
"pkg:nuget/System.Threading@4.0.11"
|
|
1297
1312
|
],
|
|
1298
|
-
ref: "pkg:nuget/Serilog@
|
|
1313
|
+
ref: "pkg:nuget/Serilog@3.0.1"
|
|
1299
1314
|
}
|
|
1300
1315
|
];
|
|
1301
1316
|
let pkg_list = [
|
|
@@ -1308,8 +1323,8 @@ test("get nget metadata", async () => {
|
|
|
1308
1323
|
{
|
|
1309
1324
|
group: "",
|
|
1310
1325
|
name: "Serilog",
|
|
1311
|
-
version: "
|
|
1312
|
-
"bom-ref": "pkg:nuget/Serilog@
|
|
1326
|
+
version: "3.0.1",
|
|
1327
|
+
"bom-ref": "pkg:nuget/Serilog@3.0.1"
|
|
1313
1328
|
}
|
|
1314
1329
|
];
|
|
1315
1330
|
const { pkgList, dependencies } = await getNugetMetadata(pkg_list, dep_list);
|
|
@@ -2261,6 +2276,25 @@ test("parse requirements.txt", async () => {
|
|
|
2261
2276
|
version: "8.6.2",
|
|
2262
2277
|
scope: "required"
|
|
2263
2278
|
});
|
|
2279
|
+
deps = await parseReqFile(
|
|
2280
|
+
readFileSync("./test/data/chen-science-requirements.txt", {
|
|
2281
|
+
encoding: "utf-8"
|
|
2282
|
+
}),
|
|
2283
|
+
false
|
|
2284
|
+
);
|
|
2285
|
+
expect(deps.length).toEqual(87);
|
|
2286
|
+
expect(deps[0]).toEqual({
|
|
2287
|
+
name: "aiofiles",
|
|
2288
|
+
version: "23.2.1",
|
|
2289
|
+
scope: undefined,
|
|
2290
|
+
properties: [
|
|
2291
|
+
{
|
|
2292
|
+
name: "cdx:pip:markers",
|
|
2293
|
+
value:
|
|
2294
|
+
'python_full_version >= "3.8.1" and python_version < "3.12" --hash=sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107 --hash=sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a'
|
|
2295
|
+
}
|
|
2296
|
+
]
|
|
2297
|
+
});
|
|
2264
2298
|
});
|
|
2265
2299
|
|
|
2266
2300
|
test("parse pyproject.toml", async () => {
|