Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package goldpinger for openSUSE:Factory 
checked in at 2026-09-21 12:09:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/goldpinger (Old)
 and      /work/SRC/openSUSE:Factory/.goldpinger.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "goldpinger"

Mon Sep 21 12:09:29 2026 rev:6 rq:1379201 version:3.11.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/goldpinger/goldpinger.changes    2026-04-25 
21:35:56.010263119 +0200
+++ /work/SRC/openSUSE:Factory/.goldpinger.new.383539/goldpinger.changes        
2026-09-21 12:09:32.251979764 +0200
@@ -1,0 +2,8 @@
+Sun Sep 20 07:23:08 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 3.11.3:
+  * fix(logging): install configured logger as zap global
+  * fix(heatmap): skip unknown peer-reported destinations
+  * fix(client): bound untrusted peer results in CheckCluster
+
+-------------------------------------------------------------------

Old:
----
  goldpinger-3.11.2.obscpio

New:
----
  goldpinger-3.11.3.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ goldpinger.spec ++++++
--- /var/tmp/diff_new_pack.gxrjqE/_old  2026-09-21 12:09:33.403027657 +0200
+++ /var/tmp/diff_new_pack.gxrjqE/_new  2026-09-21 12:09:33.406027782 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           goldpinger
-Version:        3.11.2
+Version:        3.11.3
 Release:        0
 Summary:        Tests and displays connectivity between nodes in a Kubernetes 
cluster
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.gxrjqE/_old  2026-09-21 12:09:33.445029404 +0200
+++ /var/tmp/diff_new_pack.gxrjqE/_new  2026-09-21 12:09:33.449029571 +0200
@@ -1,9 +1,9 @@
 <services>
   <service name="obs_scm" mode="manual">
-    <param name="url">https://github.com/bloomberg/goldpinger</param>
+    <param name="url">https://github.com/bloomberg/goldpinger.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v3.11.2</param>
+    <param name="revision">refs/tags/v3.11.3</param>
     <param name="match-tag">v*</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.gxrjqE/_old  2026-09-21 12:09:33.476030694 +0200
+++ /var/tmp/diff_new_pack.gxrjqE/_new  2026-09-21 12:09:33.478030777 +0200
@@ -1,6 +1,8 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/bloomberg/goldpinger</param>
-              <param 
name="changesrevision">6f2fec60743f13d2235345b09ffc4f797d617bd9</param></service></servicedata>
+              <param 
name="changesrevision">6f2fec60743f13d2235345b09ffc4f797d617bd9</param></service><service
 name="tar_scm">
+                <param 
name="url">https://github.com/bloomberg/goldpinger.git</param>
+              <param 
name="changesrevision">2475792dbdd0c893982fda0750a473e2d093d67c</param></service></servicedata>
 (No newline at EOF)
 

++++++ goldpinger-3.11.2.obscpio -> goldpinger-3.11.3.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goldpinger-3.11.2/Makefile 
new/goldpinger-3.11.3/Makefile
--- old/goldpinger-3.11.2/Makefile      2026-04-23 17:37:08.000000000 +0200
+++ new/goldpinger-3.11.3/Makefile      2026-09-14 20:00:33.000000000 +0200
@@ -1,5 +1,5 @@
 name ?= goldpinger
-version ?= v3.11.2
+version ?= v3.11.3
 bin ?= goldpinger
 pkg ?= "github.com/bloomberg/goldpinger"
 tag = $(name):$(version)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goldpinger-3.11.2/charts/goldpinger/Chart.yaml 
new/goldpinger-3.11.3/charts/goldpinger/Chart.yaml
--- old/goldpinger-3.11.2/charts/goldpinger/Chart.yaml  2026-04-23 
17:37:08.000000000 +0200
+++ new/goldpinger-3.11.3/charts/goldpinger/Chart.yaml  2026-09-14 
20:00:33.000000000 +0200
@@ -1,7 +1,7 @@
 apiVersion: v1
 name: goldpinger
-appVersion: "3.11.2"
-version: 1.1.2
+appVersion: "3.11.3"
+version: 1.1.3
 description: Goldpinger is a tool to help debug, troubleshoot and visualize 
