mirror of
https://github.com/anchore/grype.git
synced 2026-10-28 08:24:36 +08:00
Scope Go GHSA twins by shared CVE (#3592)
* test: characterize golang govuln FP/FN behavior and GHSA-twin bypass adds weakpoint/characterization tests for the golang symbol-matching work, built on the base without the #3573 qualifier suppression patch (module fallback preserved for no-symbol packages). - `qualifier_ideal_spec_test.go`: the ideal FP/FN outcomes across real advisories (openpgp GO-2026-5932, ssh CVE-2024-45337, x/net/html, stdlib os.Root, net/http DoS). passes here: symbol evidence present -> precise suppression, symbol evidence absent -> module-level match (no silent FN). - `gosymbols_ghsa_bypass_test.go` + `go-2026-5013` / `ghsa-46q7-xr5m-cr77` fixtures: pins the GHSA-twin symbol-scope bypass. the v6 merge bridges GO->GHSA by GHSA-id alias on the GO record (`govulndb_merge.go` ghsaAliasKeys), not by shared CVE, so a CVE-only-aliased GO record leaves its GHSA twin written unscoped at module level. a sha3-only x/crypto binary then matches the ssh advisory via the twin. EXPECTED-FAILING until the merge is made CVE-aware. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(golang): bridge govulndb->GHSA symbol scope by shared CVE the build-time govulndb<->GHSA merge only found a GO record's GHSA twin when the GO record aliased the GHSA id directly. recent govulndb records for golang.org/x/crypto/ssh alias only the CVE (e.g. GO-2026-5013 -> CVE-2026-46597), so their GHSA twins were never symbol-patched and landed in the DB unscoped at module level - reproducing exactly the false positives the gosymbols qualifier exists to remove (a binary linking only x/crypto/sha3 drew 14 phantom ssh CVEs). the merge now resolves the twin by the full alias group (shared CVE and GHSA id), so symbol scope reaches every twin regardless of which id the GO record lists. the covered GO record is deduped as before, leaving the scoped GHSA as the only match. on a corpus of pinned old-version Go binaries this removes 75 module-level false positives (symbol-unreachable GHSA twins) with no new false negatives. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(golang): compare non-canonical glued-on prerelease versions grype's go version comparator could not parse a prerelease tag glued directly onto the version with no "-" separator (e.g. "9.6.0b1"), so any advisory range carrying such a bound went inert and matched nothing. go modules never publish these - real tags are canonical semver - so they only arrive as non-canonical advisory data (a GHSA range bound here). normalize the glued tag ("9.6.0b1" -> "9.6.0-b1") during parsing; it then sorts correctly before its release, and only versions that currently fail to parse are touched. fixes a false negative where github.com/redis/go-redis/v9 @ 9.6.0 (affected, fixed in 9.6.3) matched nothing. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * test(golang): cover a GHSA with no govulndb twin a Go GHSA whose CVE and GHSA id are aliased by no govulndb record (gin GHSA-3vp4-m3rf-835h / CVE-2023-26125) is held by the merge but never patched or covered, so it must be written unchanged and still match at module level - grype's coverage over a govulndb-only view. every existing fixture GHSA is twinned, so this path was untested; adds a fixture and asserts it matches. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix test Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * widen to any alias Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
1 parent
072fe5938e
commit
ba11c44c7e
13 files changed
+706
-12
No files matched your search
@@ -13,6 +13,7 @@ mise.toml
|
||||
specs
|
||||
*.xxh64
|
||||
.context
|
||||
hack/
|
||||
|
||||
# AI and LLM related files
|
||||
CLAUDE.md
|
||||
|
||||
@@ -52,6 +52,10 @@ type goVulnDBMerger struct {
|
||||
goGHSAEntries map[string]*transformers.RelatedEntries
|
||||
goGHSAOrder []string
|
||||
govulndbEntries []*transformers.RelatedEntries
|
||||
|
||||
// aliasIndex indexes held GHSA keys by every id they alias, so a GO record
|
||||
// can reach its GHSA twin by any shared id. Built in reconcile.
|
||||
aliasIndex map[string][]string
|
||||
}
|
||||
|
||||
func newGoVulnDBMerger() *goVulnDBMerger {
|
||||
@@ -105,6 +109,8 @@ func hasGoModulePackages(entry transformers.RelatedEntries) bool {
|
||||
// merger's held state is reset before returning. Called once from Close; the
|
||||
// writer persists the returned entries through its own batching path.
|
||||
func (m *goVulnDBMerger) reconcile() []transformers.RelatedEntries {
|
||||
m.aliasIndex = m.buildAliasIndex()
|
||||
|
||||
var surviving []*transformers.RelatedEntries
|
||||
for _, entry := range m.govulndbEntries {
|
||||
if m.handleEntry(entry) {
|
||||
@@ -131,6 +137,7 @@ func (m *goVulnDBMerger) reconcile() []transformers.RelatedEntries {
|
||||
m.govulndbEntries = nil
|
||||
m.goGHSAEntries = make(map[string]*transformers.RelatedEntries)
|
||||
m.goGHSAOrder = nil
|
||||
m.aliasIndex = nil
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -203,7 +210,7 @@ func (m *goVulnDBMerger) handleEntry(entry *transformers.RelatedEntries) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
ghsaKeys := ghsaAliasKeys(handle.BlobValue.Aliases)
|
||||
ghsaKeys := m.aliasedGHSAKeys(handle.BlobValue.Aliases)
|
||||
if len(ghsaKeys) == 0 {
|
||||
return true
|
||||
}
|
||||
@@ -257,12 +264,52 @@ func unwrapGoVulnDBPackages(entry *transformers.RelatedEntries) map[int]transfor
|
||||
return replacements
|
||||
}
|
||||
|
||||
// ghsaAliasKeys returns the lowercase GHSA IDs among the given aliases.
|
||||
func ghsaAliasKeys(aliases []string) []string {
|
||||
// buildAliasIndex maps each id a held GHSA aliases to that GHSA's key, so a GO
|
||||
// record can find its GHSA twin by a shared id even when it never names the GHSA
|
||||
// directly. Recent govulndb records for golang.org/x/crypto/ssh alias only the
|
||||
// CVE (e.g. GO-2026-5013 -> CVE-2026-46597), which otherwise leaves the twin
|
||||
// GHSA unbridged and written unscoped at module level (the GHSA-twin
|
||||
// symbol-scope bypass).
|
||||
//
|
||||
// Any shared id bridges, not just CVEs: two records that claim the same alias are
|
||||
// the same advisory whatever the id scheme, which is how identity is resolved at
|
||||
// match time too (see result.getIdentity). Today the GHSA feed only carries CVE
|
||||
// aliases, so this is the same set of bridges with no prefix knowledge baked in.
|
||||
func (m *goVulnDBMerger) buildAliasIndex() map[string][]string {
|
||||
index := make(map[string][]string)
|
||||
for _, key := range m.goGHSAOrder {
|
||||
held := m.goGHSAEntries[key]
|
||||
if held.VulnerabilityHandle == nil || held.VulnerabilityHandle.BlobValue == nil {
|
||||
continue
|
||||
}
|
||||
for _, alias := range held.VulnerabilityHandle.BlobValue.Aliases {
|
||||
a := strings.ToLower(alias)
|
||||
index[a] = append(index[a], key)
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// aliasedGHSAKeys returns the held GHSA keys the GO record's alias group reaches:
|
||||
// held GHSAs it names directly, plus held GHSAs sharing any other alias with it.
|
||||
// Deduplicated, arrival-order stable.
|
||||
func (m *goVulnDBMerger) aliasedGHSAKeys(aliases []string) []string {
|
||||
var keys []string
|
||||
seen := make(map[string]bool)
|
||||
add := func(k string) {
|
||||
if seen[k] {
|
||||
return
|
||||
}
|
||||
seen[k] = true
|
||||
keys = append(keys, k)
|
||||
}
|
||||
for _, alias := range aliases {
|
||||
if a := strings.ToLower(alias); strings.HasPrefix(a, "ghsa-") {
|
||||
keys = append(keys, a)
|
||||
a := strings.ToLower(alias)
|
||||
if _, held := m.goGHSAEntries[a]; held {
|
||||
add(a)
|
||||
}
|
||||
for _, k := range m.aliasIndex[a] {
|
||||
add(k)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
|
||||
@@ -685,10 +685,10 @@ func TestGetRanges(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Version: db.Version{
|
||||
// important: this emits an unknown constraint type,
|
||||
// triggering fuzzy matching when the input is not
|
||||
// valid semver
|
||||
Type: "Unknown",
|
||||
// glued-on prerelease bounds ("9.6.0b1") are normalized by the go
|
||||
// comparator, so this stays a go constraint instead of falling back
|
||||
// to fuzzy matching
|
||||
Type: "go",
|
||||
Constraint: ">=9.6.0b1,<9.6.3",
|
||||
},
|
||||
Fix: &db.Fix{
|
||||
@@ -716,7 +716,7 @@ func TestGetRanges(t *testing.T) {
|
||||
ranges = append(ranges, rng...)
|
||||
}
|
||||
|
||||
require.Equal(t, 1, len(errors))
|
||||
require.Empty(t, errors)
|
||||
if diff := cmp.Diff(expectedRanges, ranges); diff != "" {
|
||||
t.Errorf("getRanges() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/internal/dbtest"
|
||||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// TestMatcherGolang_GoSymbols_GHSATwinBypass guards a fixed WEAK POINT in the
|
||||
// build-time govulndb<->GHSA merge (grype/db/v6/build/govulndb_merge.go): when a
|
||||
// GO-* record does not directly alias its GHSA twin, the symbol scope must still
|
||||
// reach the GHSA record via the shared CVE, so the GHSA does not match at plain
|
||||
// module granularity and reproduce exactly the false positives the gosymbols
|
||||
// feature exists to remove.
|
||||
//
|
||||
// The merge now bridges GO and GHSA by any shared alias (buildAliasIndex +
|
||||
// aliasedGHSAKeys), so these assert the CORRECT behavior and pass. Do not
|
||||
// "fix" a future regression by weakening the assertion — keep the merge
|
||||
// bridging the whole alias group.
|
||||
//
|
||||
// Root cause that was fixed (confirmed against real data):
|
||||
// - handleEntry() derived the GHSA records to patch from GHSA-prefixed aliases
|
||||
// off the GO record's own alias list only.
|
||||
// - Recent govulndb records for golang.org/x/crypto/ssh alias ONLY the CVE, not
|
||||
// the GHSA, e.g. GO-2026-5013 -> ["CVE-2026-46597"] and
|
||||
// GO-2026-5005 -> ["CVE-2026-39833"]. GO-2024-3321 merged cleanly only
|
||||
// because it happens to also list "GHSA-v778-237x-gjrc".
|
||||
// - The GHSA twin lands in the DB via the github feed (CVE-linked) and is held
|
||||
// by the merger (hasGoModulePackages); with no GO record naming it directly
|
||||
// it was written UNPATCHED at module level until the CVE bridge.
|
||||
//
|
||||
// Empirical proof: a binary linking only golang.org/x/crypto/sha3 - with
|
||||
// nothing from the vulnerable ssh sub-package reachable - still drew 14
|
||||
// grype findings, all GHSA-* x/crypto ssh CVEs, while their GO-* twins were
|
||||
// correctly suppressed and absent from the output.
|
||||
//
|
||||
// The fixture pair modeling this (both hand-authored, see db.yaml):
|
||||
// - govulndb GO-2026-5013: golang.org/x/crypto, imports scoped to
|
||||
// golang.org/x/crypto/ssh (whole-package), aliases only CVE-2026-46597.
|
||||
// - github GHSA-46q7-xr5m-cr77: golang.org/x/crypto module-level, < 0.35.0,
|
||||
// same CVE-2026-46597, no symbol scope. (Synthetic GHSA id: the exact real
|
||||
// id for this CVE is not needed to exercise the merge weak point.)
|
||||
func TestMatcherGolang_GoSymbols_GHSATwinBypass(t *testing.T) {
|
||||
const (
|
||||
sshPanicGo = "GO-2026-5013" // scoped to x/crypto/ssh; aliases only CVE-2026-46597 (no GHSA) -> now covered by the twin and dropped
|
||||
sshPanicGHSA = "GHSA-46q7-xr5m-cr77" // x/crypto module-level twin, CVE-linked only -> symbol-patched via the CVE bridge
|
||||
)
|
||||
|
||||
// a version inside both the GO record's (< 0.35.0) and GHSA's (< 0.35.0) windows.
|
||||
const vulnerableXCryptoVersion = "v0.20.0"
|
||||
|
||||
dbtest.DBs(t, "govulndb-and-ghsa").Run(func(t *testing.T, db *dbtest.DB) {
|
||||
matcher := NewGolangMatcher(MatcherConfig{})
|
||||
|
||||
t.Run("x/crypto binary using only sha3 must not match the ssh advisory via EITHER namespace", func(t *testing.T) {
|
||||
// the sha3-only reproduction: links golang.org/x/crypto but never the
|
||||
// vulnerable ssh sub-package, so nothing vulnerable is reachable.
|
||||
// The GO record is correctly suppressed (ssh import absent), and the
|
||||
// GHSA twin now carries the same ssh scope via the CVE bridge, so
|
||||
// neither namespace matches.
|
||||
p := dbtest.NewPackage("golang.org/x/crypto", vulnerableXCryptoVersion, syftPkg.GoModulePkg).
|
||||
WithLanguage(syftPkg.Go).
|
||||
WithGoBinarySymbols(map[string][]string{
|
||||
"golang.org/x/crypto/sha3": {"New256", "Sum256"},
|
||||
}).
|
||||
Build()
|
||||
|
||||
findings := db.Match(t, matcher, p)
|
||||
|
||||
// a binary that never links the vulnerable ssh sub-package matches
|
||||
// nothing: the GHSA twin is symbol-scoped by the merge, so it no longer
|
||||
// leaks at module granularity.
|
||||
findings.IsEmpty()
|
||||
})
|
||||
|
||||
t.Run("x/crypto binary using ssh still matches (positive control)", func(t *testing.T) {
|
||||
// genuinely links the vulnerable golang.org/x/crypto/ssh package, so it
|
||||
// SHOULD be flagged. Once the merge bridges GO->GHSA by shared CVE the
|
||||
// GHSA twin is symbol-patched and the GO record's x/crypto package is
|
||||
// covered, so the GO record is dropped and the scoped GHSA is the only
|
||||
// match - the same dedup every clean-merge case produces (gjson, lxd,
|
||||
// aws-sdk-go). This guards against a fix that over-suppresses real users.
|
||||
p := dbtest.NewPackage("golang.org/x/crypto", vulnerableXCryptoVersion, syftPkg.GoModulePkg).
|
||||
WithLanguage(syftPkg.Go).
|
||||
WithGoBinarySymbols(map[string][]string{
|
||||
"golang.org/x/crypto/ssh": {"NewServerConn", "ServerConfig.PublicKeyCallback"},
|
||||
}).
|
||||
Build()
|
||||
|
||||
findings := db.Match(t, matcher, p)
|
||||
|
||||
// the covered GO record is dropped; the symbol-scoped GHSA twin matches.
|
||||
findings.SelectMatch(sshPanicGHSA).SelectDetailByType(match.ExactDirectMatch).AsEcosystemSearch()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -65,6 +65,7 @@ func TestMatcherGolang_GoSymbols_GHSAMerge(t *testing.T) {
|
||||
jsonPatchGo = "GO-2021-0076" // fully GHSA-covered -> dropped from the DB
|
||||
goRedisGHSA = "GHSA-92cp-5422-2mw7" // go-redis /v9 rows; symbols merged from GO-2025-3540
|
||||
goRedisGo = "GO-2025-3540" // /v9 rows dropped; base and /v7 /v8 rows survive
|
||||
ginCoverageGHSA = "GHSA-3vp4-m3rf-835h" // gin CVE-2023-26125; NO govulndb twin -> survives unpatched, module-level
|
||||
)
|
||||
|
||||
// see TestMatcherGolang_GoSymbols for why these versions are pinned
|
||||
@@ -449,5 +450,21 @@ func TestMatcherGolang_GoSymbols_GHSAMerge(t *testing.T) {
|
||||
|
||||
expectMatches(t, findings, gjsonReDoSGHSA)
|
||||
})
|
||||
|
||||
t.Run("Go GHSA with no govulndb twin survives the merge and matches at module level", func(t *testing.T) {
|
||||
// GHSA-3vp4-m3rf-835h (gin, CVE-2023-26125) has no aliased GO record, so
|
||||
// the merge holds it (go-module package) but never patches or covers it.
|
||||
// It must still be written and match - grype's coverage advantage over a
|
||||
// govulndb-only view, where this vuln is invisible. Guards that un-twinned
|
||||
// held GHSAs are not dropped by the govulndb<->GHSA reconciliation.
|
||||
p := dbtest.NewPackage("github.com/gin-gonic/gin", "v1.8.0", syftPkg.GoModulePkg).
|
||||
WithLanguage(syftPkg.Go).
|
||||
WithMetadata(pkg.GolangBinMetadata{}).
|
||||
Build()
|
||||
|
||||
findings := db.Match(t, matcher, p)
|
||||
|
||||
expectMatches(t, findings, ginCoverageGHSA)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/internal/dbtest"
|
||||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// TestMatcherGolang_GoSymbols_VNRangeGluedPrerelease guards a fixed false negative
|
||||
// where a github.com/redis/go-redis/v9 @ 9.6.0 binary matched nothing at all,
|
||||
// despite being genuinely affected (fixed in 9.6.3) and actually reachable.
|
||||
//
|
||||
// The mechanics (GO-2025-3540 / CVE-2025-29923 / GHSA-92cp-5422-2mw7):
|
||||
// - the merge marks the GO record's /v9 rows "covered" (GHSA-92cp also lists
|
||||
// github.com/redis/go-redis/v9) and drops them, leaving the GHSA as the record
|
||||
// that must match 9.6.0.
|
||||
// - GHSA-92cp's covering range is >= 9.6.0b1, < 9.6.3. The "9.6.0b1" bound is a
|
||||
// non-canonical prerelease (glued tag, no "-" separator); go modules never
|
||||
// publish it (the real beta tag is v9.6.0-beta.1), it is only advisory data.
|
||||
// - grype's golang comparator could not parse "9.6.0b1", so the whole range went
|
||||
// inert and 9.6.0 matched nothing.
|
||||
//
|
||||
// The fix normalizes a glued-on prerelease tag ("9.6.0b1" -> "9.6.0-b1", see
|
||||
// grype/version/golang_version.go), so the range parses and correctly covers 9.6.0.
|
||||
// This surfaced as the single adjudicated grype false negative across a 62-subject
|
||||
// differential corpus.
|
||||
func TestMatcherGolang_GoSymbols_VNRangeGluedPrerelease(t *testing.T) {
|
||||
const (
|
||||
goRedisGo = "GO-2025-3540" // /v9 < 9.6.3 rows are deduped onto the GHSA
|
||||
goRedisGHSA = "GHSA-92cp-5422-2mw7" // surviving twin whose >= 9.6.0b1 range now parses
|
||||
)
|
||||
|
||||
// 9.6.0 is inside the GHSA's (normalized) >= 9.6.0-b1, < 9.6.3 window.
|
||||
const affectedGoRedisVersion = "v9.6.0"
|
||||
|
||||
dbtest.DBs(t, "govulndb-and-ghsa").Run(func(t *testing.T, db *dbtest.DB) {
|
||||
matcher := NewGolangMatcher(MatcherConfig{})
|
||||
|
||||
// module-level scan (no symbols): isolates the version-range decision from
|
||||
// symbol scoping. The vuln genuinely applies (fixed in 9.6.3), so it must be
|
||||
// reported. The GO /v9 rows were deduped onto GHSA-92cp, so the match lands
|
||||
// under the GHSA namespace (GO-2025-3540 rides along as a related vuln).
|
||||
p := dbtest.NewPackage("github.com/redis/go-redis/v9", affectedGoRedisVersion, syftPkg.GoModulePkg).
|
||||
WithLanguage(syftPkg.Go).
|
||||
WithMetadata(pkg.GolangBinMetadata{}).
|
||||
Build()
|
||||
|
||||
findings := db.Match(t, matcher, p)
|
||||
|
||||
findings.SelectMatch(goRedisGHSA).SelectDetailByType(match.ExactDirectMatch).AsEcosystemSearch()
|
||||
})
|
||||
}
|
||||
+17
-2
@@ -17,8 +17,8 @@
|
||||
# - GO-2023-1840 (stdlib-only, whole-package) has no GHSA alias and passes
|
||||
# through unchanged.
|
||||
# - GO-2022-0635 aliases GHSA-7f33-f4f5-xwgw for github.com/aws/aws-sdk-go:
|
||||
# the govulndb range is open-ended (introduced: 0, no fix — govulncheck
|
||||
# flags every version) while the GHSA bounds it at < 1.34.0. The merged
|
||||
# the govulndb range is open-ended (introduced: 0, no fix, so it would
|
||||
# flag every version) while the GHSA bounds it at < 1.34.0. The merged
|
||||
# record has the GHSA's bounded range AND the govulndb s3crypto symbols,
|
||||
# eliminating both false-positive classes.
|
||||
# - GO-2024-3312 aliases GHSA-4c49-9fpc-hc3v for github.com/canonical/lxd:
|
||||
@@ -43,6 +43,18 @@
|
||||
# is not the merge's call). The 9.6.0b1 bound is malformed to the golang
|
||||
# comparator and its range is inert; this is a known FN tracked in the
|
||||
# custom_ranges odd-version-strings follow-up issue.
|
||||
# - GHSA-3vp4-m3rf-835h (github.com/gin-gonic/gin, < 1.9.0, CVE-2023-26125) has
|
||||
# NO govulndb twin: no GO record aliases its CVE or GHSA id. The merge holds it
|
||||
# (go-module package) but nothing patches or covers it, so it is written
|
||||
# unchanged and matches at module level. This is the "grype coverage advantage"
|
||||
# class - grype reports a GHSA/NVD vuln govulndb never recorded - and guards
|
||||
# that un-twinned held GHSAs survive the govulndb<->GHSA merge.
|
||||
# - GO-2026-5013 (golang.org/x/crypto, scoped to golang.org/x/crypto/ssh)
|
||||
# aliases ONLY CVE-2026-46597 - no GHSA alias. Its CVE-linked twin
|
||||
# GHSA-46q7-xr5m-cr77 (hand-authored, module-level x/crypto < 0.35.0) is held
|
||||
# by the merger but never patched, because the merge bridges GO->GHSA by
|
||||
# GHSA-id alias on the GO record, not by shared CVE. This models the observed
|
||||
# GHSA-twin symbol-scope bypass (see gosymbols_ghsa_bypass_test.go).
|
||||
auto-generate: false
|
||||
extractions:
|
||||
govulndb:
|
||||
@@ -53,6 +65,7 @@ extractions:
|
||||
- go-2024-3312
|
||||
- go-2021-0076
|
||||
- go-2025-3540
|
||||
- go-2026-5013
|
||||
github:
|
||||
- github:go/GHSA-69cg-p879-7622
|
||||
- github:go/GHSA-c9gm-7rfj-8w5h
|
||||
@@ -61,3 +74,5 @@ extractions:
|
||||
- github:go/GHSA-4c49-9fpc-hc3v
|
||||
- github:go/GHSA-gxhv-3hwf-wjp9
|
||||
- github:go/GHSA-92cp-5422-2mw7
|
||||
- github:go/GHSA-46q7-xr5m-cr77
|
||||
- github:go/GHSA-3vp4-m3rf-835h
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"identifier": "github:go/ghsa-3vp4-m3rf-835h",
|
||||
"item": {
|
||||
"Advisory": {
|
||||
"CVE": [
|
||||
"CVE-2023-26125"
|
||||
],
|
||||
"CVSS": {
|
||||
"base_metrics": {
|
||||
"base_score": 7.5,
|
||||
"base_severity": "High",
|
||||
"exploitability_score": 3.9,
|
||||
"impact_score": 3.6
|
||||
},
|
||||
"status": "N/A",
|
||||
"vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
"version": "3.1"
|
||||
},
|
||||
"Classification": "GENERAL",
|
||||
"FixedIn": [
|
||||
{
|
||||
"available": {
|
||||
"date": "2023-05-12",
|
||||
"kind": "first-observed"
|
||||
},
|
||||
"ecosystem": "go",
|
||||
"identifier": "1.9.0",
|
||||
"name": "github.com/gin-gonic/gin",
|
||||
"namespace": "github:go",
|
||||
"range": "< 1.9.0"
|
||||
}
|
||||
],
|
||||
"Metadata": {
|
||||
"CVE": [
|
||||
"CVE-2023-26125"
|
||||
]
|
||||
},
|
||||
"Severity": "Moderate",
|
||||
"Summary": "Improper input validation in github.com/gin-gonic/gin",
|
||||
"cvss_severities": [
|
||||
{
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
"version": "3.1"
|
||||
}
|
||||
],
|
||||
"ghsaId": "GHSA-3vp4-m3rf-835h",
|
||||
"namespace": "github:go",
|
||||
"published": "2023-05-12T00:00:00Z",
|
||||
"references": [
|
||||
{
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-26125"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/advisories/GHSA-3vp4-m3rf-835h"
|
||||
}
|
||||
],
|
||||
"updated": "2023-05-12T00:00:00Z",
|
||||
"url": "https://github.com/advisories/GHSA-3vp4-m3rf-835h",
|
||||
"withdrawn": null
|
||||
},
|
||||
"Vulnerability": {}
|
||||
},
|
||||
"schema": "https://raw.githubusercontent.com/anchore/vunnel/main/schema/vulnerability/github-security-advisory/schema-1.0.3.json"
|
||||
}
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"identifier": "github:go/ghsa-46q7-xr5m-cr77",
|
||||
"item": {
|
||||
"Advisory": {
|
||||
"CVE": [
|
||||
"CVE-2026-46597"
|
||||
],
|
||||
"CVSS": {
|
||||
"base_metrics": {
|
||||
"base_score": 7.5,
|
||||
"base_severity": "High",
|
||||
"exploitability_score": 3.9,
|
||||
"impact_score": 3.6
|
||||
},
|
||||
"status": "N/A",
|
||||
"vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
"version": "3.1"
|
||||
},
|
||||
"Classification": "GENERAL",
|
||||
"FixedIn": [
|
||||
{
|
||||
"available": {
|
||||
"date": "2026-06-01",
|
||||
"kind": "first-observed"
|
||||
},
|
||||
"ecosystem": "go",
|
||||
"identifier": "0.35.0",
|
||||
"name": "golang.org/x/crypto",
|
||||
"namespace": "github:go",
|
||||
"range": "< 0.35.0"
|
||||
}
|
||||
],
|
||||
"Metadata": {
|
||||
"CVE": [
|
||||
"CVE-2026-46597"
|
||||
]
|
||||
},
|
||||
"Severity": "High",
|
||||
"Summary": "golang.org/x/crypto/ssh panic via crafted value",
|
||||
"cvss_severities": [
|
||||
{
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
"version": "3.1"
|
||||
}
|
||||
],
|
||||
"ghsaId": "GHSA-46q7-xr5m-cr77",
|
||||
"namespace": "github:go",
|
||||
"published": "2026-06-01T00:00:00Z",
|
||||
"references": [
|
||||
{
|
||||
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-46597"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/advisories/GHSA-46q7-xr5m-cr77"
|
||||
}
|
||||
],
|
||||
"updated": "2026-06-01T00:00:00Z",
|
||||
"url": "https://github.com/advisories/GHSA-46q7-xr5m-cr77",
|
||||
"withdrawn": null
|
||||
},
|
||||
"Vulnerability": {}
|
||||
},
|
||||
"schema": "https://raw.githubusercontent.com/anchore/vunnel/main/schema/vulnerability/github-security-advisory/schema-1.0.3.json"
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"identifier": "go-2026-5013",
|
||||
"item": {
|
||||
"schema_version": "1.3.1",
|
||||
"id": "GO-2026-5013",
|
||||
"modified": "2026-06-01T00:00:00Z",
|
||||
"published": "2026-06-01T00:00:00Z",
|
||||
"aliases": [
|
||||
"CVE-2026-46597"
|
||||
],
|
||||
"summary": "Invoking byte arithmetic causes underflow and panic in golang.org/x/crypto/ssh",
|
||||
"details": "A crafted value can cause an underflow and panic in golang.org/x/crypto/ssh.",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"name": "golang.org/x/crypto",
|
||||
"ecosystem": "Go"
|
||||
},
|
||||
"ranges": [
|
||||
{
|
||||
"type": "SEMVER",
|
||||
"events": [
|
||||
{
|
||||
"introduced": "0"
|
||||
},
|
||||
{
|
||||
"fixed": "0.35.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ecosystem_specific": {
|
||||
"imports": [
|
||||
{
|
||||
"path": "golang.org/x/crypto/ssh"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"type": "FIX",
|
||||
"url": "https://go.googlesource.com/crypto/+/refs/tags/v0.35.0"
|
||||
}
|
||||
],
|
||||
"database_specific": {
|
||||
"url": "https://pkg.go.dev/vuln/GO-2026-5013",
|
||||
"review_status": "REVIEWED"
|
||||
}
|
||||
},
|
||||
"schema": "https://raw.githubusercontent.com/anchore/vunnel/main/schema/vulnerability/osv/schema-1.3.1.json"
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package gosymbols
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
)
|
||||
|
||||
// This file is an executable SPEC of the correct gosymbols qualifier behavior, encoded as real
|
||||
// advisories. The rule it pins: suppress a match ONLY when there is symbol coverage for
|
||||
// THIS module AND the vulnerable path/symbol is absent from it (evidence of absence). When the module
|
||||
// carries no symbol evidence at all - an SBOM built without symbol capture (syft's default), capture
|
||||
// disabled, or a stdlib-only scope that leaves non-stdlib modules empty - we cannot prove the code is
|
||||
// unused, so we fall back to a module-level match to preserve recall. Absence of evidence is not
|
||||
// evidence of absence.
|
||||
//
|
||||
// Each advisory is exercised through three routes:
|
||||
// - "covered + used": non-empty Symbols map with the vulnerable path + vulnerable local symbol(s) -> true
|
||||
// - "covered + not used": non-empty Symbols map, only sibling paths/symbols of the same module -> false (FP avoided)
|
||||
// - "no symbol evidence": empty Symbols map (pkg.GolangBinMetadata{}) -> true (module fallback; FN avoided)
|
||||
func TestGoSymbolsQualifier_IdealSpec_RealAdvisories(t *testing.T) {
|
||||
// symbol maps are keyed by import path; values are LOCAL symbol names (import-path prefix stripped),
|
||||
// already normalized to govulndb's convention (as the provider delivers them).
|
||||
pkgWithSymbols := func(name string, symbols map[string][]string) pkg.Package {
|
||||
return pkg.Package{Name: name, Metadata: pkg.GolangBinMetadata{Symbols: symbols}}
|
||||
}
|
||||
noSymbols := func(name string) pkg.Package {
|
||||
return pkg.Package{Name: name, Metadata: pkg.GolangBinMetadata{}}
|
||||
}
|
||||
|
||||
// GO-2026-5932: golang.org/x/crypto/openpgp is unmaintained/unsafe. Whole-package scoping (no
|
||||
// symbols on the advisory) across every openpgp subpackage. This advisory is the FALSE-POSITIVE
|
||||
// source: module-level matching flags any x/crypto consumer, even ones that only touch ssh.
|
||||
openpgpVuln := []Import{
|
||||
{Path: "golang.org/x/crypto/openpgp"},
|
||||
{Path: "golang.org/x/crypto/openpgp/packet"},
|
||||
{Path: "golang.org/x/crypto/openpgp/armor"},
|
||||
{Path: "golang.org/x/crypto/openpgp/clearsign"},
|
||||
{Path: "golang.org/x/crypto/openpgp/errors"},
|
||||
{Path: "golang.org/x/crypto/openpgp/elgamal"},
|
||||
{Path: "golang.org/x/crypto/openpgp/s2k"},
|
||||
}
|
||||
|
||||
// GO-2024-3321 / CVE-2024-45337: golang.org/x/crypto/ssh authorization bypass (fixed 0.31.0). A
|
||||
// real, fixable, high-severity vuln scoped to specific ssh server symbols. This is the
|
||||
// FALSE-NEGATIVE source: suppressing it when symbols are absent silently drops an auth-bypass.
|
||||
sshAuthBypassVuln := []Import{{
|
||||
Path: "golang.org/x/crypto/ssh",
|
||||
Symbols: []string{"NewServerConn", "ServerConfig.PublicKeyCallback", "connection.serverAuthenticate"},
|
||||
}}
|
||||
|
||||
// GO-2025-3595 / CVE-2025-22872: golang.org/x/net/html (fixed 0.38.0).
|
||||
netHTMLVuln := []Import{{
|
||||
Path: "golang.org/x/net/html",
|
||||
Symbols: []string{"Parse", "ParseFragment", "ParseFragmentWithOptions", "ParseWithOptions", "Tokenizer.Next", "Tokenizer.readStartTag"},
|
||||
}}
|
||||
|
||||
// GO-2026-4602 / CVE-2026-27139: stdlib os.Root escape (fixed 1.25.8/1.26.1). stdlib scope, so
|
||||
// affects nearly every binary built with an older toolchain.
|
||||
osRootEscapeVuln := []Import{{
|
||||
Path: "os",
|
||||
Symbols: []string{"File.ReadDir", "File.Readdir", "ReadDir", "dirFS.ReadDir", "rootFS.ReadDir"},
|
||||
}}
|
||||
|
||||
// GO-2022-0969 / CVE-2022-27664: stdlib net/http server DoS. Server-side entrypoints only.
|
||||
httpServerDoSVuln := []Import{{
|
||||
Path: "net/http",
|
||||
Symbols: []string{
|
||||
"ListenAndServe", "ListenAndServeTLS", "Serve", "ServeTLS",
|
||||
"Server.ListenAndServe", "Server.ListenAndServeTLS", "Server.Serve", "Server.ServeTLS",
|
||||
"http2Server.ServeConn", "http2serverConn.goAway",
|
||||
},
|
||||
}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
advisoryID string
|
||||
route string
|
||||
imports []Import
|
||||
pkg pkg.Package
|
||||
wantSatisfied bool
|
||||
fpOrFn string
|
||||
}{
|
||||
// --- GO-2026-5932: openpgp whole-package (FP source) ---
|
||||
{
|
||||
name: "GO-2026-5932 openpgp / covered+used: binary genuinely links openpgp -> match",
|
||||
advisoryID: "GO-2026-5932",
|
||||
route: "covered+used",
|
||||
imports: openpgpVuln,
|
||||
pkg: pkgWithSymbols("golang.org/x/crypto", map[string][]string{"golang.org/x/crypto/openpgp": {"ReadMessage", "ArmoredDetachSign"}}),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "correct positive: openpgp actually used",
|
||||
},
|
||||
{
|
||||
name: "GO-2026-5932 openpgp / covered+not-used: binary links only ssh (issue #3573) -> no match",
|
||||
advisoryID: "GO-2026-5932",
|
||||
route: "covered+not-used",
|
||||
imports: openpgpVuln,
|
||||
pkg: pkgWithSymbols("golang.org/x/crypto", map[string][]string{"golang.org/x/crypto/ssh": {"Dial", "NewClientConn"}}),
|
||||
wantSatisfied: false,
|
||||
fpOrFn: "FP correctly avoided: openpgp not used, coverage proves absence",
|
||||
},
|
||||
{
|
||||
name: "GO-2026-5932 openpgp / no-symbol-evidence: SBOM without symbols -> module fallback match",
|
||||
advisoryID: "GO-2026-5932",
|
||||
route: "no-symbol-evidence",
|
||||
imports: openpgpVuln,
|
||||
pkg: noSymbols("golang.org/x/crypto"),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "FN-tradeoff: openpgp FP is the acceptable price of not knowing; the real fix is guaranteeing coverage, not guessing absence",
|
||||
},
|
||||
|
||||
// --- GO-2024-3321 / CVE-2024-45337: x/crypto/ssh auth bypass (FN source) ---
|
||||
{
|
||||
name: "GO-2024-3321 ssh-authz / covered+used: vulnerable server symbol present -> match",
|
||||
advisoryID: "GO-2024-3321",
|
||||
route: "covered+used",
|
||||
imports: sshAuthBypassVuln,
|
||||
pkg: pkgWithSymbols("golang.org/x/crypto", map[string][]string{"golang.org/x/crypto/ssh": {"NewServerConn", "ServerConfig.PublicKeyCallback"}}),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "correct positive: vulnerable ssh server symbol used",
|
||||
},
|
||||
{
|
||||
name: "GO-2024-3321 ssh-authz / covered+not-used: client-only ssh symbols -> no match",
|
||||
advisoryID: "GO-2024-3321",
|
||||
route: "covered+not-used",
|
||||
imports: sshAuthBypassVuln,
|
||||
pkg: pkgWithSymbols("golang.org/x/crypto", map[string][]string{"golang.org/x/crypto/ssh": {"Dial", "NewClientConn", "ClientConfig"}}),
|
||||
wantSatisfied: false,
|
||||
fpOrFn: "FP correctly avoided: only client-side ssh symbols used",
|
||||
},
|
||||
{
|
||||
name: "GO-2024-3321 ssh-authz / no-symbol-evidence: SBOM without symbols -> module fallback match",
|
||||
advisoryID: "GO-2024-3321",
|
||||
route: "no-symbol-evidence",
|
||||
imports: sshAuthBypassVuln,
|
||||
pkg: noSymbols("golang.org/x/crypto"),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "no-symbol fallback: suppressing here would silently drop an auth bypass",
|
||||
},
|
||||
|
||||
// --- GO-2025-3595 / CVE-2025-22872: x/net/html ---
|
||||
{
|
||||
name: "GO-2025-3595 net/html / covered+used: Parse present -> match",
|
||||
advisoryID: "GO-2025-3595",
|
||||
route: "covered+used",
|
||||
imports: netHTMLVuln,
|
||||
pkg: pkgWithSymbols("golang.org/x/net", map[string][]string{"golang.org/x/net/html": {"Parse", "Tokenizer.Next"}}),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "correct positive: vulnerable html parser symbol used",
|
||||
},
|
||||
{
|
||||
name: "GO-2025-3595 net/html / no-symbol-evidence: SBOM without symbols -> module fallback match",
|
||||
advisoryID: "GO-2025-3595",
|
||||
route: "no-symbol-evidence",
|
||||
imports: netHTMLVuln,
|
||||
pkg: noSymbols("golang.org/x/net"),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "no-symbol fallback: suppressing here would silently drop a real vuln",
|
||||
},
|
||||
|
||||
// --- GO-2026-4602 / CVE-2026-27139: stdlib os.Root escape ---
|
||||
{
|
||||
name: "GO-2026-4602 os.Root / covered+used: escape symbol present -> match",
|
||||
advisoryID: "GO-2026-4602",
|
||||
route: "covered+used",
|
||||
imports: osRootEscapeVuln,
|
||||
pkg: pkgWithSymbols("stdlib", map[string][]string{"os": {"rootFS.ReadDir", "File.ReadDir"}}),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "correct positive: vulnerable os symbol used",
|
||||
},
|
||||
{
|
||||
name: "GO-2026-4602 os.Root / covered+not-used: uses os but not escape symbols -> no match",
|
||||
advisoryID: "GO-2026-4602",
|
||||
route: "covered+not-used",
|
||||
imports: osRootEscapeVuln,
|
||||
pkg: pkgWithSymbols("stdlib", map[string][]string{"os": {"Open", "Create", "Getenv"}}),
|
||||
wantSatisfied: false,
|
||||
fpOrFn: "FP correctly avoided: os used but not the escape path",
|
||||
},
|
||||
{
|
||||
name: "GO-2026-4602 os.Root / no-symbol-evidence: SBOM without symbols -> module fallback match",
|
||||
advisoryID: "GO-2026-4602",
|
||||
route: "no-symbol-evidence",
|
||||
imports: osRootEscapeVuln,
|
||||
pkg: noSymbols("stdlib"),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "no-symbol fallback: stdlib, affects nearly every older-toolchain binary",
|
||||
},
|
||||
|
||||
// --- GO-2022-0969 / CVE-2022-27664: stdlib net/http server DoS ---
|
||||
{
|
||||
name: "GO-2022-0969 net/http-DoS / covered+used: server symbol present -> match",
|
||||
advisoryID: "GO-2022-0969",
|
||||
route: "covered+used",
|
||||
imports: httpServerDoSVuln,
|
||||
pkg: pkgWithSymbols("stdlib", map[string][]string{"net/http": {"ListenAndServe", "http2Server.ServeConn"}}),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "correct positive: server entrypoint used",
|
||||
},
|
||||
{
|
||||
name: "GO-2022-0969 net/http-DoS / covered+not-used: client-only net/http symbols -> no match",
|
||||
advisoryID: "GO-2022-0969",
|
||||
route: "covered+not-used",
|
||||
imports: httpServerDoSVuln,
|
||||
pkg: pkgWithSymbols("stdlib", map[string][]string{"net/http": {"Get", "NewRequest", "Client.Do", "Transport.RoundTrip"}}),
|
||||
wantSatisfied: false,
|
||||
fpOrFn: "FP correctly avoided: only http client symbols used",
|
||||
},
|
||||
{
|
||||
name: "GO-2022-0969 net/http-DoS / no-symbol-evidence: SBOM without symbols -> module fallback match",
|
||||
advisoryID: "GO-2022-0969",
|
||||
route: "no-symbol-evidence",
|
||||
imports: httpServerDoSVuln,
|
||||
pkg: noSymbols("stdlib"),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "no-symbol fallback: suppressing here would silently drop a real vuln",
|
||||
},
|
||||
|
||||
// --- module-level sanity: advisory carries NO imports; symbols are irrelevant, always match ---
|
||||
{
|
||||
name: "module-level advisory / no imports + symbols present -> always match",
|
||||
advisoryID: "module-level",
|
||||
route: "no-imports",
|
||||
imports: nil,
|
||||
pkg: pkgWithSymbols("golang.org/x/crypto", map[string][]string{"golang.org/x/crypto/ssh": {"Dial"}}),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "unaffected: pure module-level advisory",
|
||||
},
|
||||
{
|
||||
name: "module-level advisory / no imports + no symbols -> always match",
|
||||
advisoryID: "module-level",
|
||||
route: "no-imports",
|
||||
imports: nil,
|
||||
pkg: noSymbols("golang.org/x/crypto"),
|
||||
wantSatisfied: true,
|
||||
fpOrFn: "unaffected: pure module-level advisory",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := New(tt.imports).Satisfied(tt.pkg)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.wantSatisfied, got,
|
||||
"advisory=%s route=%s (%s)", tt.advisoryID, tt.route, tt.fpOrFn)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
hashiVer "github.com/anchore/go-version"
|
||||
@@ -9,6 +10,14 @@ import (
|
||||
|
||||
var _ Comparator = (*golangVersion)(nil)
|
||||
|
||||
// goGluedPrereleasePattern matches a version whose prerelease tag is glued directly onto the
|
||||
// release with no "-" separator (e.g. "9.6.0b1"). Go modules never publish these — real tags are
|
||||
// canonical semver ("v9.6.0-beta.1") — so such strings only reach us as non-canonical advisory
|
||||
// data (e.g. a GHSA range bound). The tag begins with a letter right after the numeric version;
|
||||
// a "-" (already-separated prerelease) or "+" (build metadata) does not match, so only otherwise
|
||||
// unparseable versions are touched.
|
||||
var goGluedPrereleasePattern = regexp.MustCompile(`^(v?\d+\.\d+(?:\.\d+)?)([a-zA-Z].*)$`)
|
||||
|
||||
type golangVersion struct {
|
||||
raw string
|
||||
obj *hashiVer.Version
|
||||
@@ -28,8 +37,13 @@ func newGolangVersion(v string) (golangVersion, error) {
|
||||
// go1.24 creates non-dot separated build metadata fields, e.g. +incompatible+dirty
|
||||
// Fix up as per semver spec
|
||||
before, after, found := strings.Cut(fixedUp, "+")
|
||||
// insert the missing "-" before a glued-on prerelease tag (e.g. "9.6.0b1" -> "9.6.0-b1") so
|
||||
// non-canonical advisory version bounds compare instead of falling back to fuzzy matching.
|
||||
before = goGluedPrereleasePattern.ReplaceAllString(before, "$1-$2")
|
||||
if found {
|
||||
fixedUp = before + "+" + strings.ReplaceAll(after, "+", ".")
|
||||
} else {
|
||||
fixedUp = before
|
||||
}
|
||||
|
||||
semver, err := hashiVer.NewSemver(fixedUp)
|
||||
|
||||
@@ -40,6 +40,20 @@ func TestGolangVersion_Constraint(t *testing.T) {
|
||||
constraint: "",
|
||||
satisfied: true,
|
||||
},
|
||||
{
|
||||
// non-canonical advisory bound (glued-on prerelease, no "-" separator): go-redis
|
||||
// GHSA-92cp-5422-2mw7 carries ">= 9.6.0b1, < 9.6.3"; 9.6.0 must fall inside it.
|
||||
name: "glued-on prerelease range bound is comparable",
|
||||
version: "v9.6.0",
|
||||
constraint: ">= 9.6.0b1, < 9.6.3",
|
||||
satisfied: true,
|
||||
},
|
||||
{
|
||||
name: "glued-on prerelease sorts before its release",
|
||||
version: "v9.6.0",
|
||||
constraint: "> 9.6.0b1",
|
||||
satisfied: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
||||
Reference in new issue
Block a user