From 8107f5b1a326ebe6f6ce236ceccc54b0bcf41f98 Mon Sep 17 00:00:00 2001 From: John Saurabh Date: Wed, 3 Jun 2026 16:05:33 -0700 Subject: [PATCH 1/3] fix: use commit query for npm packages resolved from git URLs Packages from yarn.lock that are pinned to a git URL (e.g. resolved via git+https://) carry ecosystem=npm because that reflects the file they were found in, but they were never published to the npm registry. Querying OSV.dev by npm ecosystem+name produces false positive matches against unrelated packages that happen to share the same name on npm. The yarnlock extractor already populates SourceCode.Commit for these dependencies via commitextractor.TryExtractCommit. When a commit hash is present for an npm-ecosystem package, use a commit query instead so the lookup is scoped to the actual upstream git commit. Fixes #2850 --- internal/clients/clientimpl/osvmatcher/osvmatcher.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/clients/clientimpl/osvmatcher/osvmatcher.go b/internal/clients/clientimpl/osvmatcher/osvmatcher.go index 6f3b50a4f79..ea8775c3ff2 100644 --- a/internal/clients/clientimpl/osvmatcher/osvmatcher.go +++ b/internal/clients/clientimpl/osvmatcher/osvmatcher.go @@ -139,6 +139,18 @@ func (matcher *OSVMatcher) MatchVulnerabilities(ctx context.Context, pkgs []*ext } func pkgToQuery(pkg *extractor.Package) *api.Query { + // A git-pinned npm dependency (e.g. resolved via git+https:// in yarn.lock) has + // ecosystem=npm but was not fetched from the npm registry. Querying by npm ecosystem + // causes false positives from unrelated packages that share the same name on npm. + // Use a commit query instead, which the yarnlock extractor already populates. + if imodels.Commit(pkg) != "" && imodels.Ecosystem(pkg).Ecosystem == osvconstants.EcosystemNPM { + return &api.Query{ + Param: &api.Query_Commit{ + Commit: imodels.Commit(pkg), + }, + } + } + if imodels.Name(pkg) != "" && !imodels.Ecosystem(pkg).IsEmpty() && imodels.Version(pkg) != "" { name := imodels.Name(pkg) From 0bcc3abc423a55ed2a4dbfa2daa9e52134d77a17 Mon Sep 17 00:00:00 2001 From: John Saurabh Date: Wed, 3 Jun 2026 16:05:47 -0700 Subject: [PATCH 2/3] test: add unit tests for pkgToQuery git-pinned npm behaviour Verify that regular npm packages continue to use ecosystem queries, and that npm packages with a populated commit hash (git-pinned via yarn.lock) use a commit query instead. --- .../clientimpl/osvmatcher/osvmatcher_test.go | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/internal/clients/clientimpl/osvmatcher/osvmatcher_test.go b/internal/clients/clientimpl/osvmatcher/osvmatcher_test.go index 1e4e7b410c1..ee535e5b510 100644 --- a/internal/clients/clientimpl/osvmatcher/osvmatcher_test.go +++ b/internal/clients/clientimpl/osvmatcher/osvmatcher_test.go @@ -100,6 +100,53 @@ func TestOSVMatcher_MatchVulnerabilities(t *testing.T) { } } +func TestPkgToQuery(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pkg *extractor.Package + want *api.Query + }{ + { + name: "regular npm package uses ecosystem query", + pkg: &extractor.Package{ + Name: "lodash", + Version: "4.17.21", + PURLType: purl.TypeNPM, + }, + want: &api.Query{ + Package: &osvschema.Package{Name: "lodash", Ecosystem: "npm"}, + Param: &api.Query_Version{Version: "4.17.21"}, + }, + }, + { + name: "git-pinned npm package uses commit query, not npm ecosystem", + pkg: &extractor.Package{ + Name: "closure-net", + Version: "0.0.0", + PURLType: purl.TypeNPM, + SourceCode: &extractor.SourceCodeIdentifier{ + Commit: "6f48f578d3e80fe7a85e530a5d95b9351433d135", + }, + }, + want: &api.Query{ + Param: &api.Query_Commit{Commit: "6f48f578d3e80fe7a85e530a5d95b9351433d135"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := pkgToQuery(tt.pkg) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("pkgToQuery() = %v, want %v", got, tt.want) + } + }) + } +} + func mustReadAll(t *testing.T, r *http.Request) []byte { t.Helper() From d069698223fb5487ecf80c05eda0ad325abf57d9 Mon Sep 17 00:00:00 2001 From: John Saurabh Date: Mon, 8 Jun 2026 23:43:50 -0700 Subject: [PATCH 3/3] test: add e2e test for yarn.lock git-pinned dependency false positive --- .../source/__snapshots__/command_test.snap | 12 ++++++ cmd/osv-scanner/scan/source/command_test.go | 6 +++ .../testdata/cassettes/TestCommand.yaml | 40 +++++++++++++++++++ .../testdata/locks-yarn-git-dep/yarn.lock | 6 +++ 4 files changed, 64 insertions(+) create mode 100644 cmd/osv-scanner/scan/source/testdata/locks-yarn-git-dep/yarn.lock diff --git a/cmd/osv-scanner/scan/source/__snapshots__/command_test.snap b/cmd/osv-scanner/scan/source/__snapshots__/command_test.snap index 870f2f06b3e..0523204707c 100755 --- a/cmd/osv-scanner/scan/source/__snapshots__/command_test.snap +++ b/cmd/osv-scanner/scan/source/__snapshots__/command_test.snap @@ -2169,6 +2169,18 @@ No issues found --- +[TestCommand/yarn_lock_with_git_pinned_dep_no_false_positive - 1] +Scanning dir ./testdata/locks-yarn-git-dep/yarn.lock +Scanned /testdata/locks-yarn-git-dep/yarn.lock file and found 1 package + +No issues found + +--- + +[TestCommand/yarn_lock_with_git_pinned_dep_no_false_positive - 2] + +--- + [TestCommandNonGit/one_specific_supported_lockfile - 1] Scanning dir /composer.lock Scanned /composer.lock file and found 1 package diff --git a/cmd/osv-scanner/scan/source/command_test.go b/cmd/osv-scanner/scan/source/command_test.go index c24f5754f9e..88e38a956c1 100644 --- a/cmd/osv-scanner/scan/source/command_test.go +++ b/cmd/osv-scanner/scan/source/command_test.go @@ -403,6 +403,12 @@ func TestCommand(t *testing.T) { Args: []string{"", "source", "--help"}, Exit: 0, }, + // yarn.lock with a git-pinned dependency should not produce false positives + { + Name: "yarn_lock_with_git_pinned_dep_no_false_positive", + Args: []string{"", "source", "./testdata/locks-yarn-git-dep/yarn.lock"}, + Exit: 0, + }, } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { diff --git a/cmd/osv-scanner/scan/source/testdata/cassettes/TestCommand.yaml b/cmd/osv-scanner/scan/source/testdata/cassettes/TestCommand.yaml index f381b2da767..f5d5e29dab0 100644 --- a/cmd/osv-scanner/scan/source/testdata/cassettes/TestCommand.yaml +++ b/cmd/osv-scanner/scan/source/testdata/cassettes/TestCommand.yaml @@ -6878,3 +6878,43 @@ interactions: status: 200 OK code: 200 duration: 0s + - request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 94 + host: api.osv.dev + body: | + { + "queries": [ + { + "commit": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" + } + ] + } + headers: + Content-Type: + - application/json + X-Test-Name: + - TestCommand/yarn_lock_with_git_pinned_dep_no_false_positive + url: https://api.osv.dev/v1/querybatch + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: 16 + body: | + { + "results": [ + {} + ] + } + headers: + Content-Length: + - "16" + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 0s diff --git a/cmd/osv-scanner/scan/source/testdata/locks-yarn-git-dep/yarn.lock b/cmd/osv-scanner/scan/source/testdata/locks-yarn-git-dep/yarn.lock new file mode 100644 index 00000000000..e87a8b70194 --- /dev/null +++ b/cmd/osv-scanner/scan/source/testdata/locks-yarn-git-dep/yarn.lock @@ -0,0 +1,6 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +"some-git-dep@git+https://github.com/some-org/some-repo.git": + version "1.0.0" + resolved "git+https://github.com/some-org/some-repo.git#a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"