network connectivity and slowness issues.
 home: https://github.com/bloomberg/goldpinger
 sources:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goldpinger-3.11.2/cmd/goldpinger/main.go 
new/goldpinger-3.11.3/cmd/goldpinger/main.go
--- old/goldpinger-3.11.2/cmd/goldpinger/main.go        2026-04-23 
17:37:08.000000000 +0200
+++ new/goldpinger-3.11.3/cmd/goldpinger/main.go        2026-09-14 
20:00:33.000000000 +0200
@@ -110,6 +110,11 @@
        }
        defer logger.Sync()
 
+       // Install this logger as the global returned by zap.L(). Without this,
+       // every zap.L() call throughout the goldpinger package resolves to 
zap's
+       // default no-op logger and those logs are silently dropped.
+       zap.ReplaceGlobals(logger)
+
        undo := zap.RedirectStdLog(logger)
        defer undo()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goldpinger-3.11.2/pkg/goldpinger/client.go 
new/goldpinger-3.11.3/pkg/goldpinger/client.go
--- old/goldpinger-3.11.2/pkg/goldpinger/client.go      2026-04-23 
17:37:08.000000000 +0200
+++ new/goldpinger-3.11.3/pkg/goldpinger/client.go      2026-09-14 
20:00:33.000000000 +0200
@@ -54,12 +54,17 @@
 
 // CheckCluster does a CheckNeighboursNeighbours and analyses results to 
produce a binary OK or not OK
 func CheckCluster(ctx context.Context) *models.ClusterHealthResults {
+       return checkClusterHealth(ctx, SelectPods())
+}
+
+// checkClusterHealth calls every pod in selectedPods and aggregates the 
responses
+// into a binary OK or not OK for the cluster.
+func checkClusterHealth(ctx context.Context, selectedPods 
map[string]*GoldpingerPod) *models.ClusterHealthResults {
        start := time.Now()
        output := models.ClusterHealthResults{
                GeneratedAt: strfmt.DateTime(start),
                OK:          true,
        }
-       selectedPods := SelectPods()
 
        // precompute the expected set of nodes
        expectedNodes := []string{}
@@ -75,41 +80,108 @@
        if len(checkAll.Responses) < 1 {
                output.OK = false
        }
-       for _, resp := range checkAll.Responses {
+       for podName, resp := range checkAll.Responses {
+               verdict := evaluatePeerResponse(podName, resp, expectedNodes)
                // 1. check that all nodes report OK
-               if *resp.OK {
-                       output.NodesHealthy = append(output.NodesHealthy, 
resp.HostIP.String())
+               if verdict.reachable {
+                       output.NodesHealthy = append(output.NodesHealthy, 
verdict.hostIP)
                } else {
-                       output.NodesUnhealthy = append(output.NodesUnhealthy, 
resp.HostIP.String())
+                       output.NodesUnhealthy = append(output.NodesUnhealthy, 
verdict.hostIP)
                        output.OK = false
                }
                output.NodesTotal++
                // 2. check that all nodes report the expected peers
-               // on error, there might be no response from the node
-               if resp.Response == nil {
-                       output.OK = false
-                       continue
-               }
-               // if we get a response, let's check we get the expected nodes
-               observedNodes := []string{}
-               for _, peer := range resp.Response.PodResults {
-                       observedNodes = append(observedNodes, 
string(peer.HostIP))
-               }
-               sort.Strings(observedNodes)
-               if len(observedNodes) != len(expectedNodes) {
+               if !verdict.reportsExpectedNodes {
                        output.OK = false
                }
-               for i, val := range observedNodes {
-                       if val != expectedNodes[i] {
-                               output.OK = false
-                               break
-                       }
-               }
        }
        output.DurationNs = time.Since(start).Nanoseconds()
        return &output
 }
 
+// peerVerdict is the outcome of inspecting a single peer's response.
+type peerVerdict struct {
+       // hostIP is the node the peer was discovered on, as we know it - never
+       // as the peer reports it.
+       hostIP string
+       // reachable is true if the peer answered our /check call successfully.
+       reachable bool
+       // reportsExpectedNodes is true if the peer's view of the cluster 
matches ours.
+       reportsExpectedNodes bool
+}
+
+// evaluatePeerResponse inspects a single peer's response to our /check call.
+//
+// Peer identity rests on kubernetes metadata and network reachability rather
+// than on any cryptographic authentication, so anything able to join the
+// discovered peer set can serve an arbitrary body here. The response is
+// therefore untrusted input: nothing below indexes or dereferences a
+// peer-controlled value without first bounding its shape, and any panic that
+// still gets through is recovered so one hostile peer cannot take the whole
+// process down (CWE-248).
+func evaluatePeerResponse(podName string, resp models.CheckAllPodResult, 
expectedNodes []string) (verdict peerVerdict) {
+       defer func() {
+               if recovered := recover(); recovered != nil {
+                       zap.L().Error(
+                               "Recovered from panic while processing a peer 
response",
+                               zap.String("op", "checkCluster"),
+                               zap.String("name", podName),
+                               zap.String("hostIP", verdict.hostIP),
+                               zap.Any("panic", recovered),
+                       )
+                       CountError("checkClusterPeerResponse")
+                       verdict.reachable = false
+                       verdict.reportsExpectedNodes = false
+               }
+       }()
+
+       verdict.hostIP = resp.HostIP.String()
+       verdict.reachable = resp.OK != nil && *resp.OK
+       // on error, there might be no response from the node
+       if resp.Response == nil {
+               return
+       }
+
+       verdict.reportsExpectedNodes = reportsExpectedNodes(expectedNodes, 
resp.Response.PodResults)
+       if !verdict.reportsExpectedNodes {
+               zap.L().Warn(
+                       "Peer reported an unexpected set of nodes",
+                       zap.String("op", "checkCluster"),
+                       zap.String("name", podName),
+                       zap.String("hostIP", verdict.hostIP),
+                       zap.Int("observedPodResults", 
len(resp.Response.PodResults)),
+                       zap.Int("expectedNodes", len(expectedNodes)),
+               )
+       }
+       return
+}
+
+// reportsExpectedNodes reports whether the host IPs in an untrusted peer's pod
+// results are exactly the nodes we expect, compared as sorted multisets.
+// expectedNodes must already be sorted.
+//
+// The count is compared first, both because a peer that reports a different
+// number of nodes already disagrees with us and because it bounds the work we
+// do - and the slice we index - to the number of pods we discovered ourselves.
+// A peer that returns extra or unexpected entries is simply not in agreement;
+// it must never be able to walk this loop off the end of expectedNodes.
+func reportsExpectedNodes(expectedNodes []string, podResults 
map[string]models.PodResult) bool {
+       if len(podResults) != len(expectedNodes) {
+               return false
+       }
+       observedNodes := make([]string, 0, len(expectedNodes))
+       for _, peer := range podResults {
+               observedNodes = append(observedNodes, string(peer.HostIP))
+       }
+       sort.Strings(observedNodes)
+       for i, val := range observedNodes {
+               if val != expectedNodes[i] {
+                       return false
+               }
+       }
+       return true
+}
+
 // PingAllPodsResult holds results from pinging all nodes
 type PingAllPodsResult struct {
        podName   string
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goldpinger-3.11.2/pkg/goldpinger/client_test.go 
new/goldpinger-3.11.3/pkg/goldpinger/client_test.go
--- old/goldpinger-3.11.2/pkg/goldpinger/client_test.go 1970-01-01 
01:00:00.000000000 +0100
+++ new/goldpinger-3.11.3/pkg/goldpinger/client_test.go 2026-09-14 
20:00:33.000000000 +0200
@@ -0,0 +1,286 @@
+// Copyright 2018 Bloomberg Finance L.P.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package goldpinger
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+       "net/http"
+       "net/http/httptest"
+       "net/url"
+       "strconv"
+       "testing"
+       "time"
+
+       "github.com/bloomberg/goldpinger/v3/pkg/models"
+       "github.com/go-openapi/strfmt"
+)
+
+const (
+       // peerPodName is the name goldpinger discovers for the peer under test.
+       peerPodName = "goldpinger-peer"
+       // peerHostIP is the host IP goldpinger discovers for the peer under 
test.
+       // The peer is dialled over loopback, but is reported to live on this 
node.
+       peerHostIP = "10.0.0.1"
+       // peerLoopbackIP is the address the peer's HTTP server actually 
listens on.
+       peerLoopbackIP = "127.0.0.1"
+       // checkPath is the path the generated client calls for a /check.
+       checkPath = "/check"
+
+       testCheckTimeout    = 5 * time.Second
+       testCheckAllTimeout = 10 * time.Second
+       testHostname        = "goldpinger-under-test"
+
+       // floodedPodResults is the number of pod results a hostile peer crams 
into
+       // a single /check response. Any value greater than the number of pods 
we
+       // discovered ourselves is enough to walk off the end of the expected 
list.
+       floodedPodResults = 5000
+)
+
+// startPeer runs a real HTTP server answering GET /check with the given
+// handler and points GoldpingerConfig at its port, so the generated swagger
+// client dials it over a real connection. It returns the single-pod peer set
+// that checkClusterHealth should be given.
+func startPeer(t *testing.T, handler http.Handler) map[string]*GoldpingerPod {
+       t.Helper()
+
+       server := httptest.NewServer(handler)
+       t.Cleanup(server.Close)
+
+       parsed, err := url.Parse(server.URL)
+       if err != nil {
+               t.Fatalf("could not parse peer server URL %q: %v", server.URL, 
err)
+       }
+       port, err := strconv.Atoi(parsed.Port())
+       if err != nil {
+               t.Fatalf("could not parse peer server port from %q: %v", 
server.URL, err)
+       }
+
+       originalConfig := GoldpingerConfig
+       t.Cleanup(func() { GoldpingerConfig = originalConfig })
+       GoldpingerConfig.Port = port
+       GoldpingerConfig.UseHostIP = false
+       GoldpingerConfig.Hostname = testHostname
+       GoldpingerConfig.CheckTimeout = testCheckTimeout
+       GoldpingerConfig.CheckAllTimeout = testCheckAllTimeout
+
+       // Checking a peer registers per-peer label sets on package-global
+       // collectors. Drop them again while the config they were labelled with 
is
+       // still in place, so we don't leak label sets into other tests. 
Registered
+       // after the config restore above so cleanups run in that order.
+       t.Cleanup(func() { DeletePeerMetrics(peerHostIP, peerLoopbackIP) })
+
+       return map[string]*GoldpingerPod{
+               peerPodName: {Name: peerPodName, PodIP: peerLoopbackIP, HostIP: 
peerHostIP},
+       }
+}
+
+// checkHandler serves body as the JSON response to GET /check, and fails the
+// test if the client asks for anything else.
+func checkHandler(t *testing.T, body []byte) http.Handler {
+       t.Helper()
+       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               if r.URL.Path != checkPath {
+                       t.Errorf("peer received unexpected request for %q, want 
%q", r.URL.Path, checkPath)
+                       w.WriteHeader(http.StatusNotFound)
+                       return
+               }
+               w.Header().Set("Content-Type", "application/json")
+               w.WriteHeader(http.StatusOK)
+               if _, err := w.Write(body); err != nil {
+                       t.Errorf("peer could not write response body: %v", err)
+               }
+       })
+}
+
+// peerCheckResults builds a /check response body listing one pod result per
+// host IP given, as an honest peer would report its own neighbours.
+func peerCheckResults(t *testing.T, hostIPs ...string) []byte {
+       t.Helper()
+
+       ok := true
+       results := models.CheckResults{PodResults: 
make(map[string]models.PodResult, len(hostIPs))}
+       for index, hostIP := range hostIPs {
+               results.PodResults[fmt.Sprintf("pod-%d", index)] = 
models.PodResult{
+                       OK:         &ok,
+                       HostIP:     strfmt.IPv4(hostIP),
+                       PodIP:      strfmt.IPv4(hostIP),
+                       StatusCode: http.StatusOK,
+               }
+       }
+
+       body, err := json.Marshal(results)
+       if err != nil {
+               t.Fatalf("could not marshal peer check results: %v", err)
+       }
+       return body
+}
+
+// syntheticHostIPs generates count distinct host IPs, for peers that report
+// far more nodes than actually exist.
+func syntheticHostIPs(count int) []string {
+       hostIPs := make([]string, 0, count)
+       for i := 0; i < count; i++ {
+               hostIPs = append(hostIPs, fmt.Sprintf("10.%d.%d.%d", 1+i/65536, 
(i/256)%256, i%256))
+       }
+       return hostIPs
+}
+
+// TestCheckClusterHealthWithHostilePeer covers /cluster_health aggregation 
when
+// the single discovered peer returns crafted results. A workload that can join
+// the peer set controls this body entirely, so no shape of it may panic the
+// process (CWE-248) — it may only mark the cluster not OK.
+func TestCheckClusterHealthWithHostilePeer(t *testing.T) {
+       tests := []struct {
+               name string
+               // body is the raw bytes the peer serves for GET /check.
+               body []byte
+               // wantOK is the expected aggregated cluster verdict.
+               wantOK bool
+       }{
+               {
+                       name:   "peer reports exactly the expected node",
+                       body:   peerCheckResults(t, peerHostIP),
+                       wantOK: true,
+               },
+               {
+                       name:   "peer reports extra pod results",
+                       body:   peerCheckResults(t, peerHostIP, "10.0.0.2", 
"10.0.0.3"),
+                       wantOK: false,
+               },
+               {
+                       name:   "peer reports no pod results",
+                       body:   peerCheckResults(t),
+                       wantOK: false,
+               },
+               {
+                       name:   "peer reports a node we never discovered",
+                       body:   peerCheckResults(t, "10.9.9.9"),
+                       wantOK: false,
+               },
+               {
+                       name:   "peer floods pod results",
+                       body:   peerCheckResults(t, 
syntheticHostIPs(floodedPodResults)...),
+                       wantOK: false,
+               },
+               {
+                       name:   "peer returns a null body",
+                       body:   []byte("null"),
+                       wantOK: false,
+               },
+               {
+                       name:   "peer returns an empty object",
+                       body:   []byte("{}"),
+                       wantOK: false,
+               },
+               {
+                       name:   "peer returns pod results with empty host IPs",
+                       body:   []byte(`{"podResults":{"a":{},"b":{}}}`),
+                       wantOK: false,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       pods := startPeer(t, checkHandler(t, tt.body))
+
+                       // A panic anywhere in here fails the test: that is the 
bug under test.
+                       output := checkClusterHealth(context.Background(), pods)
+
+                       if output.OK != tt.wantOK {
+                               t.Errorf("cluster OK = %v, want %v (healthy=%v 
unhealthy=%v)",
+                                       output.OK, tt.wantOK, 
output.NodesHealthy, output.NodesUnhealthy)
+                       }
+                       if output.NodesTotal != 1 {
+                               t.Errorf("NodesTotal = %d, want 1 — exactly one 
peer was discovered", output.NodesTotal)
+                       }
+                       // The peer answered with a 200, so it counts as a 
reachable node
+                       // regardless of how nonsensical its payload was.
+                       if len(output.NodesHealthy) != 1 || 
output.NodesHealthy[0] != peerHostIP {
+                               t.Errorf("NodesHealthy = %v, want [%s]", 
output.NodesHealthy, peerHostIP)
+                       }
+                       if len(output.NodesUnhealthy) != 0 {
+                               t.Errorf("NodesUnhealthy = %v, want []", 
output.NodesUnhealthy)
+                       }
+               })
+       }
+}
+
+// TestCheckClusterHealthWithUnreachablePeer covers peers that fail the /check
+// call outright: they must be reported unhealthy rather than crashing or
+// silently passing.
+func TestCheckClusterHealthWithUnreachablePeer(t *testing.T) {
+       tests := []struct {
+               name    string
+               handler http.Handler
+       }{
+               {
+                       name: "peer returns a server error",
+                       handler: http.HandlerFunc(func(w http.ResponseWriter, r 
*http.Request) {
+                               w.WriteHeader(http.StatusInternalServerError)
+                       }),
+               },
+               {
+                       name: "peer returns a body that is not JSON",
+                       handler: http.HandlerFunc(func(w http.ResponseWriter, r 
*http.Request) {
+                               w.Header().Set("Content-Type", 
"application/json")
+                               if _, err := w.Write([]byte("this is not 
json")); err != nil {
+                                       t.Errorf("peer could not write response 
body: %v", err)
+                               }
+                       }),
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       pods := startPeer(t, tt.handler)
+
+                       output := checkClusterHealth(context.Background(), pods)
+
+                       if output.OK {
+                               t.Errorf("cluster OK = true, want false for an 
unreachable peer")
+                       }
+                       if output.NodesTotal != 1 {
+                               t.Errorf("NodesTotal = %d, want 1", 
output.NodesTotal)
+                       }
+                       if len(output.NodesUnhealthy) != 1 || 
output.NodesUnhealthy[0] != peerHostIP {
+                               t.Errorf("NodesUnhealthy = %v, want [%s]", 
output.NodesUnhealthy, peerHostIP)
+                       }
+                       if len(output.NodesHealthy) != 0 {
+                               t.Errorf("NodesHealthy = %v, want []", 
output.NodesHealthy)
+                       }
+               })
+       }
+}
+
+// TestCheckClusterHealthWithNoPeers verifies the degenerate case: with nothing
+// to talk to, we should at least not report the cluster as OK.
+func TestCheckClusterHealthWithNoPeers(t *testing.T) {
+       originalConfig := GoldpingerConfig
+       t.Cleanup(func() { GoldpingerConfig = originalConfig })
+       GoldpingerConfig.Hostname = testHostname
+       GoldpingerConfig.CheckTimeout = testCheckTimeout
+       GoldpingerConfig.CheckAllTimeout = testCheckAllTimeout
+
+       output := checkClusterHealth(context.Background(), 
map[string]*GoldpingerPod{})
+
+       if output.OK {
+               t.Errorf("cluster OK = true, want false when no peers 
responded")
+       }
+       if output.NodesTotal != 0 {
+               t.Errorf("NodesTotal = %d, want 0", output.NodesTotal)
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goldpinger-3.11.2/pkg/goldpinger/heatmap.go 
new/goldpinger-3.11.3/pkg/goldpinger/heatmap.go
--- old/goldpinger-3.11.2/pkg/goldpinger/heatmap.go     2026-04-23 
17:37:08.000000000 +0200
+++ new/goldpinger-3.11.3/pkg/goldpinger/heatmap.go     2026-09-14 
20:00:33.000000000 +0200
@@ -121,12 +121,20 @@
 
        // draw all the boxes
        for sourceIP, results := range checkResults.Responses {
-               if *results.OK {
-                       for destinationIP, response := range 
results.Response.PodResults {
-                               x, y := getPingBoxCoordinates(order[sourceIP], 
order[destinationIP], boxSize, paddingSize)
-                               color := 
getPingBoxColor(response.ResponseTimeMs, tresholdLatencies)
-                               drawPingBox(canvas, boxSize+x, boxSize+y, 
boxSize, color)
+               if results.OK == nil || !*results.OK || results.Response == nil 
{
+                       continue
+               }
+               for destinationIP, response := range 
results.Response.PodResults {
+                       // Peer responses are untrusted: a peer can name any 
destination it
+                       // likes. Skip the ones we didn't discover ourselves 
rather than
+                       // letting them default to row 0 and paint over a real 
peer's row.
+                       destination, known := order[destinationIP]
+                       if !known {
+                               continue
                        }
+                       x, y := getPingBoxCoordinates(order[sourceIP], 
destination, boxSize, paddingSize)
+                       color := getPingBoxColor(response.ResponseTimeMs, 
tresholdLatencies)
+                       drawPingBox(canvas, boxSize+x, boxSize+y, boxSize, 
color)
                }
        }
 

++++++ goldpinger.obsinfo ++++++
--- /var/tmp/diff_new_pack.gxrjqE/_old  2026-09-21 12:09:33.823045133 +0200
+++ /var/tmp/diff_new_pack.gxrjqE/_new  2026-09-21 12:09:33.828045341 +0200
@@ -1,5 +1,5 @@
 name: goldpinger
-version: 3.11.2
-mtime: 1776958628
-commit: 6f2fec60743f13d2235345b09ffc4f797d617bd9
+version: 3.11.3
+mtime: 1789408833
+commit: 2475792dbdd0c893982fda0750a473e2d093d67c
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/goldpinger/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.goldpinger.new.383539/vendor.tar.gz differ: char 
140, line 2

Reply via email to