Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 19 additions & 0 deletions pkg/models/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ import (
"github.com/stretchr/testify/assert"
)

func TestGetOsvEcosystem(t *testing.T) {
cases := []struct {
name string
input string
expected string
}{
{name: "vscode marketplace", input: EcosystemVSCodeExtensions, expected: "vscode"},
{name: "openvsx marketplace", input: EcosystemOpenVSXExtensions, expected: "vscode:open-vsx.org"},
{name: "npm unchanged", input: EcosystemNpm, expected: ""},
{name: "distro unchanged", input: "Alpine:v3.23", expected: ""},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, GetOsvEcosystem(tc.input))
})
}
}

func TestNewPackageManifestFromContainerImage(t *testing.T) {
cases := []struct {
name string
Expand Down
14 changes: 14 additions & 0 deletions pkg/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,20 @@ func (pm *PackageManifest) GetSpecEcosystem() modelspec.Ecosystem {
}
}

// GetOsvEcosystem returns the OSV ecosystem identifier used for vulnerability
// lookups. Extension marketplaces use OSV-specific names that differ from
// internal model ecosystem strings.
func GetOsvEcosystem(modelEcosystem string) string {
switch modelEcosystem {
case EcosystemVSCodeExtensions:
return "vscode"
case EcosystemOpenVSXExtensions:
return "vscode:open-vsx.org"
default:
return ""
}
}

// Map the control tower spec ecosystem to model ecosystem
func GetModelEcosystem(ecosystem packagev1.Ecosystem) string {
switch ecosystem {
Expand Down
40 changes: 24 additions & 16 deletions pkg/scanner/enrich_insightsv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,7 @@ func (e *insightsBasedPackageEnricherV2) Name() string {
return "Insights API v2"
}

// Enrich will enrich the package using Insights V2 API. However, most of the
// analysers and reporters in vet are coupled with Insights V1 data model. Till
// we are able to drive a major refactor to decouple them, we need to convert V2
// data model to V1 data model while preserving the V2 data. This will ensure
//
// - Existing analysers and reporters continue to work without any changes.
// - We can start using V2 data model in new analysers and reporters.
func (e *insightsBasedPackageEnricherV2) Enrich(pkg *models.Package,
cb PackageDependencyCallbackFn,
) error {
func buildInsightsV2Request(pkg *models.Package) *insightsv2.GetPackageVersionInsightRequest {
req := &insightsv2.GetPackageVersionInsightRequest{
PackageVersion: &packagev1.PackageVersion{
Package: &packagev1.Package{
Expand All @@ -57,13 +48,15 @@ func (e *insightsBasedPackageEnricherV2) Enrich(pkg *models.Package,
},
}

// For distro ecosystems (e.g. Alpine:v3.23, Ubuntu:22.04) that don't map to
// a known protobuf enum, pass the raw ecosystem string so control-tower can
// scope the OSV query to the correct distro advisory database.
//
// + Check if the ecosystem is unspecified, to prevent from overlapping with supported ecosystem.
if osvRawEcosystem := pkg.Manifest.Ecosystem; osvRawEcosystem != "" &&
// Extension marketplaces need explicit OSV ecosystem scoping so npm advisories
// with colliding package names are not applied to VS Code / OpenVSX extensions.
if osvEcosystem := models.GetOsvEcosystem(pkg.Manifest.Ecosystem); osvEcosystem != "" {
req.OsvEcosystem = &osvEcosystem
} else if osvRawEcosystem := pkg.Manifest.Ecosystem; osvRawEcosystem != "" &&
pkg.GetControlTowerSpecEcosystem() == packagev1.Ecosystem_ECOSYSTEM_UNSPECIFIED {
// For distro ecosystems (e.g. Alpine:v3.23, Ubuntu:22.04) that don't map to
// a known protobuf enum, pass the raw ecosystem string so control-tower can
// scope the OSV query to the correct distro advisory database.
req.OsvEcosystem = &osvRawEcosystem
}

Expand All @@ -74,6 +67,21 @@ func (e *insightsBasedPackageEnricherV2) Enrich(pkg *models.Package,
req.OsvSourceName = &osvSourceName
}

return req
}

// Enrich will enrich the package using Insights V2 API. However, most of the
// analysers and reporters in vet are coupled with Insights V1 data model. Till
// we are able to drive a major refactor to decouple them, we need to convert V2
// data model to V1 data model while preserving the V2 data. This will ensure
//
// - Existing analysers and reporters continue to work without any changes.
// - We can start using V2 data model in new analysers and reporters.
func (e *insightsBasedPackageEnricherV2) Enrich(pkg *models.Package,
cb PackageDependencyCallbackFn,
) error {
req := buildInsightsV2Request(pkg)

res, err := e.client.GetPackageVersionInsight(context.Background(), req)
if err != nil {
logger.Debugf("Failed to enrich package: %s/%s: %v",
Expand Down
69 changes: 69 additions & 0 deletions pkg/scanner/enrich_insightsv2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,77 @@ import (

packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
"github.com/stretchr/testify/assert"

"github.com/safedep/vet/pkg/models"
)

func TestBuildInsightsV2Request(t *testing.T) {
t.Run("scopes openvsx extensions to vscode:open-vsx.org OSV ecosystem", func(t *testing.T) {
pkg := &models.Package{
Manifest: models.NewPackageManifestFromLocal(
"/home/user/.cursor/extensions/extensions.json",
models.EcosystemOpenVSXExtensions,
),
PackageDetails: models.NewPackageDetail(
models.EcosystemOpenVSXExtensions,
"llvm-vs-code-extensions.vscode-clangd",
"0.4.0",
),
}

req := buildInsightsV2Request(pkg)

assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_OPENVSX, req.GetPackageVersion().GetPackage().GetEcosystem())
assert.Equal(t, "llvm-vs-code-extensions.vscode-clangd", req.GetPackageVersion().GetPackage().GetName())
assert.Equal(t, "0.4.0", req.GetPackageVersion().GetVersion())
assert.Equal(t, "vscode:open-vsx.org", req.GetOsvEcosystem())
})

t.Run("scopes vscode extensions to vscode OSV ecosystem", func(t *testing.T) {
pkg := &models.Package{
Manifest: models.NewPackageManifestFromLocal(
"/home/user/.vscode/extensions/extensions.json",
models.EcosystemVSCodeExtensions,
),
PackageDetails: models.NewPackageDetail(
models.EcosystemVSCodeExtensions,
"publisher.extension",
"1.0.0",
),
}

req := buildInsightsV2Request(pkg)

assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_VSCODE, req.GetPackageVersion().GetPackage().GetEcosystem())
assert.Equal(t, "vscode", req.GetOsvEcosystem())
})

t.Run("passes raw distro ecosystem when protobuf enum is unspecified", func(t *testing.T) {
ecosystem := "Alpine:v3.23"
pkg := &models.Package{
Manifest: models.NewPackageManifestFromLocal("lib/apk/db/installed", ecosystem),
PackageDetails: models.NewPackageDetail(ecosystem, "busybox", "1.36.1"),
}

req := buildInsightsV2Request(pkg)

assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_UNSPECIFIED, req.GetPackageVersion().GetPackage().GetEcosystem())
assert.Equal(t, ecosystem, req.GetOsvEcosystem())
})

t.Run("does not set OSV ecosystem for npm packages", func(t *testing.T) {
pkg := &models.Package{
Manifest: models.NewPackageManifestFromLocal("package-lock.json", models.EcosystemNpm),
PackageDetails: models.NewPackageDetail(models.EcosystemNpm, "lodash", "4.17.21"),
}

req := buildInsightsV2Request(pkg)

assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_NPM, req.GetPackageVersion().GetPackage().GetEcosystem())
assert.Empty(t, req.GetOsvEcosystem())
})
}

func TestCurrentVersionFromAvailableVersions(t *testing.T) {
t.Run("prefers registry default version", func(t *testing.T) {
versions := []*packagev1.PackageAvailableVersion{
Expand Down
Loading