@cyclonedx/cdxgen 12.3.3 → 12.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +64 -22
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +238 -116
  4. package/bin/convert.js +28 -13
  5. package/bin/hbom.js +490 -0
  6. package/bin/repl.js +580 -29
  7. package/bin/validate.js +34 -4
  8. package/bin/verify.js +40 -5
  9. package/data/README.md +298 -25
  10. package/data/component-tags.json +6 -0
  11. package/data/crypto-oid.json +16 -0
  12. package/data/predictive-audit-allowlist.json +11 -0
  13. package/data/queries-darwin.json +12 -1
  14. package/data/queries-win.json +7 -1
  15. package/data/queries.json +39 -2
  16. package/data/rules/ai-agent-governance.yaml +16 -0
  17. package/data/rules/asar-archives.yaml +150 -0
  18. package/data/rules/chrome-extensions.yaml +8 -0
  19. package/data/rules/ci-permissions.yaml +42 -18
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +11 -0
  22. package/data/rules/hbom-compliance.yaml +325 -0
  23. package/data/rules/hbom-performance.yaml +307 -0
  24. package/data/rules/hbom-security.yaml +248 -0
  25. package/data/rules/host-topology.yaml +165 -0
  26. package/data/rules/mcp-servers.yaml +18 -3
  27. package/data/rules/obom-runtime.yaml +907 -22
  28. package/data/rules/package-integrity.yaml +14 -0
  29. package/data/rules/rootfs-hardening.yaml +179 -0
  30. package/data/rules/vscode-extensions.yaml +9 -0
  31. package/lib/audit/index.js +209 -8
  32. package/lib/audit/index.poku.js +332 -0
  33. package/lib/audit/reporters.js +222 -0
  34. package/lib/audit/targets.js +146 -1
  35. package/lib/audit/targets.poku.js +186 -0
  36. package/lib/cli/asar.poku.js +328 -0
  37. package/lib/cli/index.js +506 -88
  38. package/lib/cli/index.poku.js +1352 -212
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/analyzer.js +1406 -29
  41. package/lib/helpers/analyzer.poku.js +342 -0
  42. package/lib/helpers/analyzerScope.js +712 -0
  43. package/lib/helpers/asarutils.js +1556 -0
  44. package/lib/helpers/asarutils.poku.js +443 -0
  45. package/lib/helpers/auditCategories.js +12 -0
  46. package/lib/helpers/auditCategories.poku.js +32 -0
  47. package/lib/helpers/cbomutils.js +271 -1
  48. package/lib/helpers/cbomutils.poku.js +248 -5
  49. package/lib/helpers/display.js +291 -1
  50. package/lib/helpers/display.poku.js +149 -0
  51. package/lib/helpers/evidenceUtils.js +58 -0
  52. package/lib/helpers/evidenceUtils.poku.js +54 -0
  53. package/lib/helpers/exportUtils.js +9 -0
  54. package/lib/helpers/gtfobins.js +142 -8
  55. package/lib/helpers/gtfobins.poku.js +24 -1
  56. package/lib/helpers/hbom.js +710 -0
  57. package/lib/helpers/hbom.poku.js +496 -0
  58. package/lib/helpers/hbomAnalysis.js +268 -0
  59. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  60. package/lib/helpers/hbomLoader.js +35 -0
  61. package/lib/helpers/hostTopology.js +803 -0
  62. package/lib/helpers/hostTopology.poku.js +363 -0
  63. package/lib/helpers/inventoryStats.js +69 -0
  64. package/lib/helpers/inventoryStats.poku.js +86 -0
  65. package/lib/helpers/lolbas.js +19 -1
  66. package/lib/helpers/lolbas.poku.js +23 -0
  67. package/lib/helpers/osqueryTransform.js +47 -0
  68. package/lib/helpers/osqueryTransform.poku.js +47 -0
  69. package/lib/helpers/plugins.js +349 -0
  70. package/lib/helpers/plugins.poku.js +57 -0
  71. package/lib/helpers/protobom.js +156 -45
  72. package/lib/helpers/protobom.poku.js +140 -5
  73. package/lib/helpers/remote/dependency-track.js +36 -3
  74. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  75. package/lib/helpers/source.js +24 -0
  76. package/lib/helpers/source.poku.js +32 -0
  77. package/lib/helpers/utils.js +1438 -93
  78. package/lib/helpers/utils.poku.js +846 -4
  79. package/lib/managers/binary.e2e.poku.js +367 -0
  80. package/lib/managers/binary.js +2293 -353
  81. package/lib/managers/binary.poku.js +1699 -1
  82. package/lib/managers/docker.js +201 -79
  83. package/lib/managers/docker.poku.js +337 -12
  84. package/lib/server/server.js +2 -27
  85. package/lib/stages/postgen/annotator.js +38 -0
  86. package/lib/stages/postgen/annotator.poku.js +107 -1
  87. package/lib/stages/postgen/auditBom.js +121 -18
  88. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  89. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  90. package/lib/stages/postgen/postgen.js +192 -1
  91. package/lib/stages/postgen/postgen.poku.js +321 -0
  92. package/lib/stages/postgen/ruleEngine.js +116 -0
  93. package/lib/stages/pregen/envAudit.js +14 -3
  94. package/package.json +23 -21
  95. package/types/bin/hbom.d.ts +3 -0
  96. package/types/bin/hbom.d.ts.map +1 -0
  97. package/types/bin/repl.d.ts.map +1 -1
  98. package/types/lib/audit/index.d.ts +44 -0
  99. package/types/lib/audit/index.d.ts.map +1 -1
  100. package/types/lib/audit/reporters.d.ts +16 -0
  101. package/types/lib/audit/reporters.d.ts.map +1 -1
  102. package/types/lib/audit/targets.d.ts.map +1 -1
  103. package/types/lib/cli/index.d.ts +16 -0
  104. package/types/lib/cli/index.d.ts.map +1 -1
  105. package/types/lib/evinser/evinser.d.ts +4 -0
  106. package/types/lib/evinser/evinser.d.ts.map +1 -1
  107. package/types/lib/helpers/analyzer.d.ts +33 -0
  108. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  109. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  110. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  111. package/types/lib/helpers/asarutils.d.ts +34 -0
  112. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  113. package/types/lib/helpers/auditCategories.d.ts +5 -0
  114. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  115. package/types/lib/helpers/cbomutils.d.ts +3 -2
  116. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  117. package/types/lib/helpers/display.d.ts.map +1 -1
  118. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  119. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  120. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  121. package/types/lib/helpers/gtfobins.d.ts +8 -0
  122. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  123. package/types/lib/helpers/hbom.d.ts +49 -0
  124. package/types/lib/helpers/hbom.d.ts.map +1 -0
  125. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  126. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  127. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  128. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  129. package/types/lib/helpers/hostTopology.d.ts +12 -0
  130. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  131. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  132. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  133. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  134. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  135. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  136. package/types/lib/helpers/plugins.d.ts +58 -0
  137. package/types/lib/helpers/plugins.d.ts.map +1 -0
  138. package/types/lib/helpers/protobom.d.ts +3 -4
  139. package/types/lib/helpers/protobom.d.ts.map +1 -1
  140. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  141. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  142. package/types/lib/helpers/source.d.ts.map +1 -1
  143. package/types/lib/helpers/utils.d.ts +45 -8
  144. package/types/lib/helpers/utils.d.ts.map +1 -1
  145. package/types/lib/managers/binary.d.ts +5 -0
  146. package/types/lib/managers/binary.d.ts.map +1 -1
  147. package/types/lib/managers/docker.d.ts.map +1 -1
  148. package/types/lib/server/server.d.ts +2 -1
  149. package/types/lib/server/server.d.ts.map +1 -1
  150. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  151. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  152. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  153. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  154. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  155. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  156. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  157. package/data/spdx-model-v3.0.1.jsonld +0 -15999
