Skip to content

Commit 1efd384

Browse files
committed
fix(scanner): scope OpenVSX extension OSV lookups to marketplace ecosystem
Pass explicit OsvEcosystem values when enriching VS Code and OpenVSX extension packages via Insights API v2. Without this scoping, npm malware advisories with colliding package names were incorrectly applied to OpenVSX extensions during vet scan --vsx. Fixes #730 Signed-off-by: syf2211 <syf2211@users.noreply.github.com>
1 parent 23b07a0 commit 1efd384

4 files changed

Lines changed: 126 additions & 16 deletions

File tree

pkg/models/manifest_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@ import (
66
"github.com/stretchr/testify/assert"
77
)
88

9+
func TestGetOsvEcosystem(t *testing.T) {
10+
cases := []struct {
11+
name string
12+
input string
13+
expected string
14+
}{
15+
{name: "vscode marketplace", input: EcosystemVSCodeExtensions, expected: "vscode"},
16+
{name: "openvsx marketplace", input: EcosystemOpenVSXExtensions, expected: "vscode:open-vsx.org"},
17+
{name: "npm unchanged", input: EcosystemNpm, expected: ""},
18+
{name: "distro unchanged", input: "Alpine:v3.23", expected: ""},
19+
}
20+
21+
for _, tc := range cases {
22+
t.Run(tc.name, func(t *testing.T) {
23+
assert.Equal(t, tc.expected, GetOsvEcosystem(tc.input))
24+
})
25+
}
26+
}
27+
928
func TestNewPackageManifestFromContainerImage(t *testing.T) {
1029
cases := []struct {
1130
name string

pkg/models/models.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,20 @@ func (pm *PackageManifest) GetSpecEcosystem() modelspec.Ecosystem {
325325
}
326326
}
327327

328+
// GetOsvEcosystem returns the OSV ecosystem identifier used for vulnerability
329+
// lookups. Extension marketplaces use OSV-specific names that differ from
330+
// internal model ecosystem strings.
331+
func GetOsvEcosystem(modelEcosystem string) string {
332+
switch modelEcosystem {
333+
case EcosystemVSCodeExtensions:
334+
return "vscode"
335+
case EcosystemOpenVSXExtensions:
336+
return "vscode:open-vsx.org"
337+
default:
338+
return ""
339+
}
340+
}
341+
328342
// Map the control tower spec ecosystem to model ecosystem
329343
func GetModelEcosystem(ecosystem packagev1.Ecosystem) string {
330344
switch ecosystem {

pkg/scanner/enrich_insightsv2.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,7 @@ func (e *insightsBasedPackageEnricherV2) Name() string {
3737
return "Insights API v2"
3838
}
3939

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

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

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

70+
return req
71+
}
72+
73+
// Enrich will enrich the package using Insights V2 API. However, most of the
74+
// analysers and reporters in vet are coupled with Insights V1 data model. Till
75+
// we are able to drive a major refactor to decouple them, we need to convert V2
76+
// data model to V1 data model while preserving the V2 data. This will ensure
77+
//
78+
// - Existing analysers and reporters continue to work without any changes.
79+
// - We can start using V2 data model in new analysers and reporters.
80+
func (e *insightsBasedPackageEnricherV2) Enrich(pkg *models.Package,
81+
cb PackageDependencyCallbackFn,
82+
) error {
83+
req := buildInsightsV2Request(pkg)
84+
7785
res, err := e.client.GetPackageVersionInsight(context.Background(), req)
7886
if err != nil {
7987
logger.Debugf("Failed to enrich package: %s/%s: %v",

pkg/scanner/enrich_insightsv2_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,77 @@ import (
55

66
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
77
"github.com/stretchr/testify/assert"
8+
9+
"github.com/safedep/vet/pkg/models"
810
)
911

12+
func TestBuildInsightsV2Request(t *testing.T) {
13+
t.Run("scopes openvsx extensions to vscode:open-vsx.org OSV ecosystem", func(t *testing.T) {
14+
pkg := &models.Package{
15+
Manifest: models.NewPackageManifestFromLocal(
16+
"/home/user/.cursor/extensions/extensions.json",
17+
models.EcosystemOpenVSXExtensions,
18+
),
19+
PackageDetails: models.NewPackageDetail(
20+
models.EcosystemOpenVSXExtensions,
21+
"llvm-vs-code-extensions.vscode-clangd",
22+
"0.4.0",
23+
),
24+
}
25+
26+
req := buildInsightsV2Request(pkg)
27+
28+
assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_OPENVSX, req.GetPackageVersion().GetPackage().GetEcosystem())
29+
assert.Equal(t, "llvm-vs-code-extensions.vscode-clangd", req.GetPackageVersion().GetPackage().GetName())
30+
assert.Equal(t, "0.4.0", req.GetPackageVersion().GetVersion())
31+
assert.Equal(t, "vscode:open-vsx.org", req.GetOsvEcosystem())
32+
})
33+
34+
t.Run("scopes vscode extensions to vscode OSV ecosystem", func(t *testing.T) {
35+
pkg := &models.Package{
36+
Manifest: models.NewPackageManifestFromLocal(
37+
"/home/user/.vscode/extensions/extensions.json",
38+
models.EcosystemVSCodeExtensions,
39+
),
40+
PackageDetails: models.NewPackageDetail(
41+
models.EcosystemVSCodeExtensions,
42+
"publisher.extension",
43+
"1.0.0",
44+
),
45+
}
46+
47+
req := buildInsightsV2Request(pkg)
48+
49+
assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_VSCODE, req.GetPackageVersion().GetPackage().GetEcosystem())
50+
assert.Equal(t, "vscode", req.GetOsvEcosystem())
51+
})
52+
53+
t.Run("passes raw distro ecosystem when protobuf enum is unspecified", func(t *testing.T) {
54+
ecosystem := "Alpine:v3.23"
55+
pkg := &models.Package{
56+
Manifest: models.NewPackageManifestFromLocal("lib/apk/db/installed", ecosystem),
57+
PackageDetails: models.NewPackageDetail(ecosystem, "busybox", "1.36.1"),
58+
}
59+
60+
req := buildInsightsV2Request(pkg)
61+
62+
assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_UNSPECIFIED, req.GetPackageVersion().GetPackage().GetEcosystem())
63+
assert.Equal(t, ecosystem, req.GetOsvEcosystem())
64+
})
65+
66+
t.Run("does not set OSV ecosystem for npm packages", func(t *testing.T) {
67+
pkg := &models.Package{
68+
Manifest: models.NewPackageManifestFromLocal("package-lock.json", models.EcosystemNpm),
69+
PackageDetails: models.NewPackageDetail(models.EcosystemNpm, "lodash", "4.17.21"),
70+
}
71+
72+
req := buildInsightsV2Request(pkg)
73+
74+
assert.Equal(t, packagev1.Ecosystem_ECOSYSTEM_NPM, req.GetPackageVersion().GetPackage().GetEcosystem())
75+
assert.Empty(t, req.GetOsvEcosystem())
76+
})
77+
}
78+
1079
func TestCurrentVersionFromAvailableVersions(t *testing.T) {
1180
t.Run("prefers registry default version", func(t *testing.T) {
1281
versions := []*packagev1.PackageAvailableVersion{

0 commit comments

Comments
 (0)