@cyclonedx/cdxgen 12.1.4 → 12.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -39
- package/bin/cdxgen.js +181 -90
- package/bin/evinse.js +4 -4
- package/bin/repl.js +3 -3
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +484 -440
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +5 -18
- package/lib/evinser/swiftsem.js +1 -1
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/caxa.js +1 -1
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +429 -14
- package/lib/helpers/envcontext.js +23 -8
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +305 -0
- package/lib/helpers/pythonutils.poku.js +469 -0
- package/lib/helpers/utils.js +970 -528
- package/lib/helpers/utils.poku.js +139 -256
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +4 -10
- package/lib/parsers/npmrc.js +92 -0
- package/lib/parsers/npmrc.poku.js +528 -0
- package/lib/server/openapi.yaml +1 -10
- package/lib/server/server.js +58 -16
- package/lib/server/server.poku.js +123 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +197 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
- package/lib/third-party/arborist/lib/node.js +3 -3
- package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
- package/lib/third-party/arborist/lib/tree-check.js +1 -1
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -8
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +18 -0
- package/types/lib/helpers/pythonutils.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +532 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +26 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +21 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
[](https://github.com/cdxgen/cdxgen)
|
|
2
|
+
[](./AI-DECLARATION.md)
|
|
2
3
|
[![JSR][badge-jsr]][jsr-cdxgen]
|
|
3
4
|
[![NPM][badge-npm]][npmjs-cdxgen]
|
|
4
5
|
[![GitHub Releases][badge-github-releases]][github-releases]
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
|
|
12
13
|
<img src="./docs/_media/cdxgen.png" width="200" height="auto" />
|
|
13
14
|
|
|
14
|
-
cdxgen is a CLI tool, library, [REPL](./ADVANCED.md), and server to create
|
|
15
|
+
cdxgen is a CLI tool, library, [REPL](./ADVANCED.md), and server to create, validate, sign, and verify [CycloneDX][cyclonedx-homepage] Bill of Materials (BOM) containing an aggregate of all project dependencies in JSON format. CycloneDX is a full-stack BOM specification that is easily created, human and machine-readable, and simple to parse. The tool supports CycloneDX specification versions from 1.5 - 1.7.
|
|
15
16
|
|
|
16
17
|
Supported BOM formats:
|
|
17
18
|
|
|
@@ -31,8 +32,8 @@ Most SBOM tools are like simple barcode scanners. For easy applications, they ca
|
|
|
31
32
|
- _Explainability:_ Don't list, but explain with evidence.
|
|
32
33
|
- _Precision:_ Try using multiple techniques to improve precision, even if it takes extra time.
|
|
33
34
|
- _Personas:_ Cater to the needs of a range of personas such as security researchers, compliance auditors, developers, and SOC.
|
|
34
|
-
- _Lifecycle:_ Support BOM generation for various product lifecycles.
|
|
35
35
|
- _Machine Learning:_ Optimize the generated data for Machine Learning (ML) purposes by considering the various model properties.
|
|
36
|
+
- _Safety:_ Execute external build tools and handle untrusted inputs defensively, with hardened defaults and a [secure mode](docs/PERMISSIONS.md) for sensitive environments.
|
|
36
37
|
|
|
37
38
|
## Documentation
|
|
38
39
|
|
|
@@ -47,6 +48,8 @@ Sections include:
|
|
|
47
48
|
- [Environment Variables][docs-env-vars]
|
|
48
49
|
- [Advanced Usage][docs-advanced-usage]
|
|
49
50
|
- [Permissions][docs-permissions]
|
|
51
|
+
- [Security Policy](SECURITY.md)
|
|
52
|
+
- [Threat Model](docs/THREAT_MODEL.md)
|
|
50
53
|
- [Support (Enterprise & Community)][docs-support]
|
|
51
54
|
|
|
52
55
|
## Usage
|
|
@@ -104,7 +107,7 @@ docker run --rm -e CDXGEN_DEBUG_MODE=debug -v /tmp:/tmp -v $(pwd):/app:rw -t ghc
|
|
|
104
107
|
In deno applications, cdxgen could be directly imported without any conversion. Please see the section on [integration as a library](#integration-as-library)
|
|
105
108
|
|
|
106
109
|
```ts
|
|
107
|
-
import { createBom, submitBom } from "npm:@cyclonedx/cdxgen@^12.
|
|
110
|
+
import { createBom, submitBom } from "npm:@cyclonedx/cdxgen@^12.2.0";
|
|
108
111
|
```
|
|
109
112
|
|
|
110
113
|
## Getting Help
|
|
@@ -153,8 +156,8 @@ Options:
|
|
|
153
156
|
--validate Validate the generated SBOM using json schema. Defaults to true. Pass --no-validate to
|
|
154
157
|
disable. [boolean] [default: true]
|
|
155
158
|
--evidence Generate SBOM with evidence for supported languages. [boolean] [default: false]
|
|
156
|
-
--spec-version CycloneDX Specification version to use. Defaults to 1.
|
|
157
|
-
[number] [choices: 1.4, 1.5, 1.6, 1.7] [default: 1.
|
|
159
|
+
--spec-version CycloneDX Specification version to use. Defaults to 1.7
|
|
160
|
+
[number] [choices: 1.4, 1.5, 1.6, 1.7] [default: 1.7]
|
|
158
161
|
--filter Filter components containing this word in purl or component.properties.value. Multiple
|
|
159
162
|
values allowed. [array]
|
|
160
163
|
--only Include components only containing this word in purl. Useful to generate BOM with firs
|
|
@@ -217,11 +220,11 @@ To recursively generate a single BOM for all languages pass `-r` argument.
|
|
|
217
220
|
cdxgen -r -o bom.json
|
|
218
221
|
```
|
|
219
222
|
|
|
220
|
-
The default specification used by cdxgen is 1.
|
|
223
|
+
The default specification used by cdxgen is 1.7. To generate BOM for a different specification version, such as 1.5 or 1.6, pass the version number using the `--spec-version` argument.
|
|
221
224
|
|
|
222
225
|
```shell
|
|
223
|
-
# 1.
|
|
224
|
-
cdxgen -r -o bom.json --spec-version 1.
|
|
226
|
+
# 1.6 is supported by most tools
|
|
227
|
+
cdxgen -r -o bom.json --spec-version 1.6
|
|
225
228
|
```
|
|
226
229
|
|
|
227
230
|
To generate SBOM for C or Python, ensure Java >= 21 is installed.
|
|
@@ -418,60 +421,65 @@ cbom -t java
|
|
|
418
421
|
|
|
419
422
|
See [evinse mode](./ADVANCED.md) in the advanced documentation.
|
|
420
423
|
|
|
424
|
+
---
|
|
425
|
+
|
|
421
426
|
## BOM signing
|
|
422
427
|
|
|
423
|
-
cdxgen
|
|
428
|
+
cdxgen features a best-in-class, native **JSON Signature Format (JSF)** implementation for BOM signing, providing robust authenticity and non-repudiation capabilities. Unlike basic signing tools, our implementation fully supports granular signatures (signing individual components, services, and annotations), parallel Multi-Signatures (`signers`), and sequential Signature Chains (`chain`).
|
|
429
|
+
|
|
430
|
+
To enable automatic signing during BOM generation, set the following environment variables:
|
|
431
|
+
|
|
432
|
+
- `SBOM_SIGN_ALGORITHM`: JSF Algorithm. Examples: `RS512`, `ES256`, `Ed25519`, `HS256`
|
|
433
|
+
- `SBOM_SIGN_PRIVATE_KEY`: Location of the private key (PEM format)
|
|
434
|
+
- `SBOM_SIGN_PUBLIC_KEY`: Optional. Location of the public key
|
|
435
|
+
- `SBOM_SIGN_MODE`: Optional. Signature mode (`replace`, `signers`, `chain`). Default is `replace`.
|
|
436
|
+
|
|
437
|
+
To quickly generate test public/private key pairs and sign your first BOM, you can run cdxgen with the `--generate-key-and-sign` argument.
|
|
424
438
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
-
|
|
439
|
+
### Advanced Signing with `cdx-sign`
|
|
440
|
+
|
|
441
|
+
For complex supply chain orchestration, use the bundled `cdx-sign` CLI. This tool allows multiple entities (e.g., a Builder and an Auditor) to co-sign an existing BOM without modifying its original data.
|
|
442
|
+
|
|
443
|
+
```shell
|
|
444
|
+
# Append a parallel multi-signature (Auditor co-signing)
|
|
445
|
+
# Note: Granular component signing is disabled to preserve the Builder's original signature payload.
|
|
446
|
+
cdx-sign -i bom.json -k auditor_private.pem -a ES256 --key-id "auditor-qa" --mode signers --no-sign-components
|
|
447
|
+
```
|
|
428
448
|
|
|
429
|
-
|
|
449
|
+
### Validating CycloneDX BOMs
|
|
430
450
|
|
|
431
|
-
|
|
451
|
+
Use the bundled `cdx-validate` command to validate CycloneDX BOMs against **structural**, **deep**, and **compliance** checks. Refer to this [document](./docs/CDX_VALIDATE.md) for usage.
|
|
432
452
|
|
|
433
453
|
### Verifying the signature
|
|
434
454
|
|
|
435
|
-
Use the bundled `cdx-verify` command,
|
|
455
|
+
Use the bundled `cdx-verify` command to validate BOM signatures. By default, `cdx-verify` performs a **strict deep verification**, meaning it mathematically validates the top-level BOM signature _and_ the signatures of every nested component, service, and annotation against the provided public key. Refer to this [lesson](./docs/LESSON6.md) for the usage of sign and verify commands.
|
|
436
456
|
|
|
437
457
|
```shell
|
|
438
458
|
npm install -g @cyclonedx/cdxgen
|
|
459
|
+
|
|
460
|
+
# Perform strict deep verification (default)
|
|
439
461
|
cdx-verify -i bom.json --public-key public.key
|
|
462
|
+
|
|
463
|
+
# Verify ONLY the top-level root signature (useful for verifying a multi-signer who didn't sign nested components)
|
|
464
|
+
cdx-verify -i bom.json --public-key auditor_public.key --no-deep
|
|
440
465
|
```
|
|
441
466
|
|
|
442
467
|
### Verifying the signature (pnpm)
|
|
443
468
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
You can run it directly using pnpm (no global install needed):
|
|
469
|
+
You can run the verification tools directly using pnpm (no global install needed):
|
|
447
470
|
|
|
448
471
|
```shell
|
|
449
472
|
pnpm dlx @cyclonedx/cdxgen cdx-verify -i bom.json --public-key public.key
|
|
450
473
|
```
|
|
451
474
|
|
|
452
|
-
|
|
475
|
+
You can also use pnpm to invoke the signing tool:
|
|
453
476
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
```js
|
|
457
|
-
# npm install jws
|
|
458
|
-
const jws = require("jws");
|
|
459
|
-
const fs = require("fs");
|
|
460
|
-
// Location of the SBOM json file
|
|
461
|
-
const bomJsonFile = "bom.json";
|
|
462
|
-
// Location of the public key
|
|
463
|
-
const publicKeyFile = "public.key";
|
|
464
|
-
const bomJson = JSON.parse(fs.readFileSync(bomJsonFile, "utf8"));
|
|
465
|
-
// Retrieve the signature
|
|
466
|
-
const bomSignature = bomJson.signature.value;
|
|
467
|
-
const validationResult = jws.verify(bomSignature, bomJson.signature.algorithm, fs.readFileSync(publicKeyFile, "utf8"));
|
|
468
|
-
if (validationResult) {
|
|
469
|
-
console.log("Signature is valid!");
|
|
470
|
-
} else {
|
|
471
|
-
console.log("SBOM signature is invalid :(");
|
|
472
|
-
}
|
|
477
|
+
```shell
|
|
478
|
+
pnpm dlx @cyclonedx/cdxgen cdx-sign -i bom.json -k private.key
|
|
473
479
|
```
|
|
474
480
|
|
|
481
|
+
---
|
|
482
|
+
|
|
475
483
|
## Automatic usage detection
|
|
476
484
|
|
|
477
485
|
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.
|
|
@@ -568,7 +576,7 @@ Use `pnpm add -g` command to quickly test the main branch.
|
|
|
568
576
|
```shell
|
|
569
577
|
corepack pnpm bin -g
|
|
570
578
|
corepack pnpm setup
|
|
571
|
-
corepack pnpm add -g
|
|
579
|
+
corepack pnpm add -g https://github.com/cdxgen/cdxgen
|
|
572
580
|
cdxgen --help
|
|
573
581
|
```
|
|
574
582
|
|
package/bin/cdxgen.js
CHANGED
|
@@ -7,13 +7,14 @@ import https from "node:https";
|
|
|
7
7
|
import { basename, dirname, join, resolve } from "node:path";
|
|
8
8
|
import process from "node:process";
|
|
9
9
|
|
|
10
|
-
import jws from "jws";
|
|
11
10
|
import { parse as _load } from "yaml";
|
|
12
11
|
import yargs from "yargs";
|
|
13
12
|
import { hideBin } from "yargs/helpers";
|
|
14
13
|
|
|
15
14
|
import { createBom, submitBom } from "../lib/cli/index.js";
|
|
15
|
+
import { signBom, verifyBom } from "../lib/helpers/bomSigner.js";
|
|
16
16
|
import {
|
|
17
|
+
displaySelfThreatModel,
|
|
17
18
|
printCallStack,
|
|
18
19
|
printDependencyTree,
|
|
19
20
|
printFormulation,
|
|
@@ -26,7 +27,6 @@ import {
|
|
|
26
27
|
} from "../lib/helpers/display.js";
|
|
27
28
|
import { TRACE_MODE, thoughtEnd, thoughtLog } from "../lib/helpers/logger.js";
|
|
28
29
|
import {
|
|
29
|
-
ATOM_DB,
|
|
30
30
|
commandsExecuted,
|
|
31
31
|
DEBUG_MODE,
|
|
32
32
|
getTmpDir,
|
|
@@ -39,10 +39,12 @@ import {
|
|
|
39
39
|
remoteHostsAccessed,
|
|
40
40
|
retrieveCdxgenVersion,
|
|
41
41
|
safeExistsSync,
|
|
42
|
+
toCamel,
|
|
42
43
|
} from "../lib/helpers/utils.js";
|
|
43
|
-
import { validateBom } from "../lib/helpers/validator.js";
|
|
44
44
|
import { postProcess } from "../lib/stages/postgen/postgen.js";
|
|
45
|
+
import { auditEnvironment } from "../lib/stages/pregen/envAudit.js";
|
|
45
46
|
import { prepareEnv } from "../lib/stages/pregen/pregen.js";
|
|
47
|
+
import { validateBom } from "../lib/validator/bomValidator.js";
|
|
46
48
|
|
|
47
49
|
// Support for config files
|
|
48
50
|
const configPaths = [
|
|
@@ -63,6 +65,18 @@ for (const configPattern of configPaths) {
|
|
|
63
65
|
} else {
|
|
64
66
|
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
65
67
|
}
|
|
68
|
+
if (isSecureMode || DEBUG_MODE) {
|
|
69
|
+
console.log(`Config file '${configPath}' loaded successfully.`);
|
|
70
|
+
}
|
|
71
|
+
const sensitiveOptions = ["server-url", "include-formulation"];
|
|
72
|
+
for (const opt of sensitiveOptions) {
|
|
73
|
+
if (config[opt] !== undefined || config[toCamel(opt)] !== undefined) {
|
|
74
|
+
const foundKey = config[opt] !== undefined ? opt : toCamel(opt);
|
|
75
|
+
console.warn(
|
|
76
|
+
`SECURE MODE: Config file sets '${foundKey}'. Verify this is intentional.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
66
80
|
} catch (_e) {
|
|
67
81
|
console.log("Invalid config file", configPath);
|
|
68
82
|
}
|
|
@@ -75,6 +89,9 @@ const args = _yargs
|
|
|
75
89
|
.parserConfiguration({
|
|
76
90
|
"greedy-arrays": false,
|
|
77
91
|
"short-option-groups": false,
|
|
92
|
+
"dot-notation": false,
|
|
93
|
+
"parse-numbers": true,
|
|
94
|
+
"boolean-negation": true,
|
|
78
95
|
})
|
|
79
96
|
.option("output", {
|
|
80
97
|
alias: "o",
|
|
@@ -119,6 +136,7 @@ const args = _yargs
|
|
|
119
136
|
})
|
|
120
137
|
.option("server-url", {
|
|
121
138
|
description: "Dependency track url. Eg: https://deptrack.cyclonedx.io",
|
|
139
|
+
type: "string",
|
|
122
140
|
})
|
|
123
141
|
.option("skip-dt-tls-check", {
|
|
124
142
|
type: "boolean",
|
|
@@ -127,6 +145,7 @@ const args = _yargs
|
|
|
127
145
|
})
|
|
128
146
|
.option("api-key", {
|
|
129
147
|
description: "Dependency track api key",
|
|
148
|
+
type: "string",
|
|
130
149
|
})
|
|
131
150
|
.option("project-group", {
|
|
132
151
|
description: "Dependency track project group",
|
|
@@ -179,10 +198,12 @@ const args = _yargs
|
|
|
179
198
|
.option("server-host", {
|
|
180
199
|
description: "Listen address",
|
|
181
200
|
default: "127.0.0.1",
|
|
201
|
+
type: "string",
|
|
182
202
|
})
|
|
183
203
|
.option("server-port", {
|
|
184
204
|
description: "Listen port",
|
|
185
|
-
default:
|
|
205
|
+
default: 9090,
|
|
206
|
+
type: "number",
|
|
186
207
|
})
|
|
187
208
|
.option("install-deps", {
|
|
188
209
|
type: "boolean",
|
|
@@ -229,7 +250,7 @@ const args = _yargs
|
|
|
229
250
|
})
|
|
230
251
|
.option("spec-version", {
|
|
231
252
|
description: "CycloneDX Specification version to use. Defaults to 1.6",
|
|
232
|
-
default: 1.
|
|
253
|
+
default: 1.7,
|
|
233
254
|
type: "number",
|
|
234
255
|
choices: [1.4, 1.5, 1.6, 1.7],
|
|
235
256
|
})
|
|
@@ -354,6 +375,46 @@ const args = _yargs
|
|
|
354
375
|
default: "CLEAR",
|
|
355
376
|
hidden: true,
|
|
356
377
|
})
|
|
378
|
+
.option("env-audit", {
|
|
379
|
+
type: "boolean",
|
|
380
|
+
description:
|
|
381
|
+
"Display a pre-generation environment and configuration security assessment",
|
|
382
|
+
default: false,
|
|
383
|
+
hidden: true,
|
|
384
|
+
})
|
|
385
|
+
.option("bom-audit", {
|
|
386
|
+
type: "boolean",
|
|
387
|
+
description: "Perform post-generation security audit of BOM data",
|
|
388
|
+
default: false,
|
|
389
|
+
hidden: true,
|
|
390
|
+
})
|
|
391
|
+
.option("bom-audit-rules-dir", {
|
|
392
|
+
description:
|
|
393
|
+
"Directory containing additional YAML audit rules (merged with built-in)",
|
|
394
|
+
type: "string",
|
|
395
|
+
hidden: true,
|
|
396
|
+
})
|
|
397
|
+
.option("bom-audit-categories", {
|
|
398
|
+
description:
|
|
399
|
+
"Comma-separated list of rule categories to enable (default: all)",
|
|
400
|
+
type: "string",
|
|
401
|
+
hidden: true,
|
|
402
|
+
})
|
|
403
|
+
.option("bom-audit-min-severity", {
|
|
404
|
+
description:
|
|
405
|
+
"Minimum severity to report: low, medium, or high (default: low)",
|
|
406
|
+
type: "string",
|
|
407
|
+
choices: ["low", "medium", "high"],
|
|
408
|
+
default: "low",
|
|
409
|
+
hidden: true,
|
|
410
|
+
})
|
|
411
|
+
.option("bom-audit-fail-severity", {
|
|
412
|
+
description: "Severity threshold for secure mode failure (default: high)",
|
|
413
|
+
type: "string",
|
|
414
|
+
choices: ["high", "medium", "low"],
|
|
415
|
+
default: "high",
|
|
416
|
+
hidden: true,
|
|
417
|
+
})
|
|
357
418
|
.completion("completion", "Generate bash/zsh completion")
|
|
358
419
|
.array("type")
|
|
359
420
|
.array("excludeType")
|
|
@@ -519,7 +580,7 @@ if (["cbom", "saasbom"].includes(process.argv[1])) {
|
|
|
519
580
|
}
|
|
520
581
|
}
|
|
521
582
|
options.evidence = true;
|
|
522
|
-
options.specVersion = 1.
|
|
583
|
+
options.specVersion = 1.7;
|
|
523
584
|
options.deep = true;
|
|
524
585
|
}
|
|
525
586
|
if (process.argv[1].includes("cdxgen-secure")) {
|
|
@@ -533,16 +594,29 @@ if (process.argv[1].includes("cdxgen-secure")) {
|
|
|
533
594
|
process.env.CDXGEN_SECURE_MODE = true;
|
|
534
595
|
}
|
|
535
596
|
if (options.standard) {
|
|
536
|
-
options.specVersion = 1.
|
|
597
|
+
options.specVersion = 1.7;
|
|
537
598
|
}
|
|
538
599
|
if (options.includeFormulation) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
600
|
+
if (options.serverUrl) {
|
|
601
|
+
thoughtLog(
|
|
602
|
+
"Wait, the user specified a server URL and wants to include formulation data. Let's warn about accidentally disclosing sensitive data to a remote server.",
|
|
603
|
+
);
|
|
604
|
+
console.warn(
|
|
605
|
+
`\x1b[1;35mWARNING: The formulation section may include sensitive data such as emails and secrets. This data will be submitted to '${options.serverUrl}' automatically.\x1b[0m`,
|
|
606
|
+
);
|
|
607
|
+
if (isSecureMode) {
|
|
608
|
+
process.exit(1);
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
thoughtLog(
|
|
612
|
+
"Wait, the user wants to include formulation data. Let's warn about accidentally disclosing sensitive data via the generated BOM.",
|
|
613
|
+
);
|
|
614
|
+
console.log(
|
|
615
|
+
"NOTE: The formulation section may include sensitive data such as emails and secrets.\nPlease review the generated SBOM before distribution or LLM training.\n",
|
|
616
|
+
);
|
|
617
|
+
}
|
|
545
618
|
}
|
|
619
|
+
|
|
546
620
|
/**
|
|
547
621
|
* Method to apply advanced options such as profile and lifecycles
|
|
548
622
|
*
|
|
@@ -559,11 +633,13 @@ const applyAdvancedOptions = (options) => {
|
|
|
559
633
|
switch (options.profile) {
|
|
560
634
|
case "appsec":
|
|
561
635
|
options.deep = true;
|
|
636
|
+
options.bomAudit = true;
|
|
562
637
|
break;
|
|
563
638
|
case "research":
|
|
564
639
|
options.deep = true;
|
|
565
640
|
options.evidence = true;
|
|
566
641
|
options.includeCrypto = true;
|
|
642
|
+
options.bomAudit = true;
|
|
567
643
|
process.env.CDX_MAVEN_INCLUDE_TEST_SCOPE = "true";
|
|
568
644
|
process.env.ASTGEN_IGNORE_DIRS = "";
|
|
569
645
|
process.env.ASTGEN_IGNORE_FILE_PATTERN = "";
|
|
@@ -574,10 +650,12 @@ const applyAdvancedOptions = (options) => {
|
|
|
574
650
|
} else {
|
|
575
651
|
options.projectType = ["os"];
|
|
576
652
|
}
|
|
653
|
+
options.bomAudit = true;
|
|
577
654
|
break;
|
|
578
655
|
case "threat-modeling":
|
|
579
656
|
options.deep = true;
|
|
580
657
|
options.evidence = true;
|
|
658
|
+
options.bomAudit = true;
|
|
581
659
|
break;
|
|
582
660
|
case "license-compliance":
|
|
583
661
|
process.env.FETCH_LICENSE = "true";
|
|
@@ -588,6 +666,7 @@ const applyAdvancedOptions = (options) => {
|
|
|
588
666
|
options.evidence = false;
|
|
589
667
|
options.includeCrypto = false;
|
|
590
668
|
options.installDeps = false;
|
|
669
|
+
options.bomAudit = false;
|
|
591
670
|
break;
|
|
592
671
|
case "machine-learning":
|
|
593
672
|
case "ml":
|
|
@@ -604,6 +683,7 @@ const applyAdvancedOptions = (options) => {
|
|
|
604
683
|
options.evidence = true;
|
|
605
684
|
options.includeCrypto = true;
|
|
606
685
|
options.installDeps = !isSecureMode;
|
|
686
|
+
options.bomAudit = true;
|
|
607
687
|
break;
|
|
608
688
|
default:
|
|
609
689
|
break;
|
|
@@ -639,7 +719,7 @@ const applyAdvancedOptions = (options) => {
|
|
|
639
719
|
].includes(options.projectType[0])
|
|
640
720
|
) {
|
|
641
721
|
console.log(
|
|
642
|
-
"PREVIEW: post-build lifecycle SBOM generation is supported only for
|
|
722
|
+
"PREVIEW: post-build lifecycle SBOM generation is supported only for limited project types.",
|
|
643
723
|
);
|
|
644
724
|
process.exit(1);
|
|
645
725
|
}
|
|
@@ -669,10 +749,23 @@ const applyAdvancedOptions = (options) => {
|
|
|
669
749
|
"I must avoid any package installations and focus solely on the available artefacts, such as lock files.",
|
|
670
750
|
);
|
|
671
751
|
}
|
|
752
|
+
if (options.bomAudit) {
|
|
753
|
+
if (!options.includeFormulation) {
|
|
754
|
+
console.log(
|
|
755
|
+
"NOTE: Automatically collecting formulation information. The section may include sensitive data such as emails and secrets.\nPlease review the generated SBOM before distribution or LLM training.\n",
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
options.includeFormulation = true;
|
|
759
|
+
}
|
|
672
760
|
return options;
|
|
673
761
|
};
|
|
674
762
|
applyAdvancedOptions(options);
|
|
675
763
|
|
|
764
|
+
const envAuditFindings = auditEnvironment();
|
|
765
|
+
if (options.envAudit) {
|
|
766
|
+
displaySelfThreatModel(filePath, config, options, envAuditFindings);
|
|
767
|
+
}
|
|
768
|
+
|
|
676
769
|
/**
|
|
677
770
|
* Check for node >= 20 permissions
|
|
678
771
|
*
|
|
@@ -785,22 +878,6 @@ const checkPermissions = (filePath, options) => {
|
|
|
785
878
|
return false;
|
|
786
879
|
}
|
|
787
880
|
}
|
|
788
|
-
if (!process.permission.has("fs.write", process.env.ATOM_DB || ATOM_DB)) {
|
|
789
|
-
console.log(
|
|
790
|
-
`SECURE MODE: FileSystemWrite permission is required to create the output slices file. Please invoke cdxgen with the argument --allow-fs-write="${process.env.ATOM_DB || ATOM_DB}"`,
|
|
791
|
-
);
|
|
792
|
-
return false;
|
|
793
|
-
}
|
|
794
|
-
console.log(
|
|
795
|
-
"TIP: Invoke cdxgen with `--allow-addons` to allow the use of sqlite3 native addon. This addon is required for evidence mode.",
|
|
796
|
-
);
|
|
797
|
-
} else {
|
|
798
|
-
if (process.permission.has("fs.write", process.env.ATOM_DB || ATOM_DB)) {
|
|
799
|
-
console.log(
|
|
800
|
-
`SECURE MODE: FileSystemWrite permission is not required for the directory "${process.env.ATOM_DB || ATOM_DB}" in non-evidence mode. Consider removing the argument --allow-fs-write="${process.env.ATOM_DB || ATOM_DB}".`,
|
|
801
|
-
);
|
|
802
|
-
return false;
|
|
803
|
-
}
|
|
804
881
|
}
|
|
805
882
|
if (!process.permission.has("fs.write", getTmpDir())) {
|
|
806
883
|
console.log(
|
|
@@ -840,6 +917,20 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
840
917
|
(async () => {
|
|
841
918
|
// Display the sponsor banner
|
|
842
919
|
printSponsorBanner(options);
|
|
920
|
+
// Our quest to audit and check the SBOM generation environment to prevent our users from getting exploited
|
|
921
|
+
// during SBOM generation.
|
|
922
|
+
if (envAuditFindings?.length) {
|
|
923
|
+
for (const f of envAuditFindings) {
|
|
924
|
+
console.log(`SECURE MODE: ${f.variable}: ${f.message}`);
|
|
925
|
+
}
|
|
926
|
+
// Only abort in secure mode for high or critical findings; low/medium are informational.
|
|
927
|
+
if (
|
|
928
|
+
isSecureMode &&
|
|
929
|
+
envAuditFindings.some((f) => ["high", "critical"].includes(f.severity))
|
|
930
|
+
) {
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
843
934
|
// Start SBOM server
|
|
844
935
|
if (options.server) {
|
|
845
936
|
const serverModule = await import("../lib/server/server.js");
|
|
@@ -861,7 +952,38 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
861
952
|
);
|
|
862
953
|
}
|
|
863
954
|
// Add extra metadata and annotations with post processing
|
|
864
|
-
bomNSData = postProcess(bomNSData, options);
|
|
955
|
+
bomNSData = postProcess(bomNSData, options, filePath);
|
|
956
|
+
if (options.bomAudit && bomNSData?.bomJson) {
|
|
957
|
+
const {
|
|
958
|
+
auditBom,
|
|
959
|
+
formatAnnotations,
|
|
960
|
+
formatConsoleOutput,
|
|
961
|
+
hasCriticalFindings,
|
|
962
|
+
} = await import("../lib/stages/postgen/auditBom.js");
|
|
963
|
+
thoughtLog("Let's run security audit...");
|
|
964
|
+
const postAuditFindings = await auditBom(bomNSData.bomJson, options);
|
|
965
|
+
if (postAuditFindings.length) {
|
|
966
|
+
formatConsoleOutput(postAuditFindings);
|
|
967
|
+
} else if (DEBUG_MODE) {
|
|
968
|
+
console.log("BOM audit: No findings");
|
|
969
|
+
}
|
|
970
|
+
if (postAuditFindings.length && options.specVersion >= 1.4) {
|
|
971
|
+
bomNSData.bomJson.annotations = [
|
|
972
|
+
...(bomNSData.bomJson.annotations || []),
|
|
973
|
+
...formatAnnotations(postAuditFindings, bomNSData.bomJson),
|
|
974
|
+
];
|
|
975
|
+
thoughtLog(
|
|
976
|
+
`Embedded ${postAuditFindings.length} audit findings as CycloneDX annotations`,
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
if (isSecureMode && hasCriticalFindings(postAuditFindings, options)) {
|
|
980
|
+
console.error("\nSecure mode: Critical audit findings detected.");
|
|
981
|
+
console.error(
|
|
982
|
+
"Review findings above or adjust --bom-audit-fail-severity to proceed.",
|
|
983
|
+
);
|
|
984
|
+
process.exit(1);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
865
987
|
if (
|
|
866
988
|
options.output &&
|
|
867
989
|
(typeof options.output === "string" || options.output instanceof String)
|
|
@@ -960,71 +1082,41 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
960
1082
|
}
|
|
961
1083
|
}
|
|
962
1084
|
try {
|
|
963
|
-
// Sign the individual components
|
|
964
|
-
// Let's leave the services unsigned for now since it might require additional cleansing
|
|
965
1085
|
const bomJsonUnsignedObj = JSON.parse(jsonPayload);
|
|
966
|
-
|
|
967
|
-
const compSignature = jws.sign({
|
|
968
|
-
header: { alg },
|
|
969
|
-
payload: comp,
|
|
970
|
-
privateKey: privateKeyToUse,
|
|
971
|
-
});
|
|
972
|
-
const compSignatureBlock = {
|
|
973
|
-
algorithm: alg,
|
|
974
|
-
value: compSignature,
|
|
975
|
-
};
|
|
976
|
-
if (jwkPublicKey) {
|
|
977
|
-
compSignatureBlock.publicKey = jwkPublicKey;
|
|
978
|
-
}
|
|
979
|
-
comp.signature = compSignatureBlock;
|
|
980
|
-
}
|
|
981
|
-
const signature = jws.sign({
|
|
982
|
-
header: { alg },
|
|
983
|
-
payload: JSON.stringify(bomJsonUnsignedObj, null, 2),
|
|
1086
|
+
const signOptions = {
|
|
984
1087
|
privateKey: privateKeyToUse,
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
// Verifying this signature
|
|
1006
|
-
const signatureVerification = jws.verify(
|
|
1007
|
-
signature,
|
|
1088
|
+
algorithm: alg,
|
|
1089
|
+
publicKeyJwk: jwkPublicKey,
|
|
1090
|
+
mode: process.env.SBOM_SIGN_MODE || "replace",
|
|
1091
|
+
signComponents: true,
|
|
1092
|
+
signServices: true,
|
|
1093
|
+
signAnnotations: true,
|
|
1094
|
+
};
|
|
1095
|
+
thoughtLog(`Signing the BOM file "${jsonFile}".`);
|
|
1096
|
+
const signedBom = signBom(bomJsonUnsignedObj, signOptions);
|
|
1097
|
+
fs.writeFileSync(
|
|
1098
|
+
jsonFile,
|
|
1099
|
+
JSON.stringify(signedBom, null, options.jsonPretty ? 2 : null),
|
|
1100
|
+
);
|
|
1101
|
+
if (publicKeyFile) {
|
|
1102
|
+
const publicKeyStr = fs.readFileSync(publicKeyFile, "utf8");
|
|
1103
|
+
const signatureVerification = verifyBom(signedBom, publicKeyStr);
|
|
1104
|
+
if (signatureVerification) {
|
|
1105
|
+
console.log(
|
|
1106
|
+
"SBOM signature is verifiable natively with the public key and the algorithm",
|
|
1107
|
+
publicKeyFile,
|
|
1008
1108
|
alg,
|
|
1009
|
-
fs.readFileSync(publicKeyFile, "utf8"),
|
|
1010
1109
|
);
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
publicKeyFile,
|
|
1015
|
-
alg,
|
|
1016
|
-
);
|
|
1017
|
-
} else {
|
|
1018
|
-
console.log("SBOM signature verification was unsuccessful");
|
|
1019
|
-
console.log(
|
|
1020
|
-
"Check if the public key was exported in PEM format",
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1110
|
+
} else {
|
|
1111
|
+
console.log("SBOM signature verification was unsuccessful");
|
|
1112
|
+
console.log("Check if the public key was exported in PEM format");
|
|
1023
1113
|
}
|
|
1024
1114
|
}
|
|
1025
1115
|
} catch (ex) {
|
|
1026
|
-
console.log("SBOM signing was unsuccessful", ex);
|
|
1027
|
-
console.log(
|
|
1116
|
+
console.log("SBOM signing was unsuccessful:", ex.message);
|
|
1117
|
+
console.log(
|
|
1118
|
+
"Check if the private key was exported in PEM format and the algorithm is JSF-compliant.",
|
|
1119
|
+
);
|
|
1028
1120
|
}
|
|
1029
1121
|
}
|
|
1030
1122
|
}
|
|
@@ -1056,7 +1148,6 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1056
1148
|
input: options.output,
|
|
1057
1149
|
output: options.evinseOutput,
|
|
1058
1150
|
language: options.projectType,
|
|
1059
|
-
dbPath: process.env.ATOM_DB || ATOM_DB,
|
|
1060
1151
|
skipMavenCollector: false,
|
|
1061
1152
|
force: false,
|
|
1062
1153
|
withReachables: options.deep,
|
package/bin/evinse.js
CHANGED
|
@@ -17,8 +17,7 @@ import {
|
|
|
17
17
|
printReachables,
|
|
18
18
|
printServices,
|
|
19
19
|
} from "../lib/helpers/display.js";
|
|
20
|
-
import {
|
|
21
|
-
import { validateBom } from "../lib/helpers/validator.js";
|
|
20
|
+
import { validateBom } from "../lib/validator/bomValidator.js";
|
|
22
21
|
|
|
23
22
|
const args = yargs(hideBin(process.argv))
|
|
24
23
|
.env("EVINSE")
|
|
@@ -56,8 +55,9 @@ const args = yargs(hideBin(process.argv))
|
|
|
56
55
|
],
|
|
57
56
|
})
|
|
58
57
|
.option("db-path", {
|
|
59
|
-
description:
|
|
60
|
-
default:
|
|
58
|
+
description: "Atom slices DB path. Unused",
|
|
59
|
+
default: undefined,
|
|
60
|
+
hidden: true,
|
|
61
61
|
})
|
|
62
62
|
.option("force", {
|
|
63
63
|
description: "Force creation of the database",
|