@cyclonedx/cdxgen 12.1.5 → 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.
Files changed (181) hide show
  1. package/README.md +47 -39
  2. package/bin/cdxgen.js +175 -96
  3. package/bin/evinse.js +4 -4
  4. package/bin/repl.js +1 -1
  5. package/bin/sign.js +102 -0
  6. package/bin/validate.js +233 -0
  7. package/bin/verify.js +69 -28
  8. package/data/queries.json +1 -1
  9. package/data/rules/ci-permissions.yaml +186 -0
  10. package/data/rules/dependency-sources.yaml +123 -0
  11. package/data/rules/package-integrity.yaml +135 -0
  12. package/data/rules/vscode-extensions.yaml +228 -0
  13. package/lib/cli/index.js +327 -372
  14. package/lib/evinser/db.js +137 -0
  15. package/lib/{helpers → evinser}/db.poku.js +2 -6
  16. package/lib/evinser/evinser.js +2 -14
  17. package/lib/helpers/bomSigner.js +312 -0
  18. package/lib/helpers/bomSigner.poku.js +156 -0
  19. package/lib/helpers/ciParsers/azurePipelines.js +295 -0
  20. package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
  21. package/lib/helpers/ciParsers/circleCi.js +286 -0
  22. package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
  23. package/lib/helpers/ciParsers/common.js +24 -0
  24. package/lib/helpers/ciParsers/githubActions.js +636 -0
  25. package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
  26. package/lib/helpers/ciParsers/gitlabCi.js +213 -0
  27. package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
  28. package/lib/helpers/ciParsers/jenkins.js +181 -0
  29. package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
  30. package/lib/helpers/depsUtils.js +203 -0
  31. package/lib/helpers/depsUtils.poku.js +150 -0
  32. package/lib/helpers/display.js +423 -4
  33. package/lib/helpers/envcontext.js +18 -3
  34. package/lib/helpers/formulationParsers.js +351 -0
  35. package/lib/helpers/logger.js +14 -0
  36. package/lib/helpers/protobom.js +9 -9
  37. package/lib/helpers/pythonutils.js +9 -0
  38. package/lib/helpers/utils.js +681 -406
  39. package/lib/helpers/utils.poku.js +55 -255
  40. package/lib/helpers/versutils.js +202 -0
  41. package/lib/helpers/versutils.poku.js +315 -0
  42. package/lib/helpers/vsixutils.js +1061 -0
  43. package/lib/helpers/vsixutils.poku.js +2247 -0
  44. package/lib/managers/binary.js +19 -19
  45. package/lib/managers/docker.js +108 -1
  46. package/lib/managers/oci.js +10 -0
  47. package/lib/managers/piptree.js +3 -9
  48. package/lib/parsers/npmrc.js +17 -13
  49. package/lib/parsers/npmrc.poku.js +41 -5
  50. package/lib/server/openapi.yaml +1 -1
  51. package/lib/server/server.js +40 -11
  52. package/lib/server/server.poku.js +123 -144
  53. package/lib/stages/postgen/annotator.js +1 -1
  54. package/lib/stages/postgen/auditBom.js +197 -0
  55. package/lib/stages/postgen/auditBom.poku.js +378 -0
  56. package/lib/stages/postgen/postgen.js +54 -1
  57. package/lib/stages/postgen/postgen.poku.js +90 -1
  58. package/lib/stages/postgen/ruleEngine.js +369 -0
  59. package/lib/stages/pregen/envAudit.js +299 -0
  60. package/lib/stages/pregen/envAudit.poku.js +572 -0
  61. package/lib/stages/pregen/pregen.js +12 -8
  62. package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
  63. package/lib/validator/complianceEngine.js +241 -0
  64. package/lib/validator/complianceEngine.poku.js +168 -0
  65. package/lib/validator/complianceRules.js +1610 -0
  66. package/lib/validator/complianceRules.poku.js +328 -0
  67. package/lib/validator/index.js +222 -0
  68. package/lib/validator/index.poku.js +144 -0
  69. package/lib/validator/reporters/annotations.js +121 -0
  70. package/lib/validator/reporters/console.js +149 -0
  71. package/lib/validator/reporters/index.js +41 -0
  72. package/lib/validator/reporters/json.js +37 -0
  73. package/lib/validator/reporters/sarif.js +184 -0
  74. package/lib/validator/reporters.poku.js +150 -0
  75. package/package.json +8 -8
  76. package/types/bin/sign.d.ts +3 -0
  77. package/types/bin/sign.d.ts.map +1 -0
  78. package/types/bin/validate.d.ts +3 -0
  79. package/types/bin/validate.d.ts.map +1 -0
  80. package/types/helpers/utils.d.ts +0 -1
  81. package/types/lib/cli/index.d.ts +49 -52
  82. package/types/lib/cli/index.d.ts.map +1 -1
  83. package/types/lib/evinser/db.d.ts +34 -0
  84. package/types/lib/evinser/db.d.ts.map +1 -0
  85. package/types/lib/evinser/evinser.d.ts +63 -16
  86. package/types/lib/evinser/evinser.d.ts.map +1 -1
  87. package/types/lib/helpers/bomSigner.d.ts +27 -0
  88. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  89. package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
  90. package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
  91. package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
  92. package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
  93. package/types/lib/helpers/ciParsers/common.d.ts +11 -0
  94. package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
  95. package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
  96. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
  97. package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
  98. package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
  99. package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
  100. package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
  101. package/types/lib/helpers/depsUtils.d.ts +21 -0
  102. package/types/lib/helpers/depsUtils.d.ts.map +1 -0
  103. package/types/lib/helpers/display.d.ts +111 -11
  104. package/types/lib/helpers/display.d.ts.map +1 -1
  105. package/types/lib/helpers/envcontext.d.ts +19 -7
  106. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  107. package/types/lib/helpers/formulationParsers.d.ts +50 -0
  108. package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
  109. package/types/lib/helpers/logger.d.ts +15 -1
  110. package/types/lib/helpers/logger.d.ts.map +1 -1
  111. package/types/lib/helpers/protobom.d.ts +2 -2
  112. package/types/lib/helpers/pythonutils.d.ts +10 -1
  113. package/types/lib/helpers/pythonutils.d.ts.map +1 -1
  114. package/types/lib/helpers/utils.d.ts +532 -128
  115. package/types/lib/helpers/utils.d.ts.map +1 -1
  116. package/types/lib/helpers/versutils.d.ts +8 -0
  117. package/types/lib/helpers/versutils.d.ts.map +1 -0
  118. package/types/lib/helpers/vsixutils.d.ts +130 -0
  119. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  120. package/types/lib/managers/docker.d.ts +12 -31
  121. package/types/lib/managers/docker.d.ts.map +1 -1
  122. package/types/lib/managers/oci.d.ts +11 -1
  123. package/types/lib/managers/oci.d.ts.map +1 -1
  124. package/types/lib/managers/piptree.d.ts.map +1 -1
  125. package/types/lib/parsers/npmrc.d.ts +4 -1
  126. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  127. package/types/lib/server/server.d.ts +21 -2
  128. package/types/lib/server/server.d.ts.map +1 -1
  129. package/types/lib/stages/postgen/auditBom.d.ts +20 -0
  130. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  131. package/types/lib/stages/postgen/postgen.d.ts +8 -1
  132. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  133. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  134. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  135. package/types/lib/stages/pregen/envAudit.d.ts +8 -0
  136. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  137. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  138. package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
  139. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  140. package/types/lib/validator/complianceEngine.d.ts +66 -0
  141. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  142. package/types/lib/validator/complianceRules.d.ts +70 -0
  143. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  144. package/types/lib/validator/index.d.ts +70 -0
  145. package/types/lib/validator/index.d.ts.map +1 -0
  146. package/types/lib/validator/reporters/annotations.d.ts +31 -0
  147. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  148. package/types/lib/validator/reporters/console.d.ts +30 -0
  149. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  150. package/types/lib/validator/reporters/index.d.ts +21 -0
  151. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  152. package/types/lib/validator/reporters/json.d.ts +11 -0
  153. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  154. package/types/lib/validator/reporters/sarif.d.ts +16 -0
  155. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  156. package/lib/helpers/db.js +0 -162
  157. package/lib/stages/pregen/env-audit.js +0 -34
  158. package/lib/stages/pregen/env-audit.poku.js +0 -290
  159. package/types/helpers/db.d.ts +0 -35
  160. package/types/helpers/db.d.ts.map +0 -1
  161. package/types/lib/helpers/db.d.ts +0 -35
  162. package/types/lib/helpers/db.d.ts.map +0 -1
  163. package/types/lib/helpers/validator.d.ts.map +0 -1
  164. package/types/lib/stages/pregen/env-audit.d.ts +0 -2
  165. package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
  166. package/types/managers/binary.d.ts +0 -37
  167. package/types/managers/binary.d.ts.map +0 -1
  168. package/types/managers/docker.d.ts +0 -56
  169. package/types/managers/docker.d.ts.map +0 -1
  170. package/types/managers/oci.d.ts +0 -2
  171. package/types/managers/oci.d.ts.map +0 -1
  172. package/types/managers/piptree.d.ts +0 -2
  173. package/types/managers/piptree.d.ts.map +0 -1
  174. package/types/server/server.d.ts +0 -34
  175. package/types/server/server.d.ts.map +0 -1
  176. package/types/stages/postgen/annotator.d.ts +0 -27
  177. package/types/stages/postgen/annotator.d.ts.map +0 -1
  178. package/types/stages/postgen/postgen.d.ts +0 -51
  179. package/types/stages/postgen/postgen.d.ts.map +0 -1
  180. package/types/stages/pregen/pregen.d.ts +0 -59
  181. package/types/stages/pregen/pregen.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![SBOM](https://img.shields.io/badge/SBOM-with_%E2%9D%A4%EF%B8%8F_by_cdxgen-FF753D)](https://github.com/cdxgen/cdxgen)
2
+ [![AI-DECLARATION: pair](https://img.shields.io/badge/䷼%20AI--DECLARATION-pair-ffedd5?labelColor=ffedd5)](./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 a valid and compliant [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.4 - 1.7.
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.1.0";
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.6
157
- [number] [choices: 1.4, 1.5, 1.6, 1.7] [default: 1.6]
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.6. To generate BOM for a different specification version, such as 1.5 or 1.4, pass the version number using the `--spec-version` argument.
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.5 is supported by most tools
224
- cdxgen -r -o bom.json --spec-version 1.5
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 can sign the generated BOM json file to increase authenticity and non-repudiation capabilities. To enable this, set the following environment variables.
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
- - SBOM_SIGN_ALGORITHM: Algorithm. Example: RS512
426
- - SBOM_SIGN_PRIVATE_KEY: Location to the RSA private key
427
- - SBOM_SIGN_PUBLIC_KEY: Optional. Location to the RSA public key
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
- 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][jwt-homepage] is a known site that could be used for such signature validation.
449
+ ### Validating CycloneDX BOMs
430
450
 
431
- ![SBOM signing](./docs/_media/sbom-sign.jpg)
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, which supports verifying a single signature added at the bom level.
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
- Use the bundled `cdx-verify` command, which supports verifying a single signature added at the BOM level.
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
- ### Custom verification tool (Node.js example)
475
+ You can also use pnpm to invoke the signing tool:
453
476
 
454
- There are many [libraries][jwt-libraries] available to validate JSON Web Tokens. Below is a javascript example.
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 --allow-build @appthreat/sqlite3 https://github.com/cdxgen/cdxgen
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,11 +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/env-audit.js";
45
+ import { auditEnvironment } from "../lib/stages/pregen/envAudit.js";
46
46
  import { prepareEnv } from "../lib/stages/pregen/pregen.js";
47
+ import { validateBom } from "../lib/validator/bomValidator.js";
47
48
 
48
49
  // Support for config files
49
50
  const configPaths = [
@@ -64,6 +65,18 @@ for (const configPattern of configPaths) {
64
65
  } else {
65
66
  config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
66
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
+ }
67
80
  } catch (_e) {
68
81
  console.log("Invalid config file", configPath);
69
82
  }
@@ -76,6 +89,9 @@ const args = _yargs
76
89
  .parserConfiguration({
77
90
  "greedy-arrays": false,
78
91
  "short-option-groups": false,
92
+ "dot-notation": false,
93
+ "parse-numbers": true,
94
+ "boolean-negation": true,
79
95
  })
80
96
  .option("output", {
81
97
  alias: "o",
@@ -120,6 +136,7 @@ const args = _yargs
120
136
  })
121
137
  .option("server-url", {
122
138
  description: "Dependency track url. Eg: https://deptrack.cyclonedx.io",
139
+ type: "string",
123
140
  })
124
141
  .option("skip-dt-tls-check", {
125
142
  type: "boolean",
@@ -128,6 +145,7 @@ const args = _yargs
128
145
  })
129
146
  .option("api-key", {
130
147
  description: "Dependency track api key",
148
+ type: "string",
131
149
  })
132
150
  .option("project-group", {
133
151
  description: "Dependency track project group",
@@ -180,10 +198,12 @@ const args = _yargs
180
198
  .option("server-host", {
181
199
  description: "Listen address",
182
200
  default: "127.0.0.1",
201
+ type: "string",
183
202
  })
184
203
  .option("server-port", {
185
204
  description: "Listen port",
186
- default: "9090",
205
+ default: 9090,
206
+ type: "number",
187
207
  })
188
208
  .option("install-deps", {
189
209
  type: "boolean",
@@ -230,7 +250,7 @@ const args = _yargs
230
250
  })
231
251
  .option("spec-version", {
232
252
  description: "CycloneDX Specification version to use. Defaults to 1.6",
233
- default: 1.6,
253
+ default: 1.7,
234
254
  type: "number",
235
255
  choices: [1.4, 1.5, 1.6, 1.7],
236
256
  })
@@ -355,6 +375,46 @@ const args = _yargs
355
375
  default: "CLEAR",
356
376
  hidden: true,
357
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
+ })
358
418
  .completion("completion", "Generate bash/zsh completion")
359
419
  .array("type")
360
420
  .array("excludeType")
@@ -520,7 +580,7 @@ if (["cbom", "saasbom"].includes(process.argv[1])) {
520
580
  }
521
581
  }
522
582
  options.evidence = true;
523
- options.specVersion = 1.6;
583
+ options.specVersion = 1.7;
524
584
  options.deep = true;
525
585
  }
