@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.
- package/README.md +64 -22
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +238 -116
- package/bin/convert.js +28 -13
- package/bin/hbom.js +490 -0
- package/bin/repl.js +580 -29
- package/bin/validate.js +34 -4
- package/bin/verify.js +40 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/queries-darwin.json +12 -1
- package/data/queries-win.json +7 -1
- package/data/queries.json +39 -2
- package/data/rules/ai-agent-governance.yaml +16 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/chrome-extensions.yaml +8 -0
- package/data/rules/ci-permissions.yaml +42 -18
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +11 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +18 -3
- package/data/rules/obom-runtime.yaml +907 -22
- package/data/rules/package-integrity.yaml +14 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +9 -0
- package/lib/audit/index.js +209 -8
- package/lib/audit/index.poku.js +332 -0
- package/lib/audit/reporters.js +222 -0
- package/lib/audit/targets.js +146 -1
- package/lib/audit/targets.poku.js +186 -0
- package/lib/cli/asar.poku.js +328 -0
- package/lib/cli/index.js +506 -88
- package/lib/cli/index.poku.js +1352 -212
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/analyzer.js +1406 -29
- package/lib/helpers/analyzer.poku.js +342 -0
- package/lib/helpers/analyzerScope.js +712 -0
- package/lib/helpers/asarutils.js +1556 -0
- package/lib/helpers/asarutils.poku.js +443 -0
- package/lib/helpers/auditCategories.js +12 -0
- package/lib/helpers/auditCategories.poku.js +32 -0
- package/lib/helpers/cbomutils.js +271 -1
- package/lib/helpers/cbomutils.poku.js +248 -5
- package/lib/helpers/display.js +291 -1
- package/lib/helpers/display.poku.js +149 -0
- package/lib/helpers/evidenceUtils.js +58 -0
- package/lib/helpers/evidenceUtils.poku.js +54 -0
- package/lib/helpers/exportUtils.js +9 -0
- package/lib/helpers/gtfobins.js +142 -8
- package/lib/helpers/gtfobins.poku.js +24 -1
- package/lib/helpers/hbom.js +710 -0
- package/lib/helpers/hbom.poku.js +496 -0
- package/lib/helpers/hbomAnalysis.js +268 -0
- package/lib/helpers/hbomAnalysis.poku.js +249 -0
- package/lib/helpers/hbomLoader.js +35 -0
- package/lib/helpers/hostTopology.js +803 -0
- package/lib/helpers/hostTopology.poku.js +363 -0
- package/lib/helpers/inventoryStats.js +69 -0
- package/lib/helpers/inventoryStats.poku.js +86 -0
- package/lib/helpers/lolbas.js +19 -1
- package/lib/helpers/lolbas.poku.js +23 -0
- package/lib/helpers/osqueryTransform.js +47 -0
- package/lib/helpers/osqueryTransform.poku.js +47 -0
- package/lib/helpers/plugins.js +349 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/protobom.js +156 -45
- package/lib/helpers/protobom.poku.js +140 -5
- package/lib/helpers/remote/dependency-track.js +36 -3
- package/lib/helpers/remote/dependency-track.poku.js +44 -0
- package/lib/helpers/source.js +24 -0
- package/lib/helpers/source.poku.js +32 -0
- package/lib/helpers/utils.js +1438 -93
- package/lib/helpers/utils.poku.js +846 -4
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2293 -353
- package/lib/managers/binary.poku.js +1699 -1
- package/lib/managers/docker.js +201 -79
- package/lib/managers/docker.poku.js +337 -12
- package/lib/server/server.js +2 -27
- package/lib/stages/postgen/annotator.js +38 -0
- package/lib/stages/postgen/annotator.poku.js +107 -1
- package/lib/stages/postgen/auditBom.js +121 -18
- package/lib/stages/postgen/auditBom.poku.js +1366 -31
- package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
- package/lib/stages/postgen/postgen.js +192 -1
- package/lib/stages/postgen/postgen.poku.js +321 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/package.json +23 -21
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +44 -0
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts +16 -0
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +16 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +4 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +33 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/analyzerScope.d.ts +11 -0
- package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
- package/types/lib/helpers/asarutils.d.ts +34 -0
- package/types/lib/helpers/asarutils.d.ts.map +1 -0
- package/types/lib/helpers/auditCategories.d.ts +5 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +3 -2
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/evidenceUtils.d.ts +8 -0
- package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +8 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -1
- package/types/lib/helpers/hbom.d.ts +49 -0
- package/types/lib/helpers/hbom.d.ts.map +1 -0
- package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
- package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/helpers/hbomLoader.d.ts +7 -0
- package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
- package/types/lib/helpers/hostTopology.d.ts +12 -0
- package/types/lib/helpers/hostTopology.d.ts.map +1 -0
- package/types/lib/helpers/inventoryStats.d.ts +11 -0
- package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -1
- package/types/lib/helpers/osqueryTransform.d.ts +3 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
- package/types/lib/helpers/plugins.d.ts +58 -0
- package/types/lib/helpers/plugins.d.ts.map +1 -0
- package/types/lib/helpers/protobom.d.ts +3 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +45 -8
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +5 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +2 -1
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +26 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +2 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- 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/')
|
package/lib/audit/index.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
2230
|
+
const shouldEnrichRegistryMetadata =
|
|
2231
|
+
!isDryRun && options.trusted !== "include";
|
|
2232
|
+
if (shouldEnrichRegistryMetadata) {
|
|
2104
2233
|
await enrichInputBomsWithRegistryMetadata(inputBoms);
|
|
2105
2234
|
}
|
|
2106
|
-
const
|
|
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;
|