@cyclonedx/cdxgen 9.8.4 → 9.8.6
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 +24 -24
- 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 +14 -21
- package/package.json +11 -9
- package/server.js +10 -3
- package/utils.js +183 -106
- package/utils.test.js +24 -5
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
|
|
|
@@ -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
|
@@ -686,7 +686,6 @@ function addComponent(
|
|
|
686
686
|
encodeForPurl(pkg.subpath)
|
|
687
687
|
);
|
|
688
688
|
let purlString = purl.toString();
|
|
689
|
-
purlString = decodeURIComponent(purlString);
|
|
690
689
|
let description = { "#cdata": pkg.description };
|
|
691
690
|
if (format === "json") {
|
|
692
691
|
description = pkg.description || undefined;
|
|
@@ -1128,17 +1127,11 @@ export const createJavaBom = async (path, options) => {
|
|
|
1128
1127
|
console.log(`Retrieving packages from ${path}`);
|
|
1129
1128
|
}
|
|
1130
1129
|
const tempDir = mkdtempSync(join(tmpdir(), "war-deps-"));
|
|
1131
|
-
|
|
1130
|
+
jarNSMapping = collectJarNS(tempDir);
|
|
1131
|
+
pkgList = extractJarArchive(path, tempDir, jarNSMapping);
|
|
1132
1132
|
if (pkgList.length) {
|
|
1133
1133
|
pkgList = await getMvnMetadata(pkgList);
|
|
1134
1134
|
}
|
|
1135
|
-
// Should we attempt to resolve class names
|
|
1136
|
-
if (options.resolveClass) {
|
|
1137
|
-
console.log(
|
|
1138
|
-
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1139
|
-
);
|
|
1140
|
-
jarNSMapping = collectJarNS(tempDir);
|
|
1141
|
-
}
|
|
1142
1135
|
// Clean up
|
|
1143
1136
|
if (tempDir && tempDir.startsWith(tmpdir()) && rmSync) {
|
|
1144
1137
|
console.log(`Cleaning up ${tempDir}`);
|
|
@@ -1193,7 +1186,7 @@ export const createJavaBom = async (path, options) => {
|
|
|
1193
1186
|
}
|
|
1194
1187
|
const mavenCmd = getMavenCommand(basePath, path);
|
|
1195
1188
|
// Should we attempt to resolve class names
|
|
1196
|
-
if (options.resolveClass) {
|
|
1189
|
+
if (options.resolveClass || options.deep) {
|
|
1197
1190
|
console.log(
|
|
1198
1191
|
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1199
1192
|
);
|
|
@@ -1352,7 +1345,7 @@ export const createJavaBom = async (path, options) => {
|
|
|
1352
1345
|
}
|
|
1353
1346
|
if (pkgList) {
|
|
1354
1347
|
pkgList = trimComponents(pkgList, "json");
|
|
1355
|
-
pkgList = await getMvnMetadata(pkgList);
|
|
1348
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping);
|
|
1356
1349
|
return buildBomNSData(options, pkgList, "maven", {
|
|
1357
1350
|
src: path,
|
|
1358
1351
|
filename: pomFiles.join(", "),
|
|
@@ -1552,15 +1545,14 @@ export const createJavaBom = async (path, options) => {
|
|
|
1552
1545
|
);
|
|
1553
1546
|
options.failOnError && process.exit(1);
|
|
1554
1547
|
}
|
|
1555
|
-
|
|
1556
|
-
pkgList = await getMvnMetadata(pkgList);
|
|
1557
1548
|
// Should we attempt to resolve class names
|
|
1558
|
-
if (options.resolveClass) {
|
|
1549
|
+
if (options.resolveClass || options.deep) {
|
|
1559
1550
|
console.log(
|
|
1560
1551
|
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1561
1552
|
);
|
|
1562
1553
|
jarNSMapping = collectJarNS(GRADLE_CACHE_DIR);
|
|
1563
1554
|
}
|
|
1555
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping);
|
|
1564
1556
|
return buildBomNSData(options, pkgList, "maven", {
|
|
1565
1557
|
src: path,
|
|
1566
1558
|
filename: gradleFiles.join(", "),
|
|
@@ -1655,7 +1647,8 @@ export const createJavaBom = async (path, options) => {
|
|
|
1655
1647
|
console.log("Bazel unexpectedly didn't produce any output");
|
|
1656
1648
|
options.failOnError && process.exit(1);
|
|
1657
1649
|
}
|
|
1658
|
-
|
|
1650
|
+
// FIXME: How do we retrieve jarNSMapping for bazel projects?
|
|
1651
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping);
|
|
1659
1652
|
return buildBomNSData(options, pkgList, "maven", {
|
|
1660
1653
|
src: path,
|
|
1661
1654
|
filename: "BUILD",
|
|
@@ -1836,14 +1829,14 @@ export const createJavaBom = async (path, options) => {
|
|
|
1836
1829
|
if (DEBUG_MODE) {
|
|
1837
1830
|
console.log(`Found ${pkgList.length} packages`);
|
|
1838
1831
|
}
|
|
1839
|
-
pkgList = await getMvnMetadata(pkgList);
|
|
1840
1832
|
// Should we attempt to resolve class names
|
|
1841
|
-
if (options.resolveClass) {
|
|
1833
|
+
if (options.resolveClass || options.deep) {
|
|
1842
1834
|
console.log(
|
|
1843
1835
|
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1844
1836
|
);
|
|
1845
1837
|
jarNSMapping = collectJarNS(SBT_CACHE_DIR);
|
|
1846
1838
|
}
|
|
1839
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping);
|
|
1847
1840
|
return buildBomNSData(options, pkgList, "maven", {
|
|
1848
1841
|
src: path,
|
|
1849
1842
|
filename: sbtProjects.join(", "),
|
|
@@ -3584,7 +3577,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
|
|
|
3584
3577
|
// img could have .service, .ociSpec or .image
|
|
3585
3578
|
if (img.ociSpec) {
|
|
3586
3579
|
console.log(
|
|
3587
|
-
`NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this
|
|
3580
|
+
`NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this SBOM.`
|
|
3588
3581
|
);
|
|
3589
3582
|
ociSpecs.push({
|
|
3590
3583
|
group: "",
|
|
@@ -3706,7 +3699,7 @@ export const createContainerSpecLikeBom = async (path, options) => {
|
|
|
3706
3699
|
// Parse privado files
|
|
3707
3700
|
if (privadoFiles.length) {
|
|
3708
3701
|
console.log(
|
|
3709
|
-
"Enriching your
|
|
3702
|
+
"Enriching your SBOM with information from privado.ai scan reports"
|
|
3710
3703
|
);
|
|
3711
3704
|
let rows = [["Classification", "Flow"]];
|
|
3712
3705
|
const config = {
|
|
@@ -5294,12 +5287,12 @@ export async function submitBom(args, bomContents) {
|
|
|
5294
5287
|
}).json();
|
|
5295
5288
|
} catch (error) {
|
|
5296
5289
|
console.log(
|
|
5297
|
-
"Unable to submit the
|
|
5290
|
+
"Unable to submit the SBOM to the Dependency-Track server using POST method"
|
|
5298
5291
|
);
|
|
5299
5292
|
console.log(error);
|
|
5300
5293
|
}
|
|
5301
5294
|
} else {
|
|
5302
|
-
console.log("Unable to submit the
|
|
5295
|
+
console.log("Unable to submit the SBOM to the Dependency-Track server");
|
|
5303
5296
|
console.log(error);
|
|
5304
5297
|
}
|
|
5305
5298
|
}
|
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.6",
|
|
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": "^1.2.
|
|
85
|
+
"@appthreat/atom": "^1.2.3",
|
|
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,7 +101,9 @@
|
|
|
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
|
}
|
package/server.js
CHANGED
|
@@ -55,7 +55,8 @@ const parseQueryString = (q, body, options = {}) => {
|
|
|
55
55
|
"projectVersion",
|
|
56
56
|
"parentUUID",
|
|
57
57
|
"serverUrl",
|
|
58
|
-
"apiKey"
|
|
58
|
+
"apiKey",
|
|
59
|
+
"specVersion"
|
|
59
60
|
];
|
|
60
61
|
|
|
61
62
|
for (const param of queryParams) {
|
|
@@ -88,6 +89,12 @@ const start = (options) => {
|
|
|
88
89
|
.createServer(app)
|
|
89
90
|
.listen(options.serverPort, options.serverHost);
|
|
90
91
|
configureServer(cdxgenServer);
|
|
92
|
+
|
|
93
|
+
app.use("/health", async function (req, res) {
|
|
94
|
+
res.setHeader("Content-Type", "application/json");
|
|
95
|
+
res.end(JSON.stringify({ status: "OK" }, null, 2));
|
|
96
|
+
});
|
|
97
|
+
|
|
91
98
|
app.use("/sbom", async function (req, res) {
|
|
92
99
|
const q = url.parse(req.url, true).query;
|
|
93
100
|
let cleanup = false;
|
|
@@ -105,7 +112,7 @@ const start = (options) => {
|
|
|
105
112
|
srcDir = gitClone(filePath);
|
|
106
113
|
cleanup = true;
|
|
107
114
|
}
|
|
108
|
-
console.log("Generating
|
|
115
|
+
console.log("Generating SBOM for", srcDir);
|
|
109
116
|
const bomNSData = (await createBom(srcDir, options)) || {};
|
|
110
117
|
if (bomNSData.bomJson) {
|
|
111
118
|
if (
|
|
@@ -118,7 +125,7 @@ const start = (options) => {
|
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
if (options.serverUrl && options.apiKey) {
|
|
121
|
-
console.log("Publishing
|
|
128
|
+
console.log("Publishing SBOM to Dependency Track");
|
|
122
129
|
submitBom(options, bomNSData.bomJson);
|
|
123
130
|
}
|
|
124
131
|
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
|
|
|
@@ -5710,10 +5762,15 @@ export const encodeForPurl = (s) => {
|
|
|
5710
5762
|
*
|
|
5711
5763
|
* @param {string} jarFile Path to jar file
|
|
5712
5764
|
* @param {string} tempDir Temporary directory to use for extraction
|
|
5765
|
+
* @param {object} jarNSMapping Jar class names mapping object
|
|
5713
5766
|
*
|
|
5714
5767
|
* @return pkgList Package list
|
|
5715
5768
|
*/
|
|
5716
|
-
export const extractJarArchive = function (
|
|
5769
|
+
export const extractJarArchive = function (
|
|
5770
|
+
jarFile,
|
|
5771
|
+
tempDir,
|
|
5772
|
+
jarNSMapping = {}
|
|
5773
|
+
) {
|
|
5717
5774
|
const pkgList = [];
|
|
5718
5775
|
let jarFiles = [];
|
|
5719
5776
|
const fname = basename(jarFile);
|
|
@@ -5873,10 +5930,19 @@ export const extractJarArchive = function (jarFile, tempDir) {
|
|
|
5873
5930
|
if (group == name) {
|
|
5874
5931
|
group = "";
|
|
5875
5932
|
}
|
|
5876
|
-
|
|
5877
|
-
|
|
5933
|
+
group = group === "." ? "" : encodeForPurl(group || "") || "";
|
|
5934
|
+
let apkg = {
|
|
5935
|
+
group,
|
|
5878
5936
|
name: name ? encodeForPurl(name) : "",
|
|
5879
5937
|
version,
|
|
5938
|
+
purl: new PackageURL(
|
|
5939
|
+
"maven",
|
|
5940
|
+
group,
|
|
5941
|
+
name,
|
|
5942
|
+
version,
|
|
5943
|
+
{ type: "jar" },
|
|
5944
|
+
null
|
|
5945
|
+
).toString(),
|
|
5880
5946
|
evidence: {
|
|
5881
5947
|
identity: {
|
|
5882
5948
|
field: "purl",
|
|
@@ -5896,7 +5962,18 @@ export const extractJarArchive = function (jarFile, tempDir) {
|
|
|
5896
5962
|
value: jarname
|
|
5897
5963
|
}
|
|
5898
5964
|
]
|
|
5899
|
-
}
|
|
5965
|
+
};
|
|
5966
|
+
if (
|
|
5967
|
+
jarNSMapping &&
|
|
5968
|
+
jarNSMapping[apkg.purl] &&
|
|
5969
|
+
jarNSMapping[apkg.purl].namespaces
|
|
5970
|
+
) {
|
|
5971
|
+
apkg.properties.push({
|
|
5972
|
+
name: "Namespaces",
|
|
5973
|
+
value: jarNSMapping[apkg.purl].namespaces.join("\n")
|
|
5974
|
+
});
|
|
5975
|
+
}
|
|
5976
|
+
pkgList.push(apkg);
|
|
5900
5977
|
} else {
|
|
5901
5978
|
if (DEBUG_MODE) {
|
|
5902
5979
|
console.log(`Ignored jar ${jarname}`, jarMetadata, name, version);
|
|
@@ -6225,7 +6302,7 @@ export const executeAtom = (src, args) => {
|
|
|
6225
6302
|
result.stderr.includes("Error: Could not create the Java Virtual Machine")
|
|
6226
6303
|
) {
|
|
6227
6304
|
console.log(
|
|
6228
|
-
"Atom requires Java 17 or above. To improve the
|
|
6305
|
+
"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
6306
|
);
|
|
6230
6307
|
console.log(`Current JAVA_HOME: ${env["JAVA_HOME"] || ""}`);
|
|
6231
6308
|
} else if (result.stderr.includes("astgen")) {
|
|
@@ -6510,7 +6587,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
|
|
|
6510
6587
|
) {
|
|
6511
6588
|
versionRelatedError = true;
|
|
6512
6589
|
console.log(
|
|
6513
|
-
"The version or the version specifiers used for a dependency is invalid. Resolve the below error to improve
|
|
6590
|
+
"The version or the version specifiers used for a dependency is invalid. Resolve the below error to improve SBOM accuracy."
|
|
6514
6591
|
);
|
|
6515
6592
|
console.log(result.stderr);
|
|
6516
6593
|
}
|
|
@@ -6518,7 +6595,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
|
|
|
6518
6595
|
console.log("args used:", pipInstallArgs);
|
|
6519
6596
|
console.log(result.stdout, result.stderr);
|
|
6520
6597
|
console.log(
|
|
6521
|
-
"Possible build errors detected. The resulting list in the
|
|
6598
|
+
"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
6599
|
);
|
|
6523
6600
|
if (platform() === "win32") {
|
|
6524
6601
|
console.log(
|
|
@@ -6541,7 +6618,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
|
|
|
6541
6618
|
if (env.VIRTUAL_ENV && env.VIRTUAL_ENV.length) {
|
|
6542
6619
|
/**
|
|
6543
6620
|
* 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
|
|
6621
|
+
* 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
6622
|
*/
|
|
6546
6623
|
if (DEBUG_MODE) {
|
|
6547
6624
|
console.log(
|
|
@@ -6597,7 +6674,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
|
|
|
6597
6674
|
} else {
|
|
6598
6675
|
if (DEBUG_MODE) {
|
|
6599
6676
|
console.log(
|
|
6600
|
-
"NOTE: Setup and activate a python virtual environment for this project prior to invoking cdxgen to improve
|
|
6677
|
+
"NOTE: Setup and activate a python virtual environment for this project prior to invoking cdxgen to improve SBOM accuracy."
|
|
6601
6678
|
);
|
|
6602
6679
|
}
|
|
6603
6680
|
}
|
package/utils.test.js
CHANGED
|
@@ -1143,7 +1143,7 @@ test("parse github actions workflow data", async () => {
|
|
|
1143
1143
|
dep_list = parseGitHubWorkflowData(
|
|
1144
1144
|
readFileSync("./.github/workflows/repotests.yml", { encoding: "utf-8" })
|
|
1145
1145
|
);
|
|
1146
|
-
expect(dep_list.length).toEqual(
|
|
1146
|
+
expect(dep_list.length).toEqual(7);
|
|
1147
1147
|
expect(dep_list[0]).toEqual({
|
|
1148
1148
|
group: "actions",
|
|
1149
1149
|
name: "checkout",
|
|
@@ -1270,7 +1270,7 @@ test("get nget metadata", async () => {
|
|
|
1270
1270
|
"pkg:nuget/NUnit.Console@3.11.1",
|
|
1271
1271
|
"pkg:nuget/NUnit3TestAdapter@3.16.1",
|
|
1272
1272
|
"pkg:nuget/NUnitLite@3.13.3",
|
|
1273
|
-
"pkg:nuget/Serilog@
|
|
1273
|
+
"pkg:nuget/Serilog@3.0.1",
|
|
1274
1274
|
"pkg:nuget/Serilog.Sinks.TextWriter@2.0.0",
|
|
1275
1275
|
"pkg:nuget/System.Security.Permissions@4.7.0",
|
|
1276
1276
|
"pkg:nuget/log4net@2.0.13",
|
|
@@ -1295,7 +1295,7 @@ test("get nget metadata", async () => {
|
|
|
1295
1295
|
"pkg:nuget/System.Text.RegularExpressions@4.1.0",
|
|
1296
1296
|
"pkg:nuget/System.Threading@4.0.11"
|
|
1297
1297
|
],
|
|
1298
|
-
ref: "pkg:nuget/Serilog@
|
|
1298
|
+
ref: "pkg:nuget/Serilog@3.0.1"
|
|
1299
1299
|
}
|
|
1300
1300
|
];
|
|
1301
1301
|
let pkg_list = [
|
|
@@ -1308,8 +1308,8 @@ test("get nget metadata", async () => {
|
|
|
1308
1308
|
{
|
|
1309
1309
|
group: "",
|
|
1310
1310
|
name: "Serilog",
|
|
1311
|
-
version: "
|
|
1312
|
-
"bom-ref": "pkg:nuget/Serilog@
|
|
1311
|
+
version: "3.0.1",
|
|
1312
|
+
"bom-ref": "pkg:nuget/Serilog@3.0.1"
|
|
1313
1313
|
}
|
|
1314
1314
|
];
|
|
1315
1315
|
const { pkgList, dependencies } = await getNugetMetadata(pkg_list, dep_list);
|
|
@@ -2261,6 +2261,25 @@ test("parse requirements.txt", async () => {
|
|
|
2261
2261
|
version: "8.6.2",
|
|
2262
2262
|
scope: "required"
|
|
2263
2263
|
});
|
|
2264
|
+
deps = await parseReqFile(
|
|
2265
|
+
readFileSync("./test/data/chen-science-requirements.txt", {
|
|
2266
|
+
encoding: "utf-8"
|
|
2267
|
+
}),
|
|
2268
|
+
false
|
|
2269
|
+
);
|
|
2270
|
+
expect(deps.length).toEqual(87);
|
|
2271
|
+
expect(deps[0]).toEqual({
|
|
2272
|
+
name: "aiofiles",
|
|
2273
|
+
version: "23.2.1",
|
|
2274
|
+
scope: undefined,
|
|
2275
|
+
properties: [
|
|
2276
|
+
{
|
|
2277
|
+
name: "cdx:pip:markers",
|
|
2278
|
+
value:
|
|
2279
|
+
'python_full_version >= "3.8.1" and python_version < "3.12" --hash=sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107 --hash=sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a'
|
|
2280
|
+
}
|
|
2281
|
+
]
|
|
2282
|
+
});
|
|
2264
2283
|
});
|
|
2265
2284
|
|
|
2266
2285
|
test("parse pyproject.toml", async () => {
|