@@ -7,6 +7,7 @@
7
7
  description: "npm packages with lifecycle hooks (preinstall, postinstall, etc.) execute arbitrary code during installation"
8
8
  severity: medium
9
9
  category: package-integrity
10
+ dry-run-support: full
10
11
  condition: |
11
12
  components[
12
13
  $prop($, 'cdx:npm:hasInstallScript') = 'true'
@@ -26,6 +27,7 @@
26
27
  description: "Detected mismatch between expected and resolved package name or version, which may indicate dependency confusion or tampering"
27
28
  severity: high
28
29
  category: package-integrity
30
+ dry-run-support: full
29
31
  condition: |
30
32
  components[
31
33
  $hasProp($, 'cdx:npm:nameMismatchError')
@@ -46,6 +48,7 @@
46
48
  description: "Go modules marked as deprecated may contain known issues or be abandoned"
47
49
  severity: medium
48
50
  category: package-integrity
51
+ dry-run-support: no
49
52
  condition: |
50
53
  components[
51
54
  $hasProp($, 'cdx:go:deprecated')
@@ -65,6 +68,7 @@
65
68
  description: "Yanked gems have been removed from RubyGems, typically due to security issues or critical bugs"
66
69
  severity: high
67
70
  category: package-integrity
71
+ dry-run-support: no
68
72
  condition: |
69
73
  components[
70
74
  $prop($, 'cdx:gem:yanked') = 'true'
@@ -84,6 +88,7 @@
84
88
  description: "npm packages marked as deprecated may have known vulnerabilities or unmaintained code"
85
89
  severity: low
86
90
  category: package-integrity
91
+ dry-run-support: partial
87
92
  condition: |
88
93
  components[
89
94
  $prop($, 'cdx:npm:deprecated') = 'true'
@@ -102,6 +107,7 @@
102
107
  description: "Dart packages from non-default registries may introduce unvetted code"
103
108
  severity: low
104
109
  category: package-integrity
110
+ dry-run-support: full
105
111
  condition: |
106
112
  components[
107
113
  $hasProp($, 'cdx:pub:registry')
@@ -120,6 +126,7 @@
120
126
  description: "Maven packages with shaded or relocated classes may obscure the true dependency graph and introduce hidden vulnerabilities"
121
127
  severity: low
122
128
  category: package-integrity
129
+ dry-run-support: full
123
130
  condition: |
124
131
  components[
125
132
  $prop($, 'cdx:maven:shaded') = 'true'
@@ -139,6 +146,7 @@
139
146
  description: "Hidden Unicode in README files can conceal malicious or misleading content in code review, especially inside comments"
140
147
  severity: medium
141
148
  category: package-integrity
149
+ dry-run-support: full
142
150
  condition: |
143
151
  formulation.components[
144
152
  $prop($, 'cdx:file:kind') = 'readme'
@@ -163,6 +171,7 @@
163
171
  description: "Lifecycle hooks that decode base64 payloads, hide long encoded blobs, or blend obfuscation with install-time execution are high-confidence supply-chain abuse indicators"
164
172
  severity: critical
165
173
  category: package-integrity
174
+ dry-run-support: full
166
175
  attack:
167
176
  tactics: [TA0005]
168
177
  techniques: [T1027]
@@ -187,6 +196,7 @@
187
196
  description: "Crates yanked from crates.io are often removed because of security, correctness, or ecosystem breakage issues"
188
197
  severity: high
189
198
  category: package-integrity
199
+ dry-run-support: no
190
200
  condition: |
191
201
  components[
192
202
  $prop($, 'cdx:cargo:yanked') = 'true'
@@ -208,6 +218,7 @@
208
218
  description: "Cargo build scripts and native build helpers expand execution surface during build and deserve additional review before release"
209
219
  severity: medium
210
220
  category: package-integrity
221
+ dry-run-support: full
211
222
  condition: |
212
223
  formulation.components[
213
224
  $prop($, 'cdx:rust:buildTool') = 'cargo'
@@ -232,6 +243,7 @@
232
243
  description: "Native Cargo build surfaces deserve additional scrutiny when the workflow relies on mutable Cargo toolchain setup actions instead of immutable SHA-pinned references"
233
244
  severity: medium
234
245
  category: package-integrity
246
+ dry-run-support: full
235
247
  condition: |
236
248
  formulation.components[
237
249
  $prop($, 'cdx:rust:buildTool') = 'cargo'
@@ -265,6 +277,7 @@
265
277
  description: "Cargo workflows that execute build/test/package/publish steps against native build surfaces increase the impact of build-script or native-helper changes"
266
278
  severity: medium
267
279
  category: package-integrity
280
+ dry-run-support: full
268
281
  condition: |
269
282
  formulation.components[
270
283
  $prop($, 'cdx:rust:buildTool') = 'cargo'
@@ -311,6 +324,7 @@
311
324
  description: "Collider lock entries should carry a SHA-256 wrap_hash so the selected wrap file remains integrity-pinned and reproducible"
312
325
  severity: high
313
326
  category: package-integrity
327
+ dry-run-support: full
314
328
  condition: |
315
329
  components[
316
330
  $hasProp($, 'cdx:collider:dependencyKind')
@@ -0,0 +1,179 @@
1
+ # Rootfs and Offline Host Hardening Rules
2
+ # Category: rootfs-hardening
3
+ # Evaluates repository trust, trust anchors, privileged helpers, and service execution paths
4
+
5
+ - id: RFS-001
6
+ name: "Enabled OS repository uses plaintext HTTP transport"
7
+ description: "Plain HTTP package repositories weaken integrity guarantees and make mirror tampering easier when network controls are absent."
8
+ severity: high
9
+ category: rootfs-hardening
10
+ dry-run-support: full
11
+ condition: |
12
+ components[
13
+ type = 'data'
14
+ and $hasProp($, 'cdx:os:repo:type')
15
+ and $safeStr($prop($, 'cdx:os:repo:enabled')) = 'true'
16
+ and $startsWith($lowercase($safeStr($prop($, 'cdx:os:repo:url'))), 'http://')
17
+ ]
18
+ location: |
19
+ {
20
+ "bomRef": $."bom-ref",
21
+ "purl": purl
22
+ }
23
+ message: "Repository '{{ name }}' is enabled and still uses plaintext HTTP: {{ $prop($, 'cdx:os:repo:url') }}"
24
+ mitigation: "Move package sources to HTTPS or a trusted local mirror protected by authenticated transport and repository signing."
25
+ evidence: |
26
+ {
27
+ "sourceFile": $prop($, 'SrcFile'),
28
+ "repoType": $prop($, 'cdx:os:repo:type'),
29
+ "repoUrl": $prop($, 'cdx:os:repo:url')
30
+ }
31
+
32
+ - id: RFS-002
33
+ name: "YUM repository has package signature checks disabled"
34
+ description: "Disabling gpgcheck in a configured YUM repository removes a core package authenticity control."
35
+ severity: critical
36
+ category: rootfs-hardening
37
+ dry-run-support: full
38
+ condition: |
39
+ components[
40
+ type = 'data'
41
+ and $safeStr($prop($, 'cdx:os:repo:type')) = 'yum-repository'
42
+ and $safeStr($prop($, 'cdx:os:repo:enabled')) = 'true'
43
+ and $safeStr($prop($, 'cdx:os:repo:gpgcheck')) = '0'
44
+ ]
45
+ location: |
46
+ {
47
+ "bomRef": $."bom-ref",
48
+ "purl": purl
49
+ }
50
+ message: "YUM repository '{{ name }}' is enabled with gpgcheck disabled"
51
+ mitigation: "Enable gpgcheck, pin the expected signing keys, and review recent repository changes before continuing to trust packages from this source."
52
+ evidence: |
53
+ {
54
+ "sourceFile": $prop($, 'SrcFile'),
55
+ "repoUrl": $prop($, 'cdx:os:repo:url'),
56
+ "gpgcheck": $prop($, 'cdx:os:repo:gpgcheck'),
57
+ "gpgkey": $prop($, 'cdx:os:repo:gpgkey')
58
+ }
59
+
60
+ - id: RFS-003
61
+ name: "Offline trust anchor is marked expired"
62
+ description: "Expired trust anchors inside an image or rootfs can break planned rotations and hide stale trust relationships."
63
+ severity: high
64
+ category: rootfs-hardening
65
+ dry-run-support: full
66
+ condition: |
67
+ components[
68
+ type = 'cryptographic-asset'
69
+ and $safeStr(cryptoProperties.assetType) = 'related-crypto-material'
70
+ and $safeStr(cryptoProperties.relatedCryptoMaterialProperties.state) = 'expired'
71
+ ]
72
+ location: |
73
+ {
74
+ "bomRef": $."bom-ref"
75
+ }
76
+ message: "Trust anchor '{{ name }}' is marked expired and should be reviewed"
77
+ mitigation: "Remove stale keys, replace expired trust anchors, and confirm the remaining repository trust chain matches approved policy."
78
+ evidence: |
79
+ {
80
+ "path": $prop($, 'SrcFile'),
81
+ "trustDomain": $prop($, 'cdx:crypto:trustDomain'),
82
+ "keyId": $prop($, 'cdx:crypto:keyId'),
83
+ "expiresAt": $prop($, 'cdx:crypto:expiresAt')
84
+ }
85
+
86
+ - id: RFS-004
87
+ name: "Offline image retains setuid GTFOBins execution helper"
88
+ description: "Setuid GTFOBins-capable binaries increase privilege-escalation exposure inside rootfs and golden image baselines."
89
+ severity: critical
90
+ category: rootfs-hardening
91
+ dry-run-support: full
92
+ condition: |
93
+ components[
94
+ type = 'file'
95
+ and (
96
+ $prop($, 'internal:has_setuid') = 'true'
97
+ or $prop($, 'internal:has_setgid') = 'true'
98
+ )
99
+ and $prop($, 'cdx:gtfobins:matched') = 'true'
100
+ and (
101
+ $listContains($prop($, 'cdx:gtfobins:functions'), 'shell')
102
+ or $listContains($prop($, 'cdx:gtfobins:functions'), 'command')
103
+ or $listContains($prop($, 'cdx:gtfobins:functions'), 'library-load')
104
+ )
105
+ ]
106
+ location: |
107
+ {
108
+ "bomRef": $."bom-ref",
109
+ "purl": purl
110
+ }
111
+ message: "Privileged helper '{{ name }}' keeps GTFOBins execution capability in the offline image"
112
+ mitigation: "Remove unnecessary setuid or setgid bits, replace the helper with a safer alternative, or justify and baseline the exposure explicitly."
113
+ evidence: |
114
+ {
115
+ "path": $prop($, 'SrcFile'),
116
+ "functions": $prop($, 'cdx:gtfobins:functions'),
117
+ "contexts": $prop($, 'cdx:gtfobins:contexts'),
118
+ "riskTags": $prop($, 'cdx:gtfobins:riskTags')
119
+ }
120
+
121
+ - id: RFS-005
122
+ name: "Offline service executes from writable or temporary path"
123
+ description: "Services pointing at writable or temporary paths are a strong persistence and drift signal in base images and offline hosts."
124
+ severity: critical
125
+ category: rootfs-hardening
126
+ dry-run-support: full
127
+ condition: |
128
+ $auditServices($)[
129
+ $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStart'))), '/tmp/')
130
+ or $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStart'))), '/var/tmp/')
131
+ or $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStart'))), '/dev/shm/')
132
+ or $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStart'))), '/home/')
133
+ or $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStart'))), '/run/user/')
134
+ ]
135
+ location: |
136
+ {
137
+ "bomRef": $."bom-ref"
138
+ }
139
+ message: "Service '{{ name }}' launches from a writable or temporary path"
140
+ mitigation: "Relocate approved service binaries into trusted system paths and investigate any service definition that executes from writable directories."
141
+ evidence: |
142
+ {
143
+ "sourceFile": $prop($, 'SrcFile'),
144
+ "manager": $prop($, 'cdx:service:manager'),
145
+ "execStart": $prop($, 'cdx:service:ExecStart'),
146
+ "packageRef": $prop($, 'cdx:service:packageRef')
147
+ }
148
+
149
+ - id: RFS-006
150
+ name: "Offline service pre-start step fetches or shells remote content"
151
+ description: "ExecStartPre hooks that fetch remote content or shell inline commands add bootstrap drift and weaken image reproducibility."
152
+ severity: high
153
+ category: rootfs-hardening
154
+ dry-run-support: full
155
+ condition: |
156
+ $auditServices($)[
157
+ (
158
+ $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStartPre'))), 'curl ')
159
+ or $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStartPre'))), 'wget ')
160
+ )
161
+ and (
162
+ $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStartPre'))), 'http://')
163
+ or $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStartPre'))), 'https://')
164
+ or $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStartPre'))), '| sh')
165
+ or $contains($lowercase($safeStr($prop($, 'cdx:service:ExecStartPre'))), '| bash')
166
+ )
167
+ ]
168
+ location: |
169
+ {
170
+ "bomRef": $."bom-ref"
171
+ }
172
+ message: "Service '{{ name }}' uses a remote-fetching ExecStartPre hook: {{ $prop($, 'cdx:service:ExecStartPre') }}"
173
+ mitigation: "Move bootstrap downloads into the image build pipeline, pin artifacts, and keep service startup paths deterministic and locally sourced."
174
+ evidence: |
175
+ {
176
+ "sourceFile": $prop($, 'SrcFile'),
177
+ "manager": $prop($, 'cdx:service:manager'),
178
+ "execStartPre": $prop($, 'cdx:service:ExecStartPre')
179
+ }
@@ -7,6 +7,7 @@
7
7
  description: "VS Code extensions with postinstall/preinstall scripts execute arbitrary code during installation"
8
8
  severity: critical
9
9
  category: vscode-extension
10
+ dry-run-support: full
10
11
  condition: |
11
12
  components[
12
13
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -32,6 +33,7 @@
32
33
  description: "Extensions activating on '*' with terminal-access contribution can execute commands in any workspace"
33
34
  severity: high
34
35
  category: vscode-extension
36
+ dry-run-support: full
35
37
  condition: |
36
38
  components[
37
39
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -57,6 +59,7 @@
57
59
  description: "Extensions supporting untrustedWorkspaces=true with filesystem-provider contributions may access sensitive files"
58
60
  severity: high
59
61
  category: vscode-extension
62
+ dry-run-support: full
60
63
  condition: |
61
64
  components[
62
65
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -82,6 +85,7 @@
82
85
  description: "Extension packs implicitly install bundled extensions; flag packs containing members with lifecycle scripts or privileged capabilities"
83
86
  severity: medium
84
87
  category: vscode-extension
88
+ dry-run-support: full
85
89
  condition: |
86
90
  components[
87
91
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -105,6 +109,7 @@
105
109
  description: "Extensions depending on other extensions inherit their trust boundary; flag chains where dependencies have install scripts or privileged access"
106
110
  severity: medium
107
111
  category: vscode-extension
112
+ dry-run-support: full
108
113
  condition: |
109
114
  components[
110
115
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -128,6 +133,7 @@
128
133
  description: "Extensions contributing debuggers or authentication providers have elevated host access and credential handling capabilities"
129
134
  severity: high
130
135
  category: vscode-extension
136
+ dry-run-support: full
131
137
  condition: |
132
138
  components[
133
139
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -155,6 +161,7 @@
155
161
  description: "Extensions with extensionKind=workspace and executesCode=true run arbitrary code on remote hosts or containers"
156
162
  severity: high
157
163
  category: vscode-extension
164
+ dry-run-support: full
158
165
  condition: |
159
166
  components[
160
167
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -180,6 +187,7 @@
180
187
  description: "Extensions requiring VS Code engine <1.80 may lack workspace trust and other modern security features"
181
188
  severity: low
182
189
  category: vscode-extension
190
+ dry-run-support: full
183
191
  condition: |
184
192
  components[
185
193
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -204,6 +212,7 @@
204
212
  description: "Extensions declaring browser entry points should run in web sandbox; flag if also declares workspace execution or filesystem access"
205
213
  severity: medium
206
214
  category: vscode-extension
215
+ dry-run-support: full
207
216
  condition: |
208
217
  components[
209
218
  $startsWith(purl, 'pkg:vscode-extension/')
@@ -4,6 +4,7 @@ import { dirname, join, relative, resolve } from "node:path";
4
4
  import process from "node:process";
5
5
 
6
6
  import { createBom } from "../cli/index.js";
7
+ import { DEFAULT_HBOM_AUDIT_CATEGORIES } from "../helpers/auditCategories.js";
7
8
  import {
8
9
  getNonCycloneDxErrorMessage,
9
10
  isCycloneDxBom,
@@ -24,13 +25,19 @@ import {
24
25
  import {
25
26
  dirNameStr,
26
27
  getTmpDir,
28
+ isDryRun,
29
+ recordActivity,
27
30
  safeExistsSync,
28
31
  safeMkdirSync,
29
32
  safeMkdtempSync,
30
33
  safeRmSync,
31
34
  safeWriteSync,
32
35
  } from "../helpers/utils.js";
33
- import { auditBom } from "../stages/postgen/auditBom.js";
36
+ import {
37
+ auditBom,
38
+ isHbomLikeBom,
39
+ isObomLikeBom,
40
+ } from "../stages/postgen/auditBom.js";
34
41
  import { postProcess } from "../stages/postgen/postgen.js";
35
42
  import { formatTargetLabel } from "./progress.js";
36
43
  import { renderAuditReport } from "./reporters.js";
@@ -52,6 +59,8 @@ export const DEFAULT_AUDIT_CATEGORIES = [
52
59
  "package-integrity",
53
60
  ];
54
61
 
62
+ const DIRECT_AUDIT_TOOL_NAME = "cdx-audit";
63
+
55
64
  const AUDIT_CACHE_DIRNAME = ".cdx-audit";
56
65
  const AUDIT_CACHE_BOM_FILE = "source-bom.json";
57
66
  const AUDIT_CACHE_META_FILE = "source-bom.meta.json";
@@ -165,6 +174,113 @@ export function loadInputBoms(options) {
165
174
  return inputBoms;
166
175
  }
167
176
 
177
+ function summarizeDirectAuditFindings(findings = []) {
178
+ const findingsBySeverity = {
179
+ critical: 0,
180
+ high: 0,
181
+ low: 0,
182
+ medium: 0,
183
+ };
184
+ let maxSeverity = "none";
185
+ for (const finding of findings) {
186
+ const severity = finding?.severity || "low";
187
+ if (findingsBySeverity[severity] !== undefined) {
188
+ findingsBySeverity[severity] += 1;
189
+ }
190
+ if (
191
+ (SEVERITY_ORDER[severity] ?? -1) > (SEVERITY_ORDER[maxSeverity] ?? -1)
192
+ ) {
193
+ maxSeverity = severity;
194
+ }
195
+ }
196
+ return {
197
+ findingsBySeverity,
198
+ findingsCount: findings.length,
199
+ maxSeverity,
200
+ };
201
+ }
202
+
203
+ function buildDirectAuditOptions(bomJson, options = {}) {
204
+ const explicitCategories = options.categories?.length
205
+ ? options.categories.join(",")
206
+ : undefined;
207
+ return {
208
+ bomAuditCategories:
209
+ explicitCategories ||
210
+ (isHbomLikeBom(bomJson)
211
+ ? DEFAULT_HBOM_AUDIT_CATEGORIES
212
+ : isObomLikeBom(bomJson)
213
+ ? "obom-runtime"
214
+ : undefined),
215
+ bomAuditMinSeverity: options.minSeverity || "low",
216
+ bomAuditRulesDir: options.rulesDir,
217
+ };
218
+ }
219
+
220
+ export async function runDirectBomAuditFromBoms(inputBoms, options = {}) {
221
+ if (!inputBoms.length) {
222
+ throw new Error("No CycloneDX BOM inputs were found.");
223
+ }
224
+ const results = [];
225
+ for (const inputBom of inputBoms) {
226
+ const directAuditOptions = buildDirectAuditOptions(
227
+ inputBom.bomJson,
228
+ options,
229
+ );
230
+ const findings = await auditBom(inputBom.bomJson, directAuditOptions);
231
+ results.push({
232
+ auditOptions: directAuditOptions,
233
+ bomFormat: inputBom.bomJson?.bomFormat,
234
+ findings,
235
+ serialNumber: inputBom.bomJson?.serialNumber,
236
+ source: inputBom.source,
237
+ specVersion: inputBom.bomJson?.specVersion,
238
+ status: "audited",
239
+ summary: summarizeDirectAuditFindings(findings),
240
+ });
241
+ }
242
+ const summary = {
243
+ findingsBySeverity: {
244
+ critical: 0,
245
+ high: 0,
246
+ low: 0,
247
+ medium: 0,
248
+ },
249
+ inputBomCount: inputBoms.length,
250
+ maxSeverity: "none",
251
+ totalFindings: 0,
252
+ bomsWithFindings: 0,
253
+ };
254
+ for (const result of results) {
255
+ summary.totalFindings += result.summary.findingsCount;
256
+ if (result.summary.findingsCount > 0) {
257
+ summary.bomsWithFindings += 1;
258
+ }
259
+ for (const [severity, count] of Object.entries(
260
+ result.summary.findingsBySeverity,
261
+ )) {
262
+ summary.findingsBySeverity[severity] += count;
263
+ }
264
+ if (
265
+ (SEVERITY_ORDER[result.summary.maxSeverity] ?? -1) >
266
+ (SEVERITY_ORDER[summary.maxSeverity] ?? -1)
267
+ ) {
268
+ summary.maxSeverity = result.summary.maxSeverity;
269
+ }
270
+ }
271
+ return {
272
+ auditMode: "direct",
273
+ generatedAt: new Date().toISOString(),
274
+ inputs: inputBoms.map((inputBom) => inputBom.source),
275
+ results,
276
+ summary,
277
+ tool: {
278
+ name: DIRECT_AUDIT_TOOL_NAME,
279
+ version: readPackageVersion(),
280
+ },
281
+ };
282
+ }
283
+
168
284
  /**
169
285
  * Read the package version from the local package.json file.
170
286
  *
@@ -399,6 +515,8 @@ function buildPredictiveAuditEstimate(selectedTargets) {
399
515
 
400
516
  function buildPredictiveAuditPreflightMessage(extractedTargets, options) {
401
517
  const selectedTargets = extractedTargets?.targets?.length || 0;
518
+ const allowlistedTargetsExcluded =
519
+ extractedTargets?.stats?.allowlistedTargetsExcluded || 0;
402
520
  const availableTargets = extractedTargets?.stats?.availableTargets || 0;
403
521
  const requiredTargets = extractedTargets?.stats?.requiredTargets || 0;
404
522
  const trustedTargetsExcluded =
@@ -411,17 +529,25 @@ function buildPredictiveAuditPreflightMessage(extractedTargets, options) {
411
529
  const trustedExclusionMessage = trustedTargetsExcluded
412
530
  ? ` Skipping ${trustedTargetsExcluded} trusted-publishing-backed package(s) by default.${trustedHint}`
413
531
  : "";
532
+ const customAllowlistSuffix = options?.allowlistFile
533
+ ? " and your custom allowlist"
534
+ : "";
535
+ const allowlistExclusionMessage = allowlistedTargetsExcluded
536
+ ? ` Skipping ${allowlistedTargetsExcluded} allowlisted package(s) using the built-in well-known purl prefix filter${customAllowlistSuffix}.`
537
+ : "";
414
538
  if (!estimate && availableTargets < LARGE_PREDICTIVE_AUDIT_THRESHOLD) {
415
- return trustedTargetsExcluded ? trustedExclusionMessage.trim() : undefined;
539
+ const passiveMessage =
540
+ `${trustedExclusionMessage}${allowlistExclusionMessage}`.trim();
541
+ return passiveMessage || undefined;
416
542
  }
417
543
  if (options?.scope === "required") {
418
- return `Predictive audit will scan ${selectedTargets} required package(s). ${estimate || "Large required-only scans may still take a while depending on repository lookups and child SBOM generation."}${trustedExclusionMessage}`;
544
+ return `Predictive audit will scan ${selectedTargets} required package(s). ${estimate || "Large required-only scans may still take a while depending on repository lookups and child SBOM generation."}${trustedExclusionMessage}${allowlistExclusionMessage}`;
419
545
  }
420
546
  if (truncatedTargets > 0) {
421
547
  const additionalTargets = Math.max(0, selectedTargets - requiredTargets);
422
- return `Predictive audit selected ${selectedTargets} of ${availableTargets} package(s) (${requiredTargets} required${additionalTargets ? ` + ${additionalTargets} additional` : ""}) using required-first prioritization. ${estimate || "This run was trimmed to keep audit time reasonable."}${trustedExclusionMessage}`;
548
+ return `Predictive audit selected ${selectedTargets} of ${availableTargets} package(s) (${requiredTargets} required${additionalTargets ? ` + ${additionalTargets} additional` : ""}) using required-first prioritization. ${estimate || "This run was trimmed to keep audit time reasonable."}${trustedExclusionMessage}${allowlistExclusionMessage}`;
423
549
  }
424
- return `Predictive audit will scan ${selectedTargets} package(s). ${estimate || "Large predictive audits may still take a while depending on repository lookups and child SBOM generation."}${trustedExclusionMessage}`;
550
+ return `Predictive audit will scan ${selectedTargets} package(s). ${estimate || "Large predictive audits may still take a while depending on repository lookups and child SBOM generation."}${trustedExclusionMessage}${allowlistExclusionMessage}`;
425
551
  }
426
552
 
427
553
  /**
@@ -1622,6 +1748,7 @@ export async function auditTarget(target, options) {
1622
1748
  const findings = await auditBom(processedBomJson, {
1623
1749
  bomAuditCategories: categories.join(","),
1624
1750
  bomAuditMinSeverity: options.minSeverity || "low",
1751
+ bomAuditRulesDir: options.rulesDir,
1625
1752
  });
1626
1753
  const contextualFindings = buildTargetContextFindings(target);
1627
1754
  const pythonSourceFindings = buildPythonSourceHeuristicFindings(
@@ -2100,15 +2227,69 @@ export async function runAuditFromBoms(inputBoms, options) {
2100
2227
  if (!inputBoms.length) {
2101
2228
  throw new Error("No CycloneDX BOM inputs were found.");
2102
2229
  }
2103
- if (options.trusted !== "include") {
2230
+ const shouldEnrichRegistryMetadata =
2231
+ !isDryRun && options.trusted !== "include";
2232
+ if (shouldEnrichRegistryMetadata) {
2104
2233
  await enrichInputBomsWithRegistryMetadata(inputBoms);
2105
2234
  }
2106
- const extractedTargets = collectAuditTargets(inputBoms, {
2235
+ const targetSelectionOptions = {
2236
+ allowlistFile: options.allowlistFile,
2107
2237
  maxTargets: options.maxTargets,
2108
2238
  prioritizeDirectRuntime: options.prioritizeDirectRuntime,
2109
2239
  scope: options.scope,
2110
2240
  trusted: options.trusted,
2111
- });
2241
+ };
2242
+ const extractedTargets = collectAuditTargets(
2243
+ inputBoms,
2244
+ targetSelectionOptions,
2245
+ );
2246
+ if (isDryRun) {
2247
+ // Dry-run mode intentionally stops after target planning. Registry metadata
2248
+ // enrichment, repository cloning, and child SBOM generation are all
2249
+ // side-effecting or outbound behaviors that the predictive audit must avoid.
2250
+ const report = {
2251
+ dryRun: true,
2252
+ generatedAt: new Date().toISOString(),
2253
+ groupedResults: [],
2254
+ inputs: inputBoms.map((inputBom) => inputBom.source),
2255
+ results: extractedTargets.targets.map((target) => ({
2256
+ assessment: {
2257
+ categoryCounts: {},
2258
+ confidenceLabel: "none",
2259
+ findingsCount: 0,
2260
+ reasons: [
2261
+ "Dry run mode planned this predictive audit target from the input BOM but skipped registry metadata fetches, upstream repository cloning, and child SBOM generation.",
2262
+ ],
2263
+ score: 0,
2264
+ severity: "none",
2265
+ },
2266
+ findings: [],
2267
+ status: "skipped",
2268
+ target,
2269
+ })),
2270
+ skipped: extractedTargets.skipped,
2271
+ summary: {
2272
+ predictiveDryRun: true,
2273
+ },
2274
+ tool: {
2275
+ name: "cdx-audit",
2276
+ version: readPackageVersion(),
2277
+ },
2278
+ };
2279
+ Object.assign(
2280
+ report.summary,
2281
+ summarizeAudit(inputBoms, report.results, extractedTargets.skipped),
2282
+ summarizeGroupedResults(report.groupedResults),
2283
+ );
2284
+ recordActivity({
2285
+ kind: "audit",
2286
+ reason:
2287
+ "Dry run mode planned predictive BOM audit targets from the input BOM but skipped registry metadata fetches, repository cloning, and child SBOM generation.",
2288
+ status: extractedTargets.targets.length ? "blocked" : "completed",
2289
+ target: "predictive-dependency-audit",
2290
+ });
2291
+ return report;
2292
+ }
2112
2293
  const results = [];
2113
2294
  const preflightMessage = buildPredictiveAuditPreflightMessage(
2114
2295
  extractedTargets,
@@ -2192,6 +2373,12 @@ export async function runAudit(options) {
2192
2373
  const inputBoms = loadInputBoms(options);
2193
2374
  const workspaceContext = prepareWorkspaceContext(options);
2194
2375
  try {
2376
+ if (options.directBomAudit) {
2377
+ return await runDirectBomAuditFromBoms(inputBoms, {
2378
+ ...options,
2379
+ workspaceDir: workspaceContext.workspaceDir,
2380
+ });
2381
+ }
2195
2382
  return await runAuditFromBoms(inputBoms, {
2196
2383
  ...options,
2197
2384
  workspaceDir: workspaceContext.workspaceDir,
@@ -2214,6 +2401,20 @@ export function finalizeAuditReport(report, options) {
2214
2401
  const output = renderAuditReport(options.report, report, {
2215
2402
  minSeverity: options.minSeverity,
2216
2403
  });
2404
+ if (report?.auditMode === "direct") {
2405
+ const shouldFail = (report.results || []).some((result) =>
2406
+ (result.findings || []).some((finding) =>
2407
+ severityMeetsThreshold(
2408
+ finding?.severity || "none",
2409
+ options.failSeverity || "high",
2410
+ ),
2411
+ ),
2412
+ );
2413
+ return {
2414
+ exitCode: shouldFail ? 3 : 0,
2415
+ output,
2416
+ };
2417
+ }
2217
2418
  const effectiveResults = report.groupedResults?.length
2218
2419
  ? report.groupedResults
2219
2420
  : report.results;