Repository: zegl/kube-score
Pull request #635 (2024-11-25 13:00:18)

Feat/split container probes identical check

```
RELNOTE: separating test cases into separate validation rules, this will add the ability to exclude validations more granularly
```


vercel[bot]: @gruzchik is attempting to deploy a commit to the **Gustav Westling's projects** Team on [Vercel](https://vercel.com).

A member of the Team first needs to [authorize it](https://vercel.com/git/authorize?team=Gustav%20Westling's%20projects&type=github&job=%7B%22headInfo%22%3A%7B%22sha%22%3A%22edadf9e32d25e60a2ae0817aaeee255d92982e8a%22%7D%2C%22id%22%3A%22QmahSMZsEhVtbYN3hB1ne5iXstFuHy8vTWcAM7xGZFhgFh%22%2C%22org%22%3A%22zegl%22%2C%22prId%22%3A635%2C%22repo%22%3A%22kube-score%22%7D).



Patch:
diff --git a/README_CHECKS.md b/README_CHECKS.md
index d82f158..72a2007 100644
--- a/README_CHECKS.md
+++ b/README_CHECKS.md
@@ -22,6 +22,7 @@
 | pod-networkpolicy | Pod | Makes sure that all Pods are targeted by a NetworkPolicy | default |
 | networkpolicy-targets-pod | NetworkPolicy | Makes sure that all NetworkPolicies targets at least one Pod | default |
 | pod-probes | Pod | Makes sure that all Pods have safe probe configurations | default |
+| pod-probes-identical | Pod | Makes sure that readiness and liveness probes are not identical | default |
 | container-security-context-user-group-id | Pod | Makes sure that all pods have a security context with valid UID and GID set  | default |
 | container-security-context-privileged | Pod | Makes sure that all pods have a unprivileged security context set | default |
 | container-security-context-readonlyrootfilesystem | Pod | Makes sure that all pods have a security context with read only filesystem set | default |
diff --git a/go.mod b/go.mod
index 1a1d515..5bb8d96 100644
--- a/go.mod
+++ b/go.mod
@@ -4,16 +4,16 @@ require (
 	github.com/buildkite/terminal-to-html v3.2.0+incompatible
 	github.com/eidolon/wordwrap v0.0.0-20161011182207-e0f54129b8bb
 	github.com/fatih/color v1.18.0
-	github.com/google/go-cmp v0.6.0
+	github.com/google/go-cmp v0.7.0
 	github.com/jstemmer/go-junit-report/v2 v2.1.0
 	github.com/mattn/go-isatty v0.0.20
-	github.com/spf13/pflag v1.0.5
+	github.com/spf13/pflag v1.0.6
 	github.com/stretchr/testify v1.10.0
-	golang.org/x/term v0.26.0
+	golang.org/x/term v0.31.0
 	gopkg.in/yaml.v3 v3.0.1
-	k8s.io/api v0.31.3
-	k8s.io/apimachinery v0.31.3
-	k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
+	k8s.io/api v0.32.3
+	k8s.io/apimachinery v0.32.3
+	k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
 )
 
 require (
@@ -23,19 +23,19 @@ require (
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/kr/text v0.2.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	golang.org/x/net v0.26.0 // indirect
-	golang.org/x/sys v0.27.0 // indirect
-	golang.org/x/text v0.16.0 // indirect
+	golang.org/x/net v0.36.0 // indirect
+	golang.org/x/sys v0.32.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
-	gopkg.in/yaml.v2 v2.4.0 // indirect
 	k8s.io/klog/v2 v2.130.1 // indirect
-	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
-	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
+	sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
 	sigs.k8s.io/yaml v1.4.0 // indirect
 )
 
diff --git a/go.sum b/go.sum
index b3cfaaf..b875203 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,6 @@
 github.com/buildkite/terminal-to-html v3.2.0+incompatible h1:WdXzl7ZmYzCAz4pElZosPaUlRTW+qwVx/SkQSCa1jXs=
 github.com/buildkite/terminal-to-html v3.2.0+incompatible/go.mod h1:BFFdFecOxCgjdcarqI+8izs6v85CU/1RA/4Bqh4GR7E=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -16,8 +17,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -46,8 +47,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
 github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -65,8 +66,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
-golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -75,14 +76,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
-golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
-golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
+golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
-golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -96,22 +97,19 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8=
-k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE=
-k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4=
-k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
+k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
+k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
+k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
+k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
-k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
-k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
-sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
-sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
-sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
+k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
+k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
+sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
 sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
 sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/score/probes/probes.go b/score/probes/probes.go
index f19927a..a30f99a 100644
--- a/score/probes/probes.go
+++ b/score/probes/probes.go
@@ -8,8 +8,10 @@ import (
 	corev1 "k8s.io/api/core/v1"
 )
 
+// Register registers the pod checks, including the new one for identical probes.
 func Register(allChecks *checks.Checks, services ks.Services) {
 	allChecks.RegisterPodCheck("Pod Probes", `Makes sure that all Pods have safe probe configurations`, containerProbes(services.Services()))
+	allChecks.RegisterPodCheck("Pod Probes Identical", `Container has the same readiness and liveness probe`, containerProbesIdentical(services.Services()))
 }
 
 // containerProbes returns a function that checks if all probes are defined correctly in the Pod.
@@ -31,72 +33,14 @@ func containerProbes(allServices []ks.Service) func(ks.PodSpecer) (scorecard.Tes
 
 		hasReadinessProbe := false
 		hasLivenessProbe := false
-		probesAreIdentical := false
-		isTargetedByService := false
-
-		for _, s := range allServices {
-			if podIsTargetedByService(podTemplate, s.Service()) {
-				isTargetedByService = true
-				break
-			}
-		}
+		isTargetedByService := isTargetedByService(allServices, podTemplate)
 
+		// Check probes for each container
 		for _, container := range allContainers {
-			if container.ReadinessProbe != nil {
-				hasReadinessProbe = true
-			}
-
-			if container.LivenessProbe != nil {
-				hasLivenessProbe = true
-			}
-
-			if container.ReadinessProbe != nil && container.LivenessProbe != nil {
-
-				r := container.ReadinessProbe
-				l := container.LivenessProbe
-
-				if r.HTTPGet != nil && l.HTTPGet != nil {
-					if r.HTTPGet.Path == l.HTTPGet.Path &&
-						r.HTTPGet.Port.IntValue() == l.HTTPGet.Port.IntValue() {
-						probesAreIdentical = true
-					}
-				}
-
-				if r.TCPSocket != nil && l.TCPSocket != nil {
-					if r.TCPSocket.Port == l.TCPSocket.Port {
-						probesAreIdentical = true
-					}
-				}
-
-				if r.Exec != nil && l.Exec != nil {
-					if len(r.Exec.Command) == len(l.Exec.Command) {
-						hasDifferent := false
-						for i, v := range r.Exec.Command {
-							if l.Exec.Command[i] != v {
-								hasDifferent = true
-								break
-							}
-						}
-
-						if !hasDifferent {
-							probesAreIdentical = true
-						}
-					}
-				}
-
-			}
-		}
-
-		if hasLivenessProbe && hasReadinessProbe && probesAreIdentical {
-			score.Grade = scorecard.GradeCritical
-			score.AddCommentWithURL(
-				"", "Container has the same readiness and liveness probe",
-				"Using the same probe for liveness and readiness is very likely dangerous. Generally it's better to avoid the livenessProbe than re-using the readinessProbe.",
-				"https://github.com/zegl/kube-score/blob/master/README_PROBES.md",
-			)
-			return score, nil
+			hasReadinessProbe, hasLivenessProbe = checkBasicProbes(container, hasReadinessProbe, hasLivenessProbe)
 		}
 
+		// If pod isn't targeted by a service, skip probe checks
 		if !isTargetedByService {
 			score.Grade = scorecard.GradeAllOK
 			score.Skipped = true
@@ -104,6 +48,7 @@ func containerProbes(allServices []ks.Service) func(ks.PodSpecer) (scorecard.Tes
 			return score, nil
 		}
 
+		// Evaluate probe checks
 		if !hasReadinessProbe {
 			score.Grade = scorecard.GradeCritical
 			score.AddCommentWithURL("", "Container is missing a readinessProbe",
@@ -126,11 +71,94 @@ func containerProbes(allServices []ks.Service) func(ks.PodSpecer) (scorecard.Tes
 		}
 
 		score.Grade = scorecard.GradeAllOK
+		return score, nil
+	}
+}
+
+// containerProbesIdentical checks if the container's readiness and liveness probes are identical.
+func containerProbesIdentical(allServices []ks.Service) func(ks.PodSpecer) (scorecard.TestScore, error) {
+	return func(ps ks.PodSpecer) (score scorecard.TestScore, err error) {
+		typeMeta := ps.GetTypeMeta()
+		if typeMeta.Kind == "CronJob" && typeMeta.GroupVersionKind().Group == "batch" || typeMeta.Kind == "Job" && typeMeta.GroupVersionKind().Group == "batch" {
+			score.Grade = scorecard.GradeAllOK
+			return score, nil
+		}
+
+		podTemplate := ps.GetPodTemplateSpec()
+		allContainers := podTemplate.Spec.InitContainers
+		allContainers = append(allContainers, podTemplate.Spec.Containers...)
+
+		probesAreIdentical := false
+		for _, container := range allContainers {
+			if container.ReadinessProbe != nil && container.LivenessProbe != nil {
+				if areProbesIdentical(container.ReadinessProbe, container.LivenessProbe) {
+					probesAreIdentical = true
+					break
+				}
+			}
+		}
 
+		// If probes are identical, mark it as a critical issue
+		if probesAreIdentical {
+			score.Grade = scorecard.GradeCritical
+			score.AddCommentWithURL(
+				"", "Container has the same readiness and liveness probe",
+				"Using the same probe for liveness and readiness is very likely dangerous. It's generally better to avoid re-using the same probe.",
+				"https://github.com/zegl/kube-score/blob/master/README_PROBES.md",
+			)
+			return score, nil
+		}
+
+		// No identical probes found, return OK grade
+		score.Grade = scorecard.GradeAllOK
 		return score, nil
 	}
 }
 
+// areProbesIdentical checks if readiness and liveness probes are identical.
+func areProbesIdentical(r, l *corev1.Probe) bool {
+	if r.HTTPGet != nil && l.HTTPGet != nil {
+		return r.HTTPGet.Path == l.HTTPGet.Path && r.HTTPGet.Port.IntValue() == l.HTTPGet.Port.IntValue()
+	}
+	if r.TCPSocket != nil && l.TCPSocket != nil {
+		return r.TCPSocket.Port == l.TCPSocket.Port
+	}
+	if r.Exec != nil && l.Exec != nil {
+		if len(r.Exec.Command) == len(l.Exec.Command) {
+			for i, v := range r.Exec.Command {
+				if l.Exec.Command[i] != v {
+					return false
+				}
+			}
+			return true
+		}
+	}
+	return false
+}
+
+// checkBasicProbes checks for the presence of readiness and liveness probes.
+func checkBasicProbes(container corev1.Container, hasReadinessProbe, hasLivenessProbe bool) (bool, bool) {
+	if container.ReadinessProbe != nil {
+		hasReadinessProbe = true
+	}
+
+	if container.LivenessProbe != nil {
+		hasLivenessProbe = true
+	}
+
+	return hasReadinessProbe, hasLivenessProbe
+}
+
+// isTargetedByService checks if the pod is targeted by any of the services.
+func isTargetedByService(allServices []ks.Service, podTemplate corev1.PodTemplateSpec) bool {
+	for _, s := range allServices {
+		if podIsTargetedByService(podTemplate, s.Service()) {
+			return true
+		}
+	}
+	return false
+}
+
 func podIsTargetedByService(pod corev1.PodTemplateSpec, service corev1.Service) bool {
 	if pod.Namespace != service.Namespace {
 		return false


Test patch:
diff --git a/score/probe_test.go b/score/probe_test.go
index 9d835ef..4ff8196 100644
--- a/score/probe_test.go
+++ b/score/probe_test.go
@@ -24,21 +24,21 @@ func TestProbesPodMissingReady(t *testing.T) {
 
 func TestProbesPodIdenticalHTTP(t *testing.T) {
 	t.Parallel()
-	comments := testExpectedScore(t, "pod-probes-identical-http.yaml", "Pod Probes", scorecard.GradeCritical)
+	comments := testExpectedScore(t, "pod-probes-identical-http.yaml", "Pod Probes Identical", scorecard.GradeCritical)
 	assert.Len(t, comments, 1)
 	assert.Equal(t, "Container has the same readiness and liveness probe", comments[0].Summary)
 }
 
 func TestProbesPodIdenticalTCP(t *testing.T) {
 	t.Parallel()
-	comments := testExpectedScore(t, "pod-probes-identical-tcp.yaml", "Pod Probes", scorecard.GradeCritical)
+	comments := testExpectedScore(t, "pod-probes-identical-tcp.yaml", "Pod Probes Identical", scorecard.GradeCritical)
 	assert.Len(t, comments, 1)
 	assert.Equal(t, "Container has the same readiness and liveness probe", comments[0].Summary)
 }
 
 func TestProbesPodIdenticalExec(t *testing.T) {
 	t.Parallel()
-	comments := testExpectedScore(t, "pod-probes-identical-exec.yaml", "Pod Probes", scorecard.GradeCritical)
+	comments := testExpectedScore(t, "pod-probes-identical-exec.yaml", "Pod Probes Identical", scorecard.GradeCritical)
 	assert.Len(t, comments, 1)
 	assert.Equal(t, "Container has the same readiness and liveness probe", comments[0].Summary)
 }