diff --git a/server/vulnerabilities/osv/downloader.go b/server/vulnerabilities/osv/downloader.go index f8a61edf81..bf4ac1ceb3 100644 --- a/server/vulnerabilities/osv/downloader.go +++ b/server/vulnerabilities/osv/downloader.go @@ -174,9 +174,15 @@ func computeFileSHA256(path string) (string, error) { } type SyncResult struct { + // Downloaded versions were fetched from the release and saved to disk. Downloaded []string - Skipped []string - Failed []string + // Skipped versions already had a local file with a matching checksum. + Skipped []string + // NotInRelease versions had no matching asset in the release (likely caused by a date-boundary). + NotInRelease []string + // Failed versions had an asset in the release but the download or + // checksum verification failed. + Failed []string } // downloadFunc is a function that downloads an asset to a destination path @@ -190,9 +196,10 @@ func SyncOSV(ctx context.Context, dstDir string, ubuntuVersions []string, date t // syncOSVWithDownloader is the internal implementation that accepts a custom download function for testing func syncOSVWithDownloader(ctx context.Context, dstDir string, ubuntuVersions []string, date time.Time, release *ReleaseInfo, download downloadFunc) (*SyncResult, error) { result := &SyncResult{ - Downloaded: make([]string, 0), - Skipped: make([]string, 0), - Failed: make([]string, 0), + Downloaded: make([]string, 0), + Skipped: make([]string, 0), + NotInRelease: make([]string, 0), + Failed: make([]string, 0), } for _, ubuntuVersion := range ubuntuVersions { @@ -201,8 +208,7 @@ func syncOSVWithDownloader(ctx context.Context, dstDir string, ubuntuVersions [] assetInfo, ok := release.Assets[filename] if !ok { - // Artifact not available, skip - result.Skipped = append(result.Skipped, ubuntuVersion) + result.NotInRelease = append(result.NotInRelease, ubuntuVersion) continue } diff --git a/server/vulnerabilities/osv/sync_test.go b/server/vulnerabilities/osv/sync_test.go index 55727dda9c..547024bb79 100644 --- a/server/vulnerabilities/osv/sync_test.go +++ b/server/vulnerabilities/osv/sync_test.go @@ -119,6 +119,30 @@ func TestRemoveOldOSVArtifactsWithSkippedVersions(t *testing.T) { require.True(t, os.IsNotExist(err), "old artifact from day before should be removed even when version was skipped") } +func TestRemoveOldOSVArtifactsDateBoundaryRace(t *testing.T) { + tmpDir := t.TempDir() + // now is April 10 but the release only created April 9 artifacts. + today := time.Date(2026, 4, 10, 0, 5, 0, 0, time.UTC) + + files := []string{ + "osv-ubuntu-2404-2026-04-09.json.gz", // Yesterday's artifact (only one available) + } + + for _, file := range files { + err := os.WriteFile(filepath.Join(tmpDir, file), []byte("test"), 0o644) + require.NoError(t, err) + } + + // 2404 is in NotInRelease, not Skipped + // so removeOldOSVArtifacts should not touch it + err := removeOldOSVArtifacts(today, tmpDir, []string{}) + require.NoError(t, err) + + // Yesterday's artifact must still exist since the version wasn't in the successful set + _, err = os.Stat(filepath.Join(tmpDir, "osv-ubuntu-2404-2026-04-09.json.gz")) + require.NoError(t, err, "old artifact must be preserved when version is not in release") +} + func TestGetNeededUbuntuVersions(t *testing.T) { tests := []struct { name string @@ -244,10 +268,10 @@ func TestSyncOSVFaultTolerance(t *testing.T) { } result, err := syncOSVWithDownloader(context.Background(), tmpDir, versions, date, release, mockDownload) - require.NoError(t, err) + require.Error(t, err) require.NotNil(t, result) - require.Contains(t, result.Skipped, "2504", "2504 artifact not in release, should be skipped") + require.Contains(t, result.NotInRelease, "2504", "2504 artifact not in release") require.Contains(t, result.Failed, "2204", "2204 download failed, should be in Failed") }