diff --git a/cmd/osv-scanner/__snapshots__/main_test.snap b/cmd/osv-scanner/__snapshots__/main_test.snap index 9991b044ce..eac9877f42 100755 --- a/cmd/osv-scanner/__snapshots__/main_test.snap +++ b/cmd/osv-scanner/__snapshots__/main_test.snap @@ -2091,6 +2091,48 @@ databases can only be downloaded when running in offline mode --- +[TestRun_LocalDatabases_AlwaysOffline/#00 - 1] +Scanning dir ./fixtures/locks-requirements +Scanned /fixtures/locks-requirements/my-requirements.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements-dev.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements.prod.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements.txt file and found 3 packages +Scanned /fixtures/locks-requirements/the_requirements_for_test.txt file and found 1 package +Scanning dir ./fixtures/locks-many +Scanned /fixtures/locks-many/Gemfile.lock file and found 1 package +Scanned /fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 14 packages +Scanned /fixtures/locks-many/composer.lock file and found 1 package +Scanned /fixtures/locks-many/package-lock.json file and found 1 package +Scanned /fixtures/locks-many/yarn.lock file and found 1 package + +--- + +[TestRun_LocalDatabases_AlwaysOffline/#00 - 2] +could not find local databases for ecosystems: Alpine, Packagist, PyPI, RubyGems, npm + +--- + +[TestRun_LocalDatabases_AlwaysOffline/#00 - 3] +Scanning dir ./fixtures/locks-requirements +Scanned /fixtures/locks-requirements/my-requirements.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements-dev.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements.prod.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements.txt file and found 3 packages +Scanned /fixtures/locks-requirements/the_requirements_for_test.txt file and found 1 package +Scanning dir ./fixtures/locks-many +Scanned /fixtures/locks-many/Gemfile.lock file and found 1 package +Scanned /fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 14 packages +Scanned /fixtures/locks-many/composer.lock file and found 1 package +Scanned /fixtures/locks-many/package-lock.json file and found 1 package +Scanned /fixtures/locks-many/yarn.lock file and found 1 package + +--- + +[TestRun_LocalDatabases_AlwaysOffline/#00 - 4] +could not find local databases for ecosystems: Alpine, Packagist, PyPI, RubyGems, npm + +--- + [TestRun_LockfileWithExplicitParseAs/#00 - 1] --- diff --git a/cmd/osv-scanner/main_test.go b/cmd/osv-scanner/main_test.go index 2dd2290dbf..5a4e579854 100644 --- a/cmd/osv-scanner/main_test.go +++ b/cmd/osv-scanner/main_test.go @@ -641,6 +641,35 @@ func TestRun_LocalDatabases(t *testing.T) { } } +func TestRun_LocalDatabases_AlwaysOffline(t *testing.T) { + t.Parallel() + + tests := []cliTestCase{ + // a bunch of different lockfiles and ecosystem + { + name: "", + args: []string{"", "--config=./fixtures/osv-scanner-empty-config.toml", "--experimental-offline", "./fixtures/locks-requirements", "./fixtures/locks-many"}, + exit: 127, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + testDir := testutility.CreateTestDir(t) + old := tt.args + tt.args = []string{"", "--experimental-local-db-path", testDir} + tt.args = append(tt.args, old[1:]...) + + // run each test twice since they should provide the same output, + // and the second run should be fast as the db is already available + testCli(t, tt) + testCli(t, tt) + }) + } +} + func TestRun_Licenses(t *testing.T) { t.Parallel() tests := []cliTestCase{ diff --git a/internal/local/check.go b/internal/local/check.go index c1fd9d996d..b8c2b4e95c 100644 --- a/internal/local/check.go +++ b/internal/local/check.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "path" + "slices" + "strings" "github.com/google/osv-scanner/pkg/lockfile" "github.com/google/osv-scanner/pkg/models" @@ -116,6 +118,9 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca return db, nil } + // slice to track ecosystems that did not have an offline database available + var missingDbs []string + for _, query := range query.Queries { pkg, err := toPackageDetails(query) @@ -143,8 +148,13 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca db, err := loadDBFromCache(pkg.Ecosystem) if err != nil { - // currently, this will actually only error if the PURL cannot be parses - r.Errorf("could not load db for %s ecosystem: %v\n", pkg.Ecosystem, err) + if errors.Is(err, ErrOfflineDatabaseNotFound) { + missingDbs = append(missingDbs, string(pkg.Ecosystem)) + } else { + // the most likely error at this point is that the PURL could not be parsed + r.Errorf("could not load db for %s ecosystem: %v\n", pkg.Ecosystem, err) + } + results = append(results, osv.Response{Vulns: []models.Vulnerability{}}) continue @@ -153,5 +163,12 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca results = append(results, osv.Response{Vulns: db.VulnerabilitiesAffectingPackage(pkg)}) } + if len(missingDbs) > 0 { + missingDbs = slices.Compact(missingDbs) + slices.Sort(missingDbs) + + r.Errorf("could not find local databases for ecosystems: %s\n", strings.Join(missingDbs, ", ")) + } + return &osv.HydratedBatchedResponse{Results: results}, nil }