From 5e7d607e167d8ab0739f45ac688b7d28399ebbba Mon Sep 17 00:00:00 2001 From: Alexandre Masson Date: Mon, 20 Jul 2026 15:56:38 +0200 Subject: [PATCH] fix(trivy): initialize resource_name for cluster-scoped k8s resources In the kubernetes-report branch of the Trivy parser, the Resources loop never initializes resource_name before the conditional concatenations, unlike the twin Vulnerabilities and Misconfigurations loops which reset service_name = "" on every iteration. Two consequences when a resource has no Namespace (cluster-scoped resources such as ClusterRole or ClusterRoleBinding, as emitted by `trivy k8s --report all --format json`): - if the first resource in the report is cluster-scoped, the parser raises UnboundLocalError and /api/v2/import-scan/ returns HTTP 500; - if a cluster-scoped resource follows a namespaced one, it silently inherits the previous iteration's value and gets a corrupted service name. Fix by initializing resource_name = "" at the top of each iteration, matching the two loops above. Adds a scan sample with a cluster-scoped resource first (plus a namespaced one and a trailing cluster-scoped one to cover the carry-over case) and a unit test asserting the parsed service names. Co-Authored-By: Claude Fable 5 --- dojo/tools/trivy/parser.py | 1 + .../kubernetes_cluster_scoped_resources.json | 132 ++++++++++++++++++ unittests/tools/test_trivy_parser.py | 20 +++ 3 files changed, 153 insertions(+) create mode 100644 unittests/scans/trivy/kubernetes_cluster_scoped_resources.json diff --git a/dojo/tools/trivy/parser.py b/dojo/tools/trivy/parser.py index 25746cd4089..5522ac5b388 100644 --- a/dojo/tools/trivy/parser.py +++ b/dojo/tools/trivy/parser.py @@ -218,6 +218,7 @@ def get_findings(self, scan_file, test): namespace = resource.get("Namespace") kind = resource.get("Kind") name = resource.get("Name") + resource_name = "" if namespace: resource_name = f"{namespace} / " if kind: diff --git a/unittests/scans/trivy/kubernetes_cluster_scoped_resources.json b/unittests/scans/trivy/kubernetes_cluster_scoped_resources.json new file mode 100644 index 00000000000..8ae32fa3b73 --- /dev/null +++ b/unittests/scans/trivy/kubernetes_cluster_scoped_resources.json @@ -0,0 +1,132 @@ +{ + "ClusterName": "kind-voc-lab", + "Resources": [ + { + "Kind": "ClusterRole", + "Name": "system:controller:bootstrap-signer", + "Results": [ + { + "Target": "ClusterRole/system:controller:bootstrap-signer", + "Class": "config", + "Type": "rbac", + "MisconfSummary": { + "Successes": 0, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Kubernetes Security Check", + "ID": "KSV113", + "Title": "Manage namespace secrets", + "Description": "Check whether role permits managing namespace secrets", + "Message": "ClusterRole 'system:controller:bootstrap-signer' shouldn't have access to manage secrets in namespace 'kube-system'", + "Namespace": "builtin.kubernetes.KSV113", + "Query": "data.builtin.kubernetes.KSV113.deny", + "Resolution": "Manage namespace secrets are not allowed. Remove resource 'secrets' from role", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv113", + "References": [ + "https://kubernetes.io/docs/concepts/security/rbac-good-practices/", + "https://avd.aquasec.com/misconfig/ksv113" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 1, + "EndLine": 1 + } + } + ] + } + ] + }, + { + "Namespace": "default", + "Kind": "Deployment", + "Name": "redis-follower", + "Results": [ + { + "Target": "Deployment/redis-follower", + "Class": "config", + "Type": "kubernetes", + "MisconfSummary": { + "Successes": 23, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Kubernetes Security Check", + "ID": "KSV001", + "Title": "Process can elevate its own privileges", + "Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", + "Message": "Container 'follower' of Deployment 'redis-follower' should set 'securityContext.allowPrivilegeEscalation' to false", + "Namespace": "builtin.kubernetes.KSV001", + "Query": "data.builtin.kubernetes.KSV001.deny", + "Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001", + "References": [ + "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", + "https://avd.aquasec.com/misconfig/ksv001" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 132, + "EndLine": 143 + } + } + ] + } + ] + }, + { + "Kind": "ClusterRoleBinding", + "Name": "cluster-admin", + "Results": [ + { + "Target": "ClusterRoleBinding/cluster-admin", + "Class": "config", + "Type": "rbac", + "MisconfSummary": { + "Successes": 0, + "Failures": 1, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "Kubernetes Security Check", + "ID": "KSV111", + "Title": "User with admin access", + "Description": "ClusterRoleBindings with cluster-admin role is being used", + "Message": "ClusterRoleBinding 'cluster-admin' should not bind to role 'cluster-admin'", + "Namespace": "builtin.kubernetes.KSV111", + "Query": "data.builtin.kubernetes.KSV111.deny", + "Resolution": "Create a role which gives less access to the user", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv111", + "References": [ + "https://kubernetes.io/docs/concepts/security/rbac-good-practices/", + "https://avd.aquasec.com/misconfig/ksv111" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Provider": "Kubernetes", + "Service": "general", + "StartLine": 1, + "EndLine": 1 + } + } + ] + } + ] + } + ] +} diff --git a/unittests/tools/test_trivy_parser.py b/unittests/tools/test_trivy_parser.py index 9aef80e263e..64906db3506 100644 --- a/unittests/tools/test_trivy_parser.py +++ b/unittests/tools/test_trivy_parser.py @@ -180,6 +180,26 @@ def test_kubernetes(self): self.assertIsNone(finding.component_version) self.assertEqual("default / Deployment / redis-follower", finding.service) + def test_kubernetes_cluster_scoped_resources(self): + """Resources whose first entry is cluster-scoped (no Namespace) must parse without UnboundLocalError""" + with sample_path("kubernetes_cluster_scoped_resources.json").open(encoding="utf-8") as test_file: + parser = TrivyParser() + findings = parser.get_findings(test_file, Test()) + self.assertEqual(len(findings), 3) + finding = findings[0] + self.assertEqual("KSV113 - Manage namespace secrets", finding.title) + self.assertEqual("Medium", finding.severity) + self.assertEqual("ClusterRole / system:controller:bootstrap-signer", finding.service) + finding = findings[1] + self.assertEqual("KSV001 - Process can elevate its own privileges", finding.title) + self.assertEqual("default / Deployment / redis-follower", finding.service) + # a cluster-scoped resource following a namespaced one must not + # inherit the previous resource's namespace/kind/name + finding = findings[2] + self.assertEqual("KSV111 - User with admin access", finding.title) + self.assertEqual("Low", finding.severity) + self.assertEqual("ClusterRoleBinding / cluster-admin", finding.service) + def test_license_scheme(self): with sample_path("license_scheme.json").open(encoding="utf-8") as test_file: parser = TrivyParser()