From a22fdf197e29bda90c42c8a222d80198ca860f1d Mon Sep 17 00:00:00 2001 From: mrhid6 Date: Thu, 6 Aug 2026 11:55:01 +0100 Subject: [PATCH] feat: map OS family and version to trivy-db advisory buckets --- server/internal/vulndb/ecosystem.go | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 server/internal/vulndb/ecosystem.go diff --git a/server/internal/vulndb/ecosystem.go b/server/internal/vulndb/ecosystem.go new file mode 100644 index 0000000..97191d4 --- /dev/null +++ b/server/internal/vulndb/ecosystem.go @@ -0,0 +1,61 @@ +package vulndb + +import ( + "fmt" + "strings" +) + +// rhelRebuilds share Red Hat's advisory feed rather than publishing their own. +var rhelRebuilds = map[string]bool{ + "redhat": true, "centos": true, "rocky": true, "alma": true, "oracle": true, +} + +// Bucket maps an OS family and version onto the trivy-db bucket that holds its +// advisories. +// +// It returns ErrUnsupportedFamily rather than a best guess when we have no +// feed. A scan that cannot be performed must say so; reporting zero findings +// for a distribution we do not cover is indistinguishable from reporting a +// clean host, and one of those is a lie. +func Bucket(family, versionID string) (string, error) { + family = strings.ToLower(strings.TrimSpace(family)) + versionID = strings.TrimSpace(versionID) + + switch { + case family == "debian" || family == "ubuntu": + if versionID == "" { + return "", fmt.Errorf("%s requires a version id", family) + } + return family + " " + versionID, nil + + case family == "alpine": + if versionID == "" { + return "", fmt.Errorf("alpine requires a version id") + } + return "alpine " + majorMinor(versionID), nil + + case rhelRebuilds[family]: + if versionID == "" { + return "", fmt.Errorf("%s requires a version id", family) + } + return "redhat " + major(versionID), nil + + default: + return "", fmt.Errorf("%w: %s", ErrUnsupportedFamily, family) + } +} + +func major(v string) string { + if i := strings.Index(v, "."); i != -1 { + return v[:i] + } + return v +} + +func majorMinor(v string) string { + parts := strings.Split(v, ".") + if len(parts) >= 2 { + return parts[0] + "." + parts[1] + } + return v +}