Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion grype/db/internal/versionutil/constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func EnforceSemVerConstraint(constraint string) string {
}

func AndConstraints(c ...string) string {
return strings.Join(c, " ")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change isn't correct, for example you can see it breaks unit tests for another OSV provider: https://github.com/anchore/grype/actions/runs/25853467114/job/76529191535?pr=3264#step:4:108

I think the arguments passed to AndConstraint are wrong in the BellSoft case?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is to fix anchore/vunnel#924 (comment)

The fix is incomplete/incorrect and need to be fixed in its order.

return strings.Join(c, ", ")
}

func OrConstraints(c ...string) string {
Expand Down
147 changes: 147 additions & 0 deletions grype/db/v6/build/transformers/osv/testdata/BELL-CVE-2025-59375.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"affected": [
{
"package": {
"ecosystem": "Alpaquita:23",
"name": "expat",
"purl": "pkg:apk/alpaquita/expat?arch=source&distro=23"
},
"ranges": [
{
"events": [
{
"introduced": "2.5.0-r0"
},
{
"fixed": "2.7.2-r0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Alpaquita:25",
"name": "expat",
"purl": "pkg:apk/alpaquita/expat?arch=source&distro=25"
},
"ranges": [
{
"events": [
{
"introduced": "2.7.1-r0"
},
{
"fixed": "2.7.2-r0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Alpaquita:stream",
"name": "expat",
"purl": "pkg:apk/alpaquita/expat?arch=source&distro=stream"
},
"ranges": [
{
"events": [
{
"introduced": "2.4.9-r0"
},
{
"fixed": "2.7.2-r0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "BellSoft Hardened Containers:23",
"name": "expat",
"purl": "pkg:apk/bellsoft-hardened-containers/expat?arch=source&distro=23"
},
"ranges": [
{
"events": [
{
"introduced": "2.5.0-r0"
},
{
"fixed": "2.7.2-r0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "BellSoft Hardened Containers:25",
"name": "expat",
"purl": "pkg:apk/bellsoft-hardened-containers/expat?arch=source&distro=25"
},
"ranges": [
{
"events": [
{
"introduced": "2.7.1-r0"
},
{
"fixed": "2.7.2-r0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "BellSoft Hardened Containers:stream",
"name": "expat",
"purl": "pkg:apk/bellsoft-hardened-containers/expat?arch=source&distro=stream"
},
"ranges": [
{
"events": [
{
"introduced": "2.4.9-r0"
},
{
"fixed": "2.7.2-r0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"id": "BELL-CVE-2025-59375",
"modified": "2025-12-23T13:05:51.859654Z",
"published": "2025-09-15T11:56:23.866967Z",
"references": [
{
"type": "ADVISORY",
"url": "https://docs.bell-sw.com/security/cves/CVE-2025-59375"
},
{
"type": "ADVISORY",
"url": "https://docs.bell-sw.com/security/advisories/BELL-SA-2025-13"
}
],
"schema_version": "1.7.4",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"upstream": [
"CVE-2025-59375"
]
}
1 change: 1 addition & 0 deletions grype/db/v6/build/transformers/osv/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ type Strategy interface {
// true handles the record. Add new providers by appending here.
var strategies = []Strategy{
almaStrategy{},
bellsoftStrategy{},
bitnamiStrategy{},
}
117 changes: 117 additions & 0 deletions grype/db/v6/build/transformers/osv/transform_bellsoft.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package osv

import (
"fmt"
"sort"
"strings"

"github.com/google/osv-scanner/pkg/models"

"github.com/anchore/grype/grype/db/data"
"github.com/anchore/grype/grype/db/internal/provider/unmarshal"
"github.com/anchore/grype/grype/db/provider"
db "github.com/anchore/grype/grype/db/v6"
"github.com/anchore/grype/grype/db/v6/build/transformers"
"github.com/anchore/grype/grype/db/v6/build/transformers/internal"
"github.com/anchore/grype/grype/db/v6/name"
"github.com/anchore/syft/syft/pkg"
)

// bellsoftStrategy handles BELL-* records from BellSoft's vulnerability database.
// BellSoft records describe *affected* version ranges of upstream components
// (apache, node, spark, etc.) packaged by BellSoft.
//
// BellSoft-specific decisions:
// - CVE refs are in `upstream` field
// - Package type comes from the PURL (always present, e.g. pkg:apk/alpaquita/apache).
// - Ecosystem stays as "BellSoft" — there is no underlying distro.
// - No qualifiers are emitted. `database_specific.cpes` is intentionally
// dropped: the bellsoft matcher never queries by CPE, the platform CPE
// qualifier is a runtime no-op for application CPEs, and CPEs without
// ecosystem context produce noisy matches.
type bellsoftStrategy struct{}

func (bellsoftStrategy) Matches(id string) bool {
return strings.HasPrefix(id, "BELL-")
}

func (bellsoftStrategy) Transform(vuln unmarshal.OSVVulnerability, state provider.State) ([]data.Entry, error) {
Comment thread
willmurphyscode marked this conversation as resolved.
severities, err := getSeverities(vuln)
if err != nil {
return nil, fmt.Errorf("unable to obtain severities: %w", err)
}

in := []any{
db.VulnerabilityHandle{
Name: vuln.ID,
ProviderID: state.Provider,
Provider: provider.Model(state),
Status: db.VulnerabilityActive,
ModifiedDate: &vuln.Modified,
PublishedDate: &vuln.Published,
BlobValue: &db.VulnerabilityBlob{
ID: vuln.ID,
Description: vuln.Details,
References: bellsoftReferences(vuln),
Aliases: vuln.Aliases,
Severities: severities,
},
},
}

for _, aph := range bellsoftAffectedPackages(vuln) {
in = append(in, aph)
}
return transformers.NewEntries(in...), nil
}

func bellsoftReferences(vuln unmarshal.OSVVulnerability) []db.Reference {
var refs []db.Reference
for _, ref := range vuln.References {
refs = append(refs, db.Reference{
URL: ref.URL,
Tags: []string{string(ref.Type)},
})
}
return refs
}

func bellsoftAffectedPackages(vuln unmarshal.OSVVulnerability) []db.AffectedPackageHandle {
if len(vuln.Affected) == 0 {
return nil
}
var aphs []db.AffectedPackageHandle
for _, affected := range vuln.Affected {
var ranges []db.Range
for _, r := range affected.Ranges {
ranges = append(ranges, getGrypeRangesFromRange(r, bellsoftRangeType(r.Type))...)
}
aphs = append(aphs, db.AffectedPackageHandle{
Package: bellsoftPackage(affected.Package),
BlobValue: &db.PackageBlob{
CVEs: vuln.Aliases, // FIXME: should be `vuln.Upstream`

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upstream field is not supported in the "github.com/google/osv-scanner/pkg/models"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment. Can you elaborate a bit?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure.
https://ossf.github.io/osv-schema/#upstream-field
it is advised to use it for a "root" CVE. But the "github.com/google/osv-scanner/pkg/models" structures do not have it (yet).

Ranges: ranges,
},
})
}
sort.Sort(internal.ByAffectedPackage(aphs))
return aphs
}

func bellsoftPackage(p models.Package) *db.Package {
pkgType := pkg.TypeFromPURL(p.Purl)
return &db.Package{
Ecosystem: string(p.Ecosystem),
Name: name.Normalize(p.Name, pkgType),
}
}

// bellsoftRangeType maps an OSV range type to the grype version-format string
// for BellSoft records. SEMVER ranges describe apk-flavored semver
// (separate version comparator); other OSV types fall through to the default.
func bellsoftRangeType(t models.RangeType) string {
if t == models.RangeSemVer {
return "apk"
}
return defaultRangeType(t)
}
6 changes: 6 additions & 0 deletions grype/distro/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const (
CentOS Type = "centos"
Fedora Type = "fedora"
Alpine Type = "alpine"
Alpaquita Type = "alpaquita"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help me understand the relationship between alpaquita and bellsoft hardened containers? It looks like they're both in OSV but with overlap and the same vendor prefix? https://osv.dev/list?ecosystem=Alpaquita and https://osv.dev/list?ecosystem=BellSoft+Hardened+Containers ?

It looks like their two different distros and we should just model them as two different distros, and their relationship is just that the same company works on them?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BellSoftHardenedContainers Type = "bellsoft-hardened-containers"
Busybox Type = "busybox"
AmazonLinux Type = "amazonlinux"
OracleLinux Type = "oraclelinux"
Expand Down Expand Up @@ -48,6 +50,8 @@ var All = []Type{
CentOS,
Fedora,
Alpine,
Alpaquita,
BellSoftHardenedContainers,
Busybox,
AmazonLinux,
OracleLinux,
Expand Down Expand Up @@ -80,6 +84,8 @@ var IDMapping = map[string]Type{
"centos": CentOS,
"fedora": Fedora,
"alpine": Alpine,
"alpaquita": Alpaquita,
"bellsoft-hardened-containers": BellSoftHardenedContainers,
"busybox": Busybox,
"amzn": AmazonLinux,
"ol": OracleLinux,
Expand Down
Loading