Bug Report
Package: pkg/semverutil
File: pkg/semverutil/semverutil.go
Function: IsCompatible
Summary
IsCompatible silently returns true when both input strings are invalid semver — because semver.Major returns an empty string "" for invalid input, and "" == "" evaluates to true.
This means two completely garbage version strings are reported as "compatible", which can cause incorrect pin-upgrade decisions in the workflow linter.
Code
func IsCompatible(pinVersion, requestedVersion string) bool {
pinVersion = EnsureVPrefix(pinVersion)
requestedVersion = EnsureVPrefix(requestedVersion)
pinMajor := semver.Major(pinVersion) // returns "" for invalid input
requestedMajor := semver.Major(requestedVersion) // returns "" for invalid input
compatible := pinMajor == requestedMajor // "" == "" → true ❌
...
return compatible
}
Reproduction
result := semverutil.IsCompatible("not-a-version", "also-not-a-version")
fmt.Println(result) // prints: true ← should be false
Similarly:
result := semverutil.IsCompatible("", "")
fmt.Println(result) // prints: true ← should be false
Expected Behavior
IsCompatible should return false if either input is not a valid semver string. Compatibility is only meaningful between two parseable versions.
Proposed Fix
Add an explicit validity guard before comparing majors:
func IsCompatible(pinVersion, requestedVersion string) bool {
pinVersion = EnsureVPrefix(pinVersion)
requestedVersion = EnsureVPrefix(requestedVersion)
// Guard: both versions must be valid semver before comparing majors.
// semver.Major returns "" for invalid input, causing "" == "" to
// incorrectly report two invalid versions as compatible.
if !semver.IsValid(pinVersion) || !semver.IsValid(requestedVersion) {
semverLog.Printf("IsCompatible: one or both versions are invalid: pin=%s, requested=%s",
pinVersion, requestedVersion)
return false
}
pinMajor := semver.Major(pinVersion)
requestedMajor := semver.Major(requestedVersion)
compatible := pinMajor == requestedMajor
semverLog.Printf("Checking semver compatibility: pin=%s (major=%s), requested=%s (major=%s) -> %v",
pinVersion, pinMajor, requestedVersion, requestedMajor, compatible)
return compatible
}
Impact
| Scenario |
Current |
Expected |
IsCompatible("", "") |
true ❌ |
false ✅ |
IsCompatible("not-a-version", "not-a-version") |
true ❌ |
false ✅ |
IsCompatible("vfoo", "vbar") |
true ❌ |
false ✅ |
IsCompatible("v5.0.0", "v5") |
true ✅ |
true ✅ |
IsCompatible("v5.0.0", "v6") |
false ✅ |
false ✅ |
Severity: Medium — any caller passing user-supplied or externally-fetched version strings that fail parsing will get a wrong compatibility result, potentially causing the linter to skip a version upgrade warning it should have raised.
Found during manual code review of pkg/semverutil/semverutil.go. The existing test suite in semverutil_test.go does not include invalid-input cases for IsCompatible.
Bug Report
Package:
pkg/semverutilFile:
pkg/semverutil/semverutil.goFunction:
IsCompatibleSummary
IsCompatiblesilently returnstruewhen both input strings are invalid semver — becausesemver.Majorreturns an empty string""for invalid input, and"" == ""evaluates totrue.This means two completely garbage version strings are reported as "compatible", which can cause incorrect pin-upgrade decisions in the workflow linter.
Code
Reproduction
Similarly:
Expected Behavior
IsCompatibleshould returnfalseif either input is not a valid semver string. Compatibility is only meaningful between two parseable versions.Proposed Fix
Add an explicit validity guard before comparing majors:
Impact
IsCompatible("", "")true❌false✅IsCompatible("not-a-version", "not-a-version")true❌false✅IsCompatible("vfoo", "vbar")true❌false✅IsCompatible("v5.0.0", "v5")true✅true✅IsCompatible("v5.0.0", "v6")false✅false✅Severity: Medium — any caller passing user-supplied or externally-fetched version strings that fail parsing will get a wrong compatibility result, potentially causing the linter to skip a version upgrade warning it should have raised.
Found during manual code review of
pkg/semverutil/semverutil.go. The existing test suite insemverutil_test.godoes not include invalid-input cases forIsCompatible.