526
586
  if (process.argv[1].includes("cdxgen-secure")) {
@@ -534,16 +594,29 @@ if (process.argv[1].includes("cdxgen-secure")) {
534
594
  process.env.CDXGEN_SECURE_MODE = true;
535
595
  }
536
596
  if (options.standard) {
537
- options.specVersion = 1.6;
597
+ options.specVersion = 1.7;
538
598
  }
539
599
  if (options.includeFormulation) {
540
- thoughtLog(
541
- "Wait, the user wants to include formulation information. Let's warn about accidentally disclosing sensitive data via the BOM files.",
542
- );
543
- console.log(
544
- "NOTE: Formulation section could include sensitive data such as emails and secrets.\nPlease review the generated SBOM before distribution.\n",
545
- );
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
+ }
546
618
  }
619
+
547
620
  /**
548
621
  * Method to apply advanced options such as profile and lifecycles
549
622
  *
@@ -560,11 +633,13 @@ const applyAdvancedOptions = (options) => {
560
633
  switch (options.profile) {
561
634
  case "appsec":
562
635
  options.deep = true;
636
+ options.bomAudit = true;
563
637
  break;
564
638
  case "research":
565
639
  options.deep = true;
566
640
  options.evidence = true;
567
641
  options.includeCrypto = true;
642
+ options.bomAudit = true;
568
643
  process.env.CDX_MAVEN_INCLUDE_TEST_SCOPE = "true";
569
644
  process.env.ASTGEN_IGNORE_DIRS = "";
570
645
  process.env.ASTGEN_IGNORE_FILE_PATTERN = "";
@@ -575,10 +650,12 @@ const applyAdvancedOptions = (options) => {
575
650
  } else {
576
651
  options.projectType = ["os"];
577
652
  }
653
+ options.bomAudit = true;
578
654
  break;
579
655
  case "threat-modeling":
580
656
  options.deep = true;
581
657
  options.evidence = true;
658
+ options.bomAudit = true;
582
659
  break;
583
660
  case "license-compliance":
584
661
  process.env.FETCH_LICENSE = "true";
@@ -589,6 +666,7 @@ const applyAdvancedOptions = (options) => {
589
666
  options.evidence = false;
590
667
  options.includeCrypto = false;
591
668
  options.installDeps = false;
669
+ options.bomAudit = false;
592
670
  break;
593
671
  case "machine-learning":
594
672
  case "ml":
@@ -605,6 +683,7 @@ const applyAdvancedOptions = (options) => {
605
683
  options.evidence = true;
606
684
  options.includeCrypto = true;
607
685
  options.installDeps = !isSecureMode;
686
+ options.bomAudit = true;
608
687
  break;
609
688
  default:
610
689
  break;
@@ -640,7 +719,7 @@ const applyAdvancedOptions = (options) => {
640
719
  ].includes(options.projectType[0])
641
720
  ) {
642
721
  console.log(
643
- "PREVIEW: post-build lifecycle SBOM generation is supported only for android, dotnet, go, and Rust projects. Please specify the type using the -t argument.",
722
+ "PREVIEW: post-build lifecycle SBOM generation is supported only for limited project types.",
644
723
  );
645
724
  process.exit(1);
646
725
  }
@@ -670,10 +749,23 @@ const applyAdvancedOptions = (options) => {
670
749
  "I must avoid any package installations and focus solely on the available artefacts, such as lock files.",
671
750
  );
672
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
+ }
673
760
  return options;
674
761
  };
675
762
  applyAdvancedOptions(options);
676
763
 
764
+ const envAuditFindings = auditEnvironment();
765
+ if (options.envAudit) {
766
+ displaySelfThreatModel(filePath, config, options, envAuditFindings);
767
+ }
768
+
677
769
  /**
678
770
  * Check for node >= 20 permissions
679
771
  *
@@ -786,22 +878,6 @@ const checkPermissions = (filePath, options) => {
786
878
  return false;
787
879
  }
788
880
  }
789
- if (!process.permission.has("fs.write", process.env.ATOM_DB || ATOM_DB)) {
790
- console.log(
791
- `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}"`,
792
- );
793
- return false;
794
- }
795
- console.log(
796
- "TIP: Invoke cdxgen with `--allow-addons` to allow the use of sqlite3 native addon. This addon is required for evidence mode.",
797
- );
798
- } else {
799
- if (process.permission.has("fs.write", process.env.ATOM_DB || ATOM_DB)) {
800
- console.log(
801
- `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}".`,
802
- );
803
- return false;
804
- }
805
881
  }
806
882
  if (!process.permission.has("fs.write", getTmpDir())) {
807
883
  console.log(
@@ -843,12 +919,15 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
843
919
  printSponsorBanner(options);
844
920
  // Our quest to audit and check the SBOM generation environment to prevent our users from getting exploited
845
921
  // during SBOM generation.
846
- const envWarnings = auditEnvironment();
847
- if (envWarnings?.length) {
848
- for (const w of envWarnings) {
849
- console.log(`SECURE MODE: ${w}`);
922
+ if (envAuditFindings?.length) {
923
+ for (const f of envAuditFindings) {
924
+ console.log(`SECURE MODE: ${f.variable}: ${f.message}`);
850
925
  }
851
- if (isSecureMode) {
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
+ ) {
852
931
  process.exit(1);
853
932
  }
854
933
  }
@@ -873,7 +952,38 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
873
952
  );
874
953
  }
875
954
  // Add extra metadata and annotations with post processing
876
- 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
+ }
877
987
  if (
878
988
  options.output &&
879
989
  (typeof options.output === "string" || options.output instanceof String)
@@ -972,71 +1082,41 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
972
1082
  }
973
1083
  }
974
1084
  try {
975
- // Sign the individual components
976
- // Let's leave the services unsigned for now since it might require additional cleansing
977
1085
  const bomJsonUnsignedObj = JSON.parse(jsonPayload);
978
- for (const comp of bomJsonUnsignedObj.components) {
979
- const compSignature = jws.sign({
980
- header: { alg },
981
- payload: comp,
982
- privateKey: privateKeyToUse,
983
- });
984
- const compSignatureBlock = {
985
- algorithm: alg,
986
- value: compSignature,
987
- };
988
- if (jwkPublicKey) {
989
- compSignatureBlock.publicKey = jwkPublicKey;
990
- }
991
- comp.signature = compSignatureBlock;
992
- }
993
- const signature = jws.sign({
994
- header: { alg },
995
- payload: JSON.stringify(bomJsonUnsignedObj, null, 2),
1086
+ const signOptions = {
996
1087
  privateKey: privateKeyToUse,
997
- });
998
- if (signature) {
999
- const signatureBlock = {
1000
- algorithm: alg,
1001
- value: signature,
1002
- };
1003
- if (jwkPublicKey) {
1004
- signatureBlock.publicKey = jwkPublicKey;
1005
- }
1006
- bomJsonUnsignedObj.signature = signatureBlock;
1007
- fs.writeFileSync(
1008
- jsonFile,
1009
- JSON.stringify(
1010
- bomJsonUnsignedObj,
1011
- null,
1012
- options.jsonPretty ? 2 : null,
1013
- ),
1014
- );
1015
- thoughtLog(`Signing the BOM file "${jsonFile}".`);
1016
- if (publicKeyFile) {
1017
- // Verifying this signature
1018
- const signatureVerification = jws.verify(
1019
- 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,
1020
1108
  alg,
1021
- fs.readFileSync(publicKeyFile, "utf8"),
1022
1109
  );
1023
- if (signatureVerification) {
1024
- console.log(
1025
- "SBOM signature is verifiable with the public key and the algorithm",
1026
- publicKeyFile,
1027
- alg,
1028
- );
1029
- } else {
1030
- console.log("SBOM signature verification was unsuccessful");
1031
- console.log(
1032
- "Check if the public key was exported in PEM format",
1033
- );
1034
- }
1110
+ } else {
1111
+ console.log("SBOM signature verification was unsuccessful");
1112
+ console.log("Check if the public key was exported in PEM format");
1035
1113
  }
1036
1114
  }
1037
1115
  } catch (ex) {
1038
- console.log("SBOM signing was unsuccessful", ex);
1039
- console.log("Check if the private key was exported in PEM format");
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
+ );
1040
1120
  }
1041
1121
  }
1042
1122
  }
@@ -1068,7 +1148,6 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
1068
1148
  input: options.output,
1069
1149
  output: options.evinseOutput,
1070
1150
  language: options.projectType,
1071
- dbPath: process.env.ATOM_DB || ATOM_DB,
1072
1151
  skipMavenCollector: false,
1073
1152
  force: false,
1074
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 { ATOM_DB } from "../lib/helpers/utils.js";
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: `Atom slices DB path. Default ${ATOM_DB}`,
60
- default: process.env.ATOM_DB || ATOM_DB,
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",
package/bin/repl.js CHANGED
@@ -22,8 +22,8 @@ import {
22
22
  } from "../lib/helpers/display.js";
23
23
  import { readBinary } from "../lib/helpers/protobom.js";
24
24
  import { getTmpDir } from "../lib/helpers/utils.js";
25
- import { validateBom } from "../lib/helpers/validator.js";
26
25
  import { getBomWithOras } from "../lib/managers/oci.js";
26
+ import { validateBom } from "../lib/validator/bomValidator.js";
27
27
 
28
28
  const options = {
29
29
  useColors: true,