Summary
Two shared-utility bugs in internal/utilities/.
Findings
1. Map checksum concatenates values only — no keys, no separators → possible silent reconcile-skip (Medium)
internal/utilities/checksum.go:71-103
Both calculateMapStringString and calculateMapStringByte, after sorting keys, do:
for _, key := range keys {
checksum += data[key] // value only — no key, no separator
}
So {"a":"1","b":"23"}, {"a":"12","b":"3"} and {"a":"123"} all hash identically — key identity and value boundaries are both lost. The sibling CalculateStringSliceChecksum (:43-56) explicitly writes a {0} separator with the comment "Add separator to avoid collisions"; the map variants never got that fix.
CalculateMapChecksum → SetObjectChecksum is the repo-wide change-detection mechanism (compared as GetObjectChecksum != CalculateMapChecksum(Data) across every cert/kubeconfig/kubeadm/datastore config Secret & ConfigMap). A collision means a genuine change hashes identical to the prior state and the reconciler skips an update it should apply — silent config/cert drift, no crash.
Note: for the actual data (PEM certificates, kubeconfigs, binary secrets) the probability of a real collision is negligible, so real-world impact is low. But the code is objectively missing key+separator that the sibling function already demonstrates as intended, and the fix is trivial.
Fix: feed keys, a separator and values into the hash, mirroring CalculateStringSliceChecksum:
h.Write([]byte(key)); h.Write([]byte{0}); h.Write(value); h.Write([]byte{0})
2. GetControlPlaneAddressAndPortFromHostname mis-parses IPv6 literals (Low)
internal/utilities/ingress.go:11-25
Splits on ":" and treats len(parts)==2 as host:port. IPv6 literals contain multiple colons (2001:db8::1, [2001:db8::1]:6443), so a raw IPv6 host yields len(parts)>2 and the port branch is skipped, while [addr]:port puts "[addr" (mangled brackets) into the address. Latent correctness bug for IPv6 clusters.
Fix: use net.SplitHostPort with a fallback for the no-port case instead of strings.Split(hostname, ":").
🤖 Reported as part of a Claude Code audit.
Summary
Two shared-utility bugs in
internal/utilities/.Findings
1. Map checksum concatenates values only — no keys, no separators → possible silent reconcile-skip (Medium)
internal/utilities/checksum.go:71-103Both
calculateMapStringStringandcalculateMapStringByte, after sorting keys, do:So
{"a":"1","b":"23"},{"a":"12","b":"3"}and{"a":"123"}all hash identically — key identity and value boundaries are both lost. The siblingCalculateStringSliceChecksum(:43-56) explicitly writes a{0}separator with the comment "Add separator to avoid collisions"; the map variants never got that fix.CalculateMapChecksum→SetObjectChecksumis the repo-wide change-detection mechanism (compared asGetObjectChecksum != CalculateMapChecksum(Data)across every cert/kubeconfig/kubeadm/datastore config Secret & ConfigMap). A collision means a genuine change hashes identical to the prior state and the reconciler skips an update it should apply — silent config/cert drift, no crash.Fix: feed keys, a separator and values into the hash, mirroring
CalculateStringSliceChecksum:2.
GetControlPlaneAddressAndPortFromHostnamemis-parses IPv6 literals (Low)internal/utilities/ingress.go:11-25Splits on
":"and treatslen(parts)==2as host:port. IPv6 literals contain multiple colons (2001:db8::1,[2001:db8::1]:6443), so a raw IPv6 host yieldslen(parts)>2and the port branch is skipped, while[addr]:portputs"[addr"(mangled brackets) into the address. Latent correctness bug for IPv6 clusters.Fix: use
net.SplitHostPortwith a fallback for the no-port case instead ofstrings.Split(hostname, ":").🤖 Reported as part of a Claude Code audit.