From 0e337a0582ce8d5fe58acc69869b4643ffdf6226 Mon Sep 17 00:00:00 2001 From: Talles Gois Date: Mon, 21 Sep 2026 14:36:54 -0300 Subject: [PATCH 1/5] fix: exclude volatile ReplicaSet name from Trivy Operator service field The Trivy Operator handlers (vulnerability, checks, secrets) built the `service` field as `namespace/kind/name`. For Pods owned by a ReplicaSet, `name` is the ReplicaSet's generated name, which changes on every deploy. Since close_old_findings matches on an exact service string, this made service change on every redeploy and permanently prevented old findings from ever being closed, causing unbounded finding accumulation across redeploys. Fixes #16026 --- dojo/tools/trivy_operator/checks_handler.py | 7 ++++++- dojo/tools/trivy_operator/secrets_handler.py | 7 ++++++- dojo/tools/trivy_operator/vulnerability_handler.py | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/dojo/tools/trivy_operator/checks_handler.py b/dojo/tools/trivy_operator/checks_handler.py index 506058ef6e9..264311e2de4 100644 --- a/dojo/tools/trivy_operator/checks_handler.py +++ b/dojo/tools/trivy_operator/checks_handler.py @@ -17,7 +17,12 @@ def handle_checks(self, labels, checks, test): resource_kind = labels.get("trivy-operator.resource.kind", "") resource_name = labels.get("trivy-operator.resource.name", "") container_name = labels.get("trivy-operator.container.name", "") - service = f"{resource_namespace}/{resource_kind}/{resource_name}" + # resource_name is excluded from service on purpose: for Pods owned by a + # ReplicaSet it holds the ReplicaSet's generated name, which changes on + # every deploy. Since close_old_findings matches on an exact service + # string, including it here would make service change on every redeploy + # and permanently prevent old findings from ever being closed. + service = f"{resource_namespace}/{resource_kind}" if container_name: service = f"{service}/{container_name}" for check in checks: diff --git a/dojo/tools/trivy_operator/secrets_handler.py b/dojo/tools/trivy_operator/secrets_handler.py index 1bead526d79..0a5d6a6368c 100644 --- a/dojo/tools/trivy_operator/secrets_handler.py +++ b/dojo/tools/trivy_operator/secrets_handler.py @@ -24,7 +24,12 @@ def handle_secrets(self, labels, secrets, test, image=None): resource_kind = labels.get("trivy-operator.resource.kind", "") resource_name = labels.get("trivy-operator.resource.name", "") container_name = labels.get("trivy-operator.container.name", "") - service = f"{resource_namespace}/{resource_kind}/{resource_name}" + # resource_name is excluded from service on purpose: for Pods owned by a + # ReplicaSet it holds the ReplicaSet's generated name, which changes on + # every deploy. Since close_old_findings matches on an exact service + # string, including it here would make service change on every redeploy + # and permanently prevent old findings from ever being closed. + service = f"{resource_namespace}/{resource_kind}" if container_name: service = f"{service}/{container_name}" for secret in secrets: diff --git a/dojo/tools/trivy_operator/vulnerability_handler.py b/dojo/tools/trivy_operator/vulnerability_handler.py index 64826292b60..9e60d7f7b49 100644 --- a/dojo/tools/trivy_operator/vulnerability_handler.py +++ b/dojo/tools/trivy_operator/vulnerability_handler.py @@ -24,7 +24,12 @@ def handle_vulns(self, labels, vulnerabilities, test, image=None): resource_kind = labels.get("trivy-operator.resource.kind", "") resource_name = labels.get("trivy-operator.resource.name", "") container_name = labels.get("trivy-operator.container.name", "") - service = f"{resource_namespace}/{resource_kind}/{resource_name}" + # resource_name is excluded from service on purpose: for Pods owned by a + # ReplicaSet it holds the ReplicaSet's generated name, which changes on + # every deploy. Since close_old_findings matches on an exact service + # string, including it here would make service change on every redeploy + # and permanently prevent old findings from ever being closed. + service = f"{resource_namespace}/{resource_kind}" if container_name: service = f"{service}/{container_name}" for vulnerability in vulnerabilities: From ebc60a1a01166d7d9accc988b834d3e98b1788a9 Mon Sep 17 00:00:00 2001 From: Talles Gois Date: Mon, 21 Sep 2026 14:37:32 -0300 Subject: [PATCH 2/5] test: assert Trivy Operator service excludes volatile ReplicaSet name --- unittests/tools/test_trivy_operator_parser.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/unittests/tools/test_trivy_operator_parser.py b/unittests/tools/test_trivy_operator_parser.py index f9ccaf16c44..f88795ce4c2 100644 --- a/unittests/tools/test_trivy_operator_parser.py +++ b/unittests/tools/test_trivy_operator_parser.py @@ -64,6 +64,19 @@ def test_vulnerabilityreport_single_vulns(self): self.assertEqual(4.2, finding.cvssv3_score) self.assertEqual(True, finding.fix_available) + def test_vulnerabilityreport_service_excludes_volatile_replicaset_name(self): + # regression test: `service` must stay stable across redeploys, otherwise + # DefectDojo's close_old_findings can never match findings from a previous + # deploy generation and old findings are never closed (see issue #16026). + # The fixture's `trivy-operator.resource.name` label is a ReplicaSet-generated + # name ("nginx-deployment-965685897") and must NOT end up in `service`. + with sample_path("vulnerabilityreport_single_vuln.json").open(encoding="utf-8") as test_file: + parser = TrivyOperatorParser() + findings = parser.get_findings(test_file, Test()) + finding = findings[0] + self.assertEqual("default/ReplicaSet/nginx", finding.service) + self.assertNotIn("nginx-deployment-965685897", finding.service) + def test_vulnerabilityreport_many(self): with sample_path("vulnerabilityreport_many.json").open(encoding="utf-8") as test_file: parser = TrivyOperatorParser() From 094863dc71f041f9684e23e5ad5d02b7812f734c Mon Sep 17 00:00:00 2001 From: Talles Gois Date: Mon, 21 Sep 2026 15:01:26 -0300 Subject: [PATCH 3/5] chore: trim service-field comment --- dojo/tools/trivy_operator/vulnerability_handler.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dojo/tools/trivy_operator/vulnerability_handler.py b/dojo/tools/trivy_operator/vulnerability_handler.py index 9e60d7f7b49..7aac4d48569 100644 --- a/dojo/tools/trivy_operator/vulnerability_handler.py +++ b/dojo/tools/trivy_operator/vulnerability_handler.py @@ -24,11 +24,8 @@ def handle_vulns(self, labels, vulnerabilities, test, image=None): resource_kind = labels.get("trivy-operator.resource.kind", "") resource_name = labels.get("trivy-operator.resource.name", "") container_name = labels.get("trivy-operator.container.name", "") - # resource_name is excluded from service on purpose: for Pods owned by a - # ReplicaSet it holds the ReplicaSet's generated name, which changes on - # every deploy. Since close_old_findings matches on an exact service - # string, including it here would make service change on every redeploy - # and permanently prevent old findings from ever being closed. + # resource_name changes every deploy (ReplicaSet-generated) — excluded so + # service stays stable and close_old_findings matches across redeploys. service = f"{resource_namespace}/{resource_kind}" if container_name: service = f"{service}/{container_name}" From f8972b9b8dd15cbd950491b16956cea69d032d3a Mon Sep 17 00:00:00 2001 From: Talles Gois Date: Mon, 21 Sep 2026 15:01:33 -0300 Subject: [PATCH 4/5] chore: trim service-field comment --- dojo/tools/trivy_operator/checks_handler.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dojo/tools/trivy_operator/checks_handler.py b/dojo/tools/trivy_operator/checks_handler.py index 264311e2de4..79876bef284 100644 --- a/dojo/tools/trivy_operator/checks_handler.py +++ b/dojo/tools/trivy_operator/checks_handler.py @@ -17,11 +17,8 @@ def handle_checks(self, labels, checks, test): resource_kind = labels.get("trivy-operator.resource.kind", "") resource_name = labels.get("trivy-operator.resource.name", "") container_name = labels.get("trivy-operator.container.name", "") - # resource_name is excluded from service on purpose: for Pods owned by a - # ReplicaSet it holds the ReplicaSet's generated name, which changes on - # every deploy. Since close_old_findings matches on an exact service - # string, including it here would make service change on every redeploy - # and permanently prevent old findings from ever being closed. + # resource_name changes every deploy (ReplicaSet-generated) — excluded so + # service stays stable and close_old_findings matches across redeploys. service = f"{resource_namespace}/{resource_kind}" if container_name: service = f"{service}/{container_name}" From 8f348acb08a5c9cb1f46e15f7525d2007284a183 Mon Sep 17 00:00:00 2001 From: Talles Gois Date: Mon, 21 Sep 2026 15:01:40 -0300 Subject: [PATCH 5/5] chore: trim service-field comment --- dojo/tools/trivy_operator/secrets_handler.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dojo/tools/trivy_operator/secrets_handler.py b/dojo/tools/trivy_operator/secrets_handler.py index 0a5d6a6368c..150c2c04dd8 100644 --- a/dojo/tools/trivy_operator/secrets_handler.py +++ b/dojo/tools/trivy_operator/secrets_handler.py @@ -24,11 +24,8 @@ def handle_secrets(self, labels, secrets, test, image=None): resource_kind = labels.get("trivy-operator.resource.kind", "") resource_name = labels.get("trivy-operator.resource.name", "") container_name = labels.get("trivy-operator.container.name", "") - # resource_name is excluded from service on purpose: for Pods owned by a - # ReplicaSet it holds the ReplicaSet's generated name, which changes on - # every deploy. Since close_old_findings matches on an exact service - # string, including it here would make service change on every redeploy - # and permanently prevent old findings from ever being closed. + # resource_name changes every deploy (ReplicaSet-generated) — excluded so + # service stays stable and close_old_findings matches across redeploys. service = f"{resource_namespace}/{resource_kind}" if container_name: service = f"{service}/{container_name}"