1010# 2. Containers have no inbound network listeners (no ports exposed)
1111# 3. Containers run with dropped capabilities (no CAP_SYS_ADMIN, etc.)
1212# 4. Containers do not run in privileged mode
13- # 5. Pipeline inputs are data files (FASTQ, BAM, VCF, reference genomes),
14- # not attacker-controlled interactive input
13+ # 5. Pipeline inputs are data files (FASTQ, BAM, VCF, reference genomes)
14+ # which may be untrusted or malformed
1515#
1616# If any assumption does not hold for a given image or use case,
1717# DO NOT apply this policy to that image.
2828# accordingly. The CVSS vector string location has changed across
2929# Trivy versions (see https://github.com/aquasecurity/trivy/issues/1627).
3030#
31- # VERSION: 1.0
32- # LAST REVIEWED: 2026-03-19
31+ # CVSS VERSION SUPPORT:
32+ # This policy supports both CVSS v3.1 and CVSS v4.0 vector strings.
33+ # Trivy is transitioning to v4.0 for newer advisories. Some CVEs may
34+ # have only a v4.0 vector (no v3.1). The helper functions at the bottom
35+ # extract vectors from both versions and the rules are written to match
36+ # either format.
37+ #
38+ # VERSION: 2.0
39+ # LAST REVIEWED: 2026-03-20
3340# REVIEW CADENCE: Quarterly, or when platform architecture changes
3441#
3542
@@ -44,20 +51,20 @@ default ignore = false
4451# These CVEs require hands-on hardware interaction (USB, Firewire,
4552# JTAG, etc.) which is impossible in any cloud PaaS context.
4653#
54+ # CVSS v3.1: AV:P
55+ # CVSS v4.0: AV:P (same field name)
56+ #
4757# Risk of false negative: Essentially zero. There is no scenario
4858# in which a pipeline container is physically accessible to an attacker.
4959# Confidence: Very High
5060# ##############################################################################
5161
5262ignore {
53- cvss_vector := get_v3_vector (input )
54- contains (cvss_vector, " /AV:P/" )
63+ has_v3_field (input , " AV:P" )
5564}
5665
57- # Also catch AV:P at the end of the vector string (no trailing slash)
5866ignore {
59- cvss_vector := get_v3_vector (input )
60- endswith (cvss_vector, " /AV:P" )
67+ has_v4_field (input , " AV:P" )
6168}
6269
6370# ##############################################################################
@@ -69,19 +76,20 @@ ignore {
6976# on orchestrated infrastructure where the attacker cannot place
7077# themselves on an adjacent segment.
7178#
79+ # CVSS v3.1: AV:A
80+ # CVSS v4.0: AV:A (same field name)
81+ #
7282# Risk of false negative: Very low. Cloud networking abstractions
7383# make adjacent-network attacks impractical against pipeline containers.
7484# Confidence: Very High
7585# ##############################################################################
7686
7787ignore {
78- cvss_vector := get_v3_vector (input )
79- contains (cvss_vector, " /AV:A/" )
88+ has_v3_field (input , " AV:A" )
8089}
8190
8291ignore {
83- cvss_vector := get_v3_vector (input )
84- endswith (cvss_vector, " /AV:A" )
92+ has_v4_field (input , " AV:A" )
8593}
8694
8795# ##############################################################################
@@ -102,14 +110,32 @@ ignore {
102110# - AV:L + UI:R together means "must have local access AND a human
103111# must do something" - genuinely inapplicable in batch containers.
104112#
113+ # CVSS v3.1: AV:L + UI:R
114+ # CVSS v4.0: AV:L + UI:P (Passive) or UI:A (Active)
115+ # v4.0 splits "Required" into Passive (viewing content) and Active
116+ # (clicking/interacting). Both require a human, so both are safe to
117+ # ignore in batch containers.
118+ #
105119# Risk of false negative: Very low for true batch pipeline containers.
106120# Confidence: High
107121# ##############################################################################
108122
123+ # v3: AV:L + UI:R
124+ ignore {
125+ has_v3_field (input , " AV:L" )
126+ has_v3_field (input , " UI:R" )
127+ }
128+
129+ # v4: AV:L + UI:P (Passive user interaction)
109130ignore {
110- cvss_vector := get_v3_vector (input )
111- contains (cvss_vector, " /AV:L/" )
112- contains (cvss_vector, " /UI:R" )
131+ has_v4_field (input , " AV:L" )
132+ has_v4_field (input , " UI:P" )
133+ }
134+
135+ # v4: AV:L + UI:A (Active user interaction)
136+ ignore {
137+ has_v4_field (input , " AV:L" )
138+ has_v4_field (input , " UI:A" )
113139}
114140
115141# ##############################################################################
@@ -126,15 +152,22 @@ ignore {
126152# accessible vulnerability requiring high privileges may still be
127153# relevant if the service runs as a privileged user.
128154#
155+ # CVSS v3.1: AV:L + PR:H
156+ # CVSS v4.0: AV:L + PR:H (same field names)
157+ #
129158# Risk of false negative: Low, assuming containers run as non-root.
130159# If your containers run as root, REMOVE THIS RULE.
131160# Confidence: High (conditional on non-root execution)
132161# ##############################################################################
133162
134163ignore {
135- cvss_vector := get_v3_vector (input )
136- contains (cvss_vector, " /AV:L/" )
137- contains (cvss_vector, " /PR:H/" )
164+ has_v3_field (input , " AV:L" )
165+ has_v3_field (input , " PR:H" )
166+ }
167+
168+ ignore {
169+ has_v4_field (input , " AV:L" )
170+ has_v4_field (input , " PR:H" )
138171}
139172
140173# ##############################################################################
@@ -154,60 +187,173 @@ ignore {
154187# the container-host boundary (e.g., container escape via kernel exploit)
155188# IS dangerous and is NOT ignored by this rule.
156189#
190+ # CVSS v3.1: AV:L + S:U
191+ # CVSS v4.0: AV:L + SC:N + SI:N + SA:N
192+ # v4.0 replaced the binary S:U/S:C with three subsequent-component
193+ # impact fields. SC:N + SI:N + SA:N means no impact on any component
194+ # beyond the vulnerable one — equivalent to v3's S:U.
195+ #
157196# Risk of false negative: Low. The theoretical concern is that AV:L+S:U
158197# could include reading mounted secrets, but an attacker with code
159198# execution can already read those secrets directly.
160199# Confidence: High
161200# ##############################################################################
162201
202+ # v3: AV:L + S:U
163203ignore {
164- cvss_vector := get_v3_vector (input )
165- contains (cvss_vector, " /AV:L/" )
166- contains (cvss_vector, " /S:U/" )
204+ has_v3_field (input , " AV:L" )
205+ has_v3_field (input , " S:U" )
167206}
168207
169- # Also catch S:U at the end of the vector string ( no trailing slash)
208+ # v4: AV:L + no subsequent-component impact
170209ignore {
171- cvss_vector := get_v3_vector (input )
172- contains (cvss_vector, " /AV:L/" )
173- endswith (cvss_vector, " /S:U" )
210+ has_v4_field (input , " AV:L" )
211+ has_v4_field (input , " SC:N" )
212+ has_v4_field (input , " SI:N" )
213+ has_v4_field (input , " SA:N" )
174214}
175215
176216# ##############################################################################
177- # HELPER FUNCTION: Extract the CVSS v3 vector string
217+ # SECTION 6: AVAILABILITY-ONLY IMPACT, SCOPE UNCHANGED
218+ #
219+ # Rationale: CVEs where the only impact is availability (DoS/resource
220+ # exhaustion) and scope is unchanged mean: processing crafted input can
221+ # crash or hang the affected process, but cannot read data (C:N),
222+ # modify data (I:N), or affect other components (S:U).
223+ #
224+ # In ephemeral batch containers, a DoS means a single pipeline job
225+ # fails or hangs until it hits its timeout or memory limit. This is
226+ # operationally equivalent to a corrupted input file or OOM — the job
227+ # fails, the container is destroyed, and the next job runs on a fresh
228+ # container. There is no persistent state corruption, no data
229+ # exfiltration, and no lateral movement.
230+ #
231+ # This rule applies regardless of attack vector (including AV:N),
232+ # because the impact is strictly contained: even if triggered by
233+ # network-delivered data, the worst outcome is one failed job.
234+ #
235+ # NOTE: This does NOT ignore:
236+ # - DoS with S:C / SC≠N / SI≠N / SA≠N (scope changed — could
237+ # affect host or other containers)
238+ # - DoS combined with any confidentiality or integrity impact
239+ # (C≠N or I≠N), which could indicate data leaks or corruption
240+ # alongside the crash
241+ #
242+ # CVSS v3.1: C:N + I:N + S:U (with any A value)
243+ # CVSS v4.0: VC:N + VI:N + SC:N + SI:N + SA:N (with any VA value)
244+ #
245+ # Risk of false negative: Low. The concern would be if a DoS could be
246+ # weaponized into a resource exhaustion attack against the compute
247+ # platform (e.g., repeatedly submitting jobs with crafted inputs to
248+ # burn credits). This is a business logic concern mitigated by job
249+ # submission controls and cost alerts, not by container hardening.
250+ # Confidence: High
251+ # ##############################################################################
252+
253+ # v3: C:N + I:N + S:U (availability-only, scope unchanged)
254+ ignore {
255+ has_v3_field (input , " C:N" )
256+ has_v3_field (input , " I:N" )
257+ has_v3_field (input , " S:U" )
258+ }
259+
260+ # v4: VC:N + VI:N + no subsequent-component impact (availability-only)
261+ ignore {
262+ has_v4_field (input , " VC:N" )
263+ has_v4_field (input , " VI:N" )
264+ has_v4_field (input , " SC:N" )
265+ has_v4_field (input , " SI:N" )
266+ has_v4_field (input , " SA:N" )
267+ }
268+
269+ # ##############################################################################
270+ # HELPER FUNCTIONS: Extract and match CVSS vector strings
178271#
179272# Trivy's JSON structure nests CVSS data under input.CVSS with vendor
180273# keys. The vector string location varies by data source. We check
181274# multiple common paths and prefer NVD.
182275#
276+ # CVSS v3.1 vectors look like: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
277+ # CVSS v4.0 vectors look like: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
278+ #
279+ # Fields are slash-delimited key:value pairs. The has_vX_field helpers
280+ # check for a field anywhere in the vector, handling both mid-string
281+ # (/field/) and end-of-string (/field) positions.
282+ #
183283# IMPORTANT: Run `trivy image --format json <your-image>` and inspect
184284# the .Vulnerabilities[].CVSS structure to confirm these paths work
185- # for your Trivy version. If the structure differs, update this
186- # function accordingly.
285+ # for your Trivy version. If the structure differs, update these
286+ # functions accordingly.
187287# ##############################################################################
188288
289+ # --- CVSS v3.1 vector extraction ---
290+
189291get_v3_vector (vuln) = vector {
190292 vector := vuln.CVSS.nvd.V3Vector
191293} else = vector {
192294 vector := vuln.CVSS.redhat.V3Vector
193295} else = vector {
194296 vector := vuln.CVSS.ghsa.V3Vector
195297} else = vector {
196- # Fallback: try any vendor that has a V3Vector
197298 some vendor
198299 vector := vuln.CVSS[vendor].V3Vector
199300} else = " " {
200301 true
201302}
202303
304+ # --- CVSS v4.0 vector extraction ---
305+
306+ get_v4_vector (vuln) = vector {
307+ vector := vuln.CVSS.nvd.V40Vector
308+ } else = vector {
309+ vector := vuln.CVSS.redhat.V40Vector
310+ } else = vector {
311+ vector := vuln.CVSS.ghsa.V40Vector
312+ } else = vector {
313+ some vendor
314+ vector := vuln.CVSS[vendor].V40Vector
315+ } else = " " {
316+ true
317+ }
318+
319+ # --- Field matching helpers ---
320+ # Check if a CVSS vector contains a specific field value.
321+ # Handles both mid-string (/AV:N/) and end-of-string (/AV:N) positions.
322+
323+ has_v3_field (vuln, field) {
324+ cvss_vector := get_v3_vector (vuln)
325+ cvss_vector != " "
326+ contains (cvss_vector, concat (" " , [" /" , field, " /" ]))
327+ }
328+
329+ has_v3_field (vuln, field) {
330+ cvss_vector := get_v3_vector (vuln)
331+ cvss_vector != " "
332+ endswith (cvss_vector, concat (" " , [" /" , field]))
333+ }
334+
335+ has_v4_field (vuln, field) {
336+ cvss_vector := get_v4_vector (vuln)
337+ cvss_vector != " "
338+ contains (cvss_vector, concat (" " , [" /" , field, " /" ]))
339+ }
340+
341+ has_v4_field (vuln, field) {
342+ cvss_vector := get_v4_vector (vuln)
343+ cvss_vector != " "
344+ endswith (cvss_vector, concat (" " , [" /" , field]))
345+ }
346+
203347# ##############################################################################
204348# RULES INTENTIONALLY NOT INCLUDED (and why):
205349#
206- # 1. AV:N (Network attack vector) — NOT ignored.
350+ # 1. AV:N (Network attack vector) — NOT blanket- ignored.
207351# Even though batch pipeline containers typically have no inbound
208352# listeners, some AV:N CVEs involve outbound connections triggered
209353# by processing attacker-influenced data (e.g., Log4Shell). We
210- # cannot safely blanket-ignore network-vector CVEs.
354+ # cannot safely blanket-ignore network-vector CVEs. However,
355+ # Section 6 does ignore AV:N CVEs that are availability-only with
356+ # no scope change, since the worst outcome is a crashed job.
211357#
212358# 2. UI:R alone (without AV:L) — NOT ignored.
213359# Some AV:N + UI:R vulnerabilities involve scenarios like processing
@@ -233,4 +379,13 @@ get_v3_vector(vuln) = vector {
233379# boundaries (e.g., container escape via kernel exploit). These are
234380# dangerous even in ephemeral containers. Only AV:L + S:U (Scope
235381# Unchanged) is ignored — see Section 5 above.
382+ #
383+ # 6. Inbound-listener-only server CVEs — NOT categorically ignored.
384+ # Many AV:N CVEs in fat JARs (Jetty, ZooKeeper, Netty server-side)
385+ # require an active network listener that we never start. However,
386+ # CVSS does not distinguish inbound-listener vs. data-processing
387+ # attack surfaces within AV:N. Adding package-name-based exceptions
388+ # here would be fragile and is better handled in .trivyignore with
389+ # per-CVE justification documenting that the server component is
390+ # never instantiated.
236391# ##############################################################################
0 commit comments