diff --git a/fixtures/testdatainner/osv-scanner.toml b/fixtures/testdatainner/osv-scanner.toml index 1f843b60e6..f9be2c0f2e 100644 --- a/fixtures/testdatainner/osv-scanner.toml +++ b/fixtures/testdatainner/osv-scanner.toml @@ -1,9 +1,25 @@ [[IgnoredVulns]] id = "GO-2022-0968" -# ignore_until = 2022-11-09 +# ignoreUntil = 2022-11-09 # reason = "" # Optional reason [[IgnoredVulns]] id = "GO-2022-1059" -# ignore_until = 2022-11-09 # Optional exception expiry date +# ignoreUntil = 2022-11-09 # Optional exception expiry date # reason = "" # Optional reason + +[[PackageOverrides]] +name = "lib" +version = "1.0.0" +ecosystem = "Go" +ignore = true +# effectiveUntil = 2022-11-09 # Optional exception expiry date +reason = "abc" + +[[PackageOverrides]] +name = "my-pkg" +version = "1.0.0" +ecosystem = "Go" +ignore = true +reason = "abc" +license.override = ["MIT", "0BSD"] diff --git a/pkg/config/config.go b/pkg/config/config.go index ddd660856a..cff4e40b6f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,9 +23,10 @@ type ConfigManager struct { } type Config struct { - IgnoredVulns []IgnoreEntry `toml:"IgnoredVulns"` - LoadPath string `toml:"LoadPath"` - GoVersionOverride string `toml:"GoVersionOverride"` + IgnoredVulns []IgnoreEntry `toml:"IgnoredVulns"` + PackageOverrides []PackageOverrideEntry `toml:"PackageOverrides"` + LoadPath string `toml:"LoadPath"` + GoVersionOverride string `toml:"GoVersionOverride"` } type IgnoreEntry struct { @@ -34,19 +35,67 @@ type IgnoreEntry struct { Reason string `toml:"reason"` } +type PackageOverrideEntry struct { + Name string `toml:"name"` + // If the version is empty, the entry applies to all versions. + Version string `toml:"version"` + Ecosystem string `toml:"ecosystem"` + Ignore bool `toml:"ignore"` + License License `toml:"license"` + EffectiveUntil time.Time `toml:"effectiveUntil"` + Reason string `toml:"reason"` +} + +type License struct { + Override []string `toml:"override"` +} + func (c *Config) ShouldIgnore(vulnID string) (bool, IgnoreEntry) { - index := slices.IndexFunc(c.IgnoredVulns, func(elem IgnoreEntry) bool { return elem.ID == vulnID }) + index := slices.IndexFunc(c.IgnoredVulns, func(e IgnoreEntry) bool { return e.ID == vulnID }) if index == -1 { return false, IgnoreEntry{} } ignoredLine := c.IgnoredVulns[index] - if ignoredLine.IgnoreUntil.IsZero() { + + return shouldIgnoreTimestamp(ignoredLine.IgnoreUntil), ignoredLine +} + +func (c *Config) filterPackageVersionEntries(name string, version string, ecosystem string, condition func(PackageOverrideEntry) bool) (bool, PackageOverrideEntry) { + index := slices.IndexFunc(c.PackageOverrides, func(e PackageOverrideEntry) bool { + if ecosystem != e.Ecosystem || name != e.Name { + return false + } + + return (version == e.Version || e.Version == "") && condition(e) + }) + if index == -1 { + return false, PackageOverrideEntry{} + } + ignoredLine := c.PackageOverrides[index] + + return shouldIgnoreTimestamp(ignoredLine.EffectiveUntil), ignoredLine +} + +func (c *Config) ShouldIgnorePackageVersion(name, version, ecosystem string) (bool, PackageOverrideEntry) { + return c.filterPackageVersionEntries(name, version, ecosystem, func(e PackageOverrideEntry) bool { + return e.Ignore + }) +} + +func (c *Config) ShouldOverridePackageVersionLicense(name, version, ecosystem string) (bool, PackageOverrideEntry) { + return c.filterPackageVersionEntries(name, version, ecosystem, func(e PackageOverrideEntry) bool { + return len(e.License.Override) > 0 + }) +} + +func shouldIgnoreTimestamp(ignoreUntil time.Time) bool { + if ignoreUntil.IsZero() { // If IgnoreUntil is not set, should ignore. - return true, ignoredLine + return true } // Should ignore if IgnoreUntil is still after current time // Takes timezone offsets into account if it is specified. otherwise it's using local time - return ignoredLine.IgnoreUntil.After(time.Now()), ignoredLine + return ignoreUntil.After(time.Now()) } // Sets the override config by reading the config file at configPath. diff --git a/pkg/config/config_internal_test.go b/pkg/config/config_internal_test.go index e271543f77..4663933e10 100644 --- a/pkg/config/config_internal_test.go +++ b/pkg/config/config_internal_test.go @@ -27,6 +27,25 @@ func TestTryLoadConfig(t *testing.T) { ID: "GO-2022-1059", }, }, + PackageOverrides: []PackageOverrideEntry{ + { + Name: "lib", + Version: "1.0.0", + Ecosystem: "Go", + Ignore: true, + Reason: "abc", + }, + { + Name: "my-pkg", + Version: "1.0.0", + Ecosystem: "Go", + Reason: "abc", + Ignore: true, + License: License{ + Override: []string{"MIT", "0BSD"}, + }, + }, + }, } testPaths := []testStruct{ { @@ -69,6 +88,9 @@ func TestTryLoadConfig(t *testing.T) { if !cmp.Equal(config.IgnoredVulns, testData.config.IgnoredVulns) { t.Errorf("Configs not equal: %+v != %+v", config, testData.config) } + if !cmp.Equal(config.PackageOverrides, testData.config.PackageOverrides) { + t.Errorf("Configs not equal: %+v != %+v", config, testData.config) + } if testData.configHasErr { if configErr == nil { t.Error("Config error not returned") @@ -191,3 +213,238 @@ func TestConfig_ShouldIgnore(t *testing.T) { }) } } + +func TestConfig_ShouldIgnorePackageVersion(t *testing.T) { + t.Parallel() + + type args struct { + name string + version string + ecosystem string + } + tests := []struct { + name string + config Config + args args + wantOk bool + wantEntry PackageOverrideEntry + }{ + { + name: "Version-level entry exists", + config: Config{ + PackageOverrides: []PackageOverrideEntry{ + { + Name: "lib1", + Version: "1.0.0", + Ecosystem: "Go", + Ignore: true, + EffectiveUntil: time.Time{}, + Reason: "abc", + }, + }, + }, + args: args{ + name: "lib1", + version: "1.0.0", + ecosystem: "Go", + }, + wantOk: true, + wantEntry: PackageOverrideEntry{ + Name: "lib1", + Version: "1.0.0", + Ecosystem: "Go", + Ignore: true, + EffectiveUntil: time.Time{}, + Reason: "abc", + }, + }, + { + name: "Package-level entry exists", + config: Config{ + PackageOverrides: []PackageOverrideEntry{ + { + Name: "lib1", + Ecosystem: "Go", + Ignore: true, + EffectiveUntil: time.Time{}, + Reason: "abc", + }, + }, + }, + args: args{ + name: "lib1", + version: "1.0.0", + ecosystem: "Go", + }, + wantOk: true, + wantEntry: PackageOverrideEntry{ + Name: "lib1", + Ecosystem: "Go", + Ignore: true, + EffectiveUntil: time.Time{}, + Reason: "abc", + }, + }, + { + name: "Entry doesn't exist", + config: Config{ + PackageOverrides: []PackageOverrideEntry{ + { + Name: "lib1", + Version: "2.0.0", + Ecosystem: "Go", + Ignore: false, + EffectiveUntil: time.Time{}, + Reason: "abc", + }, + { + Name: "lib2", + Version: "2.0.0", + Ignore: true, + Ecosystem: "Go", + EffectiveUntil: time.Time{}, + Reason: "abc", + }, + }, + }, + args: args{ + name: "lib1", + version: "2.0.0", + ecosystem: "Go", + }, + wantOk: false, + wantEntry: PackageOverrideEntry{}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gotOk, gotEntry := tt.config.ShouldIgnorePackageVersion(tt.args.name, tt.args.version, tt.args.ecosystem) + if gotOk != tt.wantOk { + t.Errorf("ShouldIgnorePackageVersion() gotOk = %v, wantOk %v", gotOk, tt.wantOk) + } + if !reflect.DeepEqual(gotEntry, tt.wantEntry) { + t.Errorf("ShouldIgnorePackageVersion() gotEntry = %v, wantEntry %v", gotEntry, tt.wantEntry) + } + }) + } +} + +func TestConfig_ShouldOverridePackageVersionLicense(t *testing.T) { + t.Parallel() + + type args struct { + name string + version string + ecosystem string + } + tests := []struct { + name string + config Config + args args + wantOk bool + wantEntry PackageOverrideEntry + }{ + { + name: "Exact version entry exists", + config: Config{ + PackageOverrides: []PackageOverrideEntry{ + { + Name: "lib1", + Version: "1.0.0", + Ecosystem: "Go", + License: License{ + Override: []string{"mit"}, + }, + Reason: "abc", + }, + }, + }, + args: args{ + name: "lib1", + version: "1.0.0", + ecosystem: "Go", + }, + wantOk: true, + wantEntry: PackageOverrideEntry{ + Name: "lib1", + Version: "1.0.0", + Ecosystem: "Go", + License: License{ + Override: []string{"mit"}, + }, + Reason: "abc", + }, + }, + { + name: "Version entry doesn't exist", + config: Config{ + PackageOverrides: []PackageOverrideEntry{ + { + Name: "lib1", + Version: "1.0.0", + Ecosystem: "Go", + License: License{ + Override: []string{"mit"}, + }, + Reason: "abc", + }, + }, + }, + args: args{ + name: "lib1", + version: "1.0.1", + ecosystem: "Go", + }, + wantOk: false, + wantEntry: PackageOverrideEntry{}, + }, + { + name: "Name matches", + config: Config{ + PackageOverrides: []PackageOverrideEntry{ + { + Name: "lib1", + Ecosystem: "Go", + License: License{ + Override: []string{"mit"}, + }, + Reason: "abc", + }, + }, + }, + args: args{ + name: "lib1", + version: "1.0.1", + ecosystem: "Go", + }, + wantOk: true, + wantEntry: PackageOverrideEntry{ + Name: "lib1", + Ecosystem: "Go", + License: License{ + Override: []string{"mit"}, + }, + Reason: "abc", + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gotOk, gotEntry := tt.config.ShouldOverridePackageVersionLicense(tt.args.name, tt.args.version, tt.args.ecosystem) + if gotOk != tt.wantOk { + t.Errorf("ShouldOverridePackageVersionLicense() gotOk = %v, wantOk %v", gotOk, tt.wantOk) + } + if !reflect.DeepEqual(gotEntry, tt.wantEntry) { + t.Errorf("ShouldOverridePackageVersionLicense() gotEntry = %v, wantEntry %v", gotEntry, tt.wantEntry) + } + }) + } +} diff --git a/pkg/osvscanner/__snapshots__/vulnerability_result_internal_test.snap b/pkg/osvscanner/__snapshots__/vulnerability_result_internal_test.snap new file mode 100755 index 0000000000..9d7d7001d5 --- /dev/null +++ b/pkg/osvscanner/__snapshots__/vulnerability_result_internal_test.snap @@ -0,0 +1,586 @@ + +[Test_assembleResult/group_vulnerabilities - 1] +{ + "results": [ + { + "source": { + "path": "dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-1", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-123", + "aliases": [ + "CVE-123" + ] + }, + { + "modified": "0001-01-01T00:00:00Z", + "id": "CVE-123" + } + ], + "groups": [ + { + "ids": [ + "CVE-123", + "GHSA-123" + ], + "aliases": [ + "CVE-123", + "GHSA-123" + ], + "max_severity": "" + } + ] + } + ] + }, + { + "source": { + "path": "other-dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-3", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-456" + } + ], + "groups": [ + { + "ids": [ + "GHSA-456" + ], + "aliases": [ + "GHSA-456" + ], + "max_severity": "" + } + ] + } + ] + } + ], + "experimental_config": { + "licenses": { + "summary": false, + "allowlist": null + } + } +} +--- + +[Test_assembleResult/group_vulnerabilities_with_all_packages_included - 1] +{ + "results": [ + { + "source": { + "path": "dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-1", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-123", + "aliases": [ + "CVE-123" + ] + }, + { + "modified": "0001-01-01T00:00:00Z", + "id": "CVE-123" + } + ], + "groups": [ + { + "ids": [ + "CVE-123", + "GHSA-123" + ], + "aliases": [ + "CVE-123", + "GHSA-123" + ], + "max_severity": "" + } + ] + }, + { + "package": { + "name": "pkg-2", + "version": "1.0.0", + "ecosystem": "npm" + } + } + ] + }, + { + "source": { + "path": "other-dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-3", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-456" + } + ], + "groups": [ + { + "ids": [ + "GHSA-456" + ], + "aliases": [ + "GHSA-456" + ], + "max_severity": "" + } + ] + } + ] + } + ], + "experimental_config": { + "licenses": { + "summary": false, + "allowlist": null + } + } +} +--- + +[Test_assembleResult/group_vulnerabilities_with_license_allowlist - 1] +{ + "results": [ + { + "source": { + "path": "dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-1", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-123", + "aliases": [ + "CVE-123" + ] + }, + { + "modified": "0001-01-01T00:00:00Z", + "id": "CVE-123" + } + ], + "groups": [ + { + "ids": [ + "CVE-123", + "GHSA-123" + ], + "aliases": [ + "CVE-123", + "GHSA-123" + ], + "max_severity": "" + } + ], + "licenses": [ + "MIT", + "0BSD" + ] + } + ] + }, + { + "source": { + "path": "other-dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-3", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-456" + } + ], + "groups": [ + { + "ids": [ + "GHSA-456" + ], + "aliases": [ + "GHSA-456" + ], + "max_severity": "" + } + ], + "licenses": [ + "UNKNOWN" + ], + "license_violations": [ + "UNKNOWN" + ] + } + ] + } + ], + "experimental_config": { + "licenses": { + "summary": false, + "allowlist": [ + "MIT", + "0BSD" + ] + } + } +} +--- + +[Test_assembleResult/group_vulnerabilities_with_license_allowlist_and_all_packages - 1] +{ + "results": [ + { + "source": { + "path": "dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-1", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-123", + "aliases": [ + "CVE-123" + ] + }, + { + "modified": "0001-01-01T00:00:00Z", + "id": "CVE-123" + } + ], + "groups": [ + { + "ids": [ + "CVE-123", + "GHSA-123" + ], + "aliases": [ + "CVE-123", + "GHSA-123" + ], + "max_severity": "" + } + ], + "licenses": [ + "MIT", + "0BSD" + ] + }, + { + "package": { + "name": "pkg-2", + "version": "1.0.0", + "ecosystem": "npm" + }, + "licenses": [ + "MIT" + ] + } + ] + }, + { + "source": { + "path": "other-dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-3", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-456" + } + ], + "groups": [ + { + "ids": [ + "GHSA-456" + ], + "aliases": [ + "GHSA-456" + ], + "max_severity": "" + } + ], + "licenses": [ + "UNKNOWN" + ], + "license_violations": [ + "UNKNOWN" + ] + } + ] + } + ], + "experimental_config": { + "licenses": { + "summary": false, + "allowlist": [ + "MIT", + "0BSD" + ] + } + } +} +--- + +[Test_assembleResult/group_vulnerabilities_with_license_allowlist_and_license_override - 1] +{ + "results": [ + { + "source": { + "path": "dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-1", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-123", + "aliases": [ + "CVE-123" + ] + }, + { + "modified": "0001-01-01T00:00:00Z", + "id": "CVE-123" + } + ], + "groups": [ + { + "ids": [ + "CVE-123", + "GHSA-123" + ], + "aliases": [ + "CVE-123", + "GHSA-123" + ], + "max_severity": "" + } + ], + "licenses": [ + "MIT", + "0BSD" + ] + } + ] + }, + { + "source": { + "path": "other-dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-3", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-456" + } + ], + "groups": [ + { + "ids": [ + "GHSA-456" + ], + "aliases": [ + "GHSA-456" + ], + "max_severity": "" + } + ], + "licenses": [ + "MIT" + ] + } + ] + } + ], + "experimental_config": { + "licenses": { + "summary": false, + "allowlist": [ + "MIT", + "0BSD" + ] + } + } +} +--- + +[Test_assembleResult/group_vulnerabilities_with_licenses - 1] +{ + "results": [ + { + "source": { + "path": "dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-1", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-123", + "aliases": [ + "CVE-123" + ] + }, + { + "modified": "0001-01-01T00:00:00Z", + "id": "CVE-123" + } + ], + "groups": [ + { + "ids": [ + "CVE-123", + "GHSA-123" + ], + "aliases": [ + "CVE-123", + "GHSA-123" + ], + "max_severity": "" + } + ], + "licenses": [ + "MIT", + "0BSD" + ] + }, + { + "package": { + "name": "pkg-2", + "version": "1.0.0", + "ecosystem": "npm" + }, + "licenses": [ + "MIT" + ] + } + ] + }, + { + "source": { + "path": "other-dir/package-lock.json", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "pkg-3", + "version": "1.0.0", + "ecosystem": "npm" + }, + "vulnerabilities": [ + { + "modified": "0001-01-01T00:00:00Z", + "id": "GHSA-456" + } + ], + "groups": [ + { + "ids": [ + "GHSA-456" + ], + "aliases": [ + "GHSA-456" + ], + "max_severity": "" + } + ], + "licenses": [ + "UNKNOWN" + ] + } + ] + } + ], + "experimental_config": { + "licenses": { + "summary": true, + "allowlist": [] + } + } +} +--- diff --git a/pkg/osvscanner/osvscanner.go b/pkg/osvscanner/osvscanner.go index 26aefd2812..290a50b9ca 100644 --- a/pkg/osvscanner/osvscanner.go +++ b/pkg/osvscanner/osvscanner.go @@ -639,6 +639,19 @@ func filterResults(r reporter.Reporter, results *models.VulnerabilityResults, co // Filters package-grouped vulnerabilities according to config, preserving ordering. Returns filtered package vulnerabilities. func filterPackageVulns(r reporter.Reporter, pkgVulns models.PackageVulns, configToUse config.Config) models.PackageVulns { + if ignore, ignoreLine := configToUse.ShouldIgnorePackageVersion(pkgVulns.Package.Name, pkgVulns.Package.Version, pkgVulns.Package.Ecosystem); ignore { + pkgString := fmt.Sprintf("%s/%s/%s", pkgVulns.Package.Ecosystem, pkgVulns.Package.Name, pkgVulns.Package.Version) + switch len(pkgVulns.Vulnerabilities) { + case 1: + r.Infof("1 vulnerability for the package %s has been filtered out because: %s\n", pkgString, ignoreLine.Reason) + default: + r.Infof("%d vulnerabilities for the package %s have been filtered out because: %s\n", len(pkgVulns.Vulnerabilities), pkgString, ignoreLine.Reason) + } + pkgVulns.Groups = nil + pkgVulns.Vulnerabilities = nil + + return pkgVulns + } ignoredVulns := map[string]struct{}{} // Iterate over groups first to remove all aliases of ignored vulnerabilities. var newGroups []models.GroupInfo @@ -846,7 +859,7 @@ func DoScan(actions ScannerActions, r reporter.Reporter) (models.VulnerabilityRe return models.VulnerabilityResults{}, err } } - results := buildVulnerabilityResults(r, filteredScannedPackages, vulnsResp, licensesResp, actions) + results := buildVulnerabilityResults(r, filteredScannedPackages, vulnsResp, licensesResp, actions, &configManager) filtered := filterResults(r, &results, &configManager, actions.ShowAllPackages) if filtered > 0 { diff --git a/pkg/osvscanner/vulnerability_result.go b/pkg/osvscanner/vulnerability_result.go index 7657dbc4c3..88cf7d02fe 100644 --- a/pkg/osvscanner/vulnerability_result.go +++ b/pkg/osvscanner/vulnerability_result.go @@ -1,11 +1,13 @@ package osvscanner import ( + "slices" "sort" "strings" "github.com/google/osv-scanner/internal/output" "github.com/google/osv-scanner/internal/sourceanalysis" + "github.com/google/osv-scanner/pkg/config" "github.com/google/osv-scanner/pkg/grouper" "github.com/google/osv-scanner/pkg/models" "github.com/google/osv-scanner/pkg/osv" @@ -22,6 +24,7 @@ func buildVulnerabilityResults( vulnsResp *osv.HydratedBatchedResponse, licensesResp [][]models.License, actions ScannerActions, + configManager *config.ConfigManager, ) models.VulnerabilityResults { results := models.VulnerabilityResults{ Results: []models.PackageSource{}, @@ -62,24 +65,34 @@ func buildVulnerabilityResults( pkg.Groups[i].MaxSeverity = output.MaxSeverity(group, pkg) } } - if len(actions.ScanLicensesAllowlist) > 0 { - pkg.Licenses = licensesResp[i] - allowlist := make(map[string]bool) - for _, license := range actions.ScanLicensesAllowlist { - allowlist[strings.ToLower(license)] = true + if actions.ScanLicensesSummary || len(actions.ScanLicensesAllowlist) > 0 { + configToUse := configManager.Get(r, rawPkg.Source.Path) + if override, entry := configToUse.ShouldOverridePackageVersionLicense(pkg.Package.Name, pkg.Package.Version, pkg.Package.Ecosystem); override { + overrideLicenses := make([]models.License, len(entry.License.Override)) + for j, license := range entry.License.Override { + overrideLicenses[j] = models.License(license) + } + r.Infof("overriding license for package %s/%s/%s with %s\n", pkg.Package.Ecosystem, pkg.Package.Name, pkg.Package.Version, strings.Join(entry.License.Override, ",")) + licensesResp[i] = overrideLicenses } - for _, license := range pkg.Licenses { - if !allowlist[strings.ToLower(string(license))] { - pkg.LicenseViolations = append(pkg.LicenseViolations, license) + if len(actions.ScanLicensesAllowlist) > 0 { + pkg.Licenses = licensesResp[i] + for _, license := range pkg.Licenses { + lowerLicense := strings.ToLower(string(license)) + if !slices.ContainsFunc(actions.ScanLicensesAllowlist, func(l string) bool { + return strings.ToLower(l) == lowerLicense + }) { + pkg.LicenseViolations = append(pkg.LicenseViolations, license) + } + } + if len(pkg.LicenseViolations) > 0 { + includePackage = true } } - if len(pkg.LicenseViolations) > 0 { - includePackage = true + if actions.ScanLicensesSummary { + pkg.Licenses = licensesResp[i] } } - if actions.ScanLicensesSummary { - pkg.Licenses = licensesResp[i] - } if includePackage { groupedBySource[rawPkg.Source] = append(groupedBySource[rawPkg.Source], pkg) } diff --git a/pkg/osvscanner/vulnerability_result_internal_test.go b/pkg/osvscanner/vulnerability_result_internal_test.go index 5781f4ee2e..a26ceed90f 100644 --- a/pkg/osvscanner/vulnerability_result_internal_test.go +++ b/pkg/osvscanner/vulnerability_result_internal_test.go @@ -1,9 +1,10 @@ package osvscanner import ( - "reflect" "testing" + "github.com/google/osv-scanner/internal/testutility" + "github.com/google/osv-scanner/pkg/config" "github.com/google/osv-scanner/pkg/lockfile" "github.com/google/osv-scanner/pkg/models" "github.com/google/osv-scanner/pkg/osv" @@ -18,6 +19,7 @@ func Test_assembleResult(t *testing.T) { vulnsResp *osv.HydratedBatchedResponse licensesResp [][]models.License actions ScannerActions + config *config.ConfigManager } packages := []scannedPackage{ { @@ -71,20 +73,25 @@ func Test_assembleResult(t *testing.T) { {models.License("MIT")}, {models.License("UNKNOWN")}, } + makeLicensesResp := func() [][]models.License { + cpy := make([][]models.License, len(licensesResp)) + copy(cpy, licensesResp) + + return cpy + } callAnalysisStates := make(map[string]bool) tests := []struct { name string args args - want models.VulnerabilityResults }{{ - name: "group vulnerabilities", + name: "group_vulnerabilities", args: args{ r: &reporter.VoidReporter{}, packages: packages, vulnsResp: vulnsResp, - licensesResp: licensesResp, + licensesResp: makeLicensesResp(), actions: ScannerActions{ ExperimentalScannerActions: ExperimentalScannerActions{ ShowAllPackages: false, @@ -92,72 +99,15 @@ func Test_assembleResult(t *testing.T) { }, CallAnalysisStates: callAnalysisStates, }, - }, - want: models.VulnerabilityResults{ - Results: []models.PackageSource{ - { - Source: models.SourceInfo{ - Path: "dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-1", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - { - ID: "GHSA-123", - Aliases: []string{"CVE-123"}, - }, - { - ID: "CVE-123", - }, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"CVE-123", "GHSA-123"}, - Aliases: []string{"CVE-123", "GHSA-123"}, - }, - }, - }, - }, - }, - { - Source: models.SourceInfo{ - Path: "other-dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-3", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - {ID: "GHSA-456"}, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"GHSA-456"}, - Aliases: []string{"GHSA-456"}, - }, - }, - }, - }, - }, - }, + config: &config.ConfigManager{}, }, }, { - name: "group vulnerabilities, with all packages included", + name: "group_vulnerabilities_with_all_packages_included", args: args{ r: &reporter.VoidReporter{}, packages: packages, vulnsResp: vulnsResp, - licensesResp: licensesResp, + licensesResp: makeLicensesResp(), actions: ScannerActions{ ExperimentalScannerActions: ExperimentalScannerActions{ ShowAllPackages: true, @@ -165,78 +115,15 @@ func Test_assembleResult(t *testing.T) { }, CallAnalysisStates: callAnalysisStates, }, - }, - want: models.VulnerabilityResults{ - Results: []models.PackageSource{ - { - Source: models.SourceInfo{ - Path: "dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-1", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - { - ID: "GHSA-123", - Aliases: []string{"CVE-123"}, - }, - { - ID: "CVE-123", - }, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"CVE-123", "GHSA-123"}, - Aliases: []string{"CVE-123", "GHSA-123"}, - }, - }, - }, { - Package: models.PackageInfo{ - Name: "pkg-2", - Ecosystem: "npm", - Version: "1.0.0", - }, - }, - }, - }, - { - Source: models.SourceInfo{ - Path: "other-dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-3", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - {ID: "GHSA-456"}, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"GHSA-456"}, - Aliases: []string{"GHSA-456"}, - }, - }, - }, - }, - }, - }, + config: &config.ConfigManager{}, }, }, { - name: "group vulnerabilities with licenses", + name: "group_vulnerabilities_with_licenses", args: args{ r: &reporter.VoidReporter{}, packages: packages, vulnsResp: vulnsResp, - licensesResp: licensesResp, + licensesResp: makeLicensesResp(), actions: ScannerActions{ ExperimentalScannerActions: ExperimentalScannerActions{ ShowAllPackages: true, @@ -245,87 +132,15 @@ func Test_assembleResult(t *testing.T) { }, CallAnalysisStates: callAnalysisStates, }, - }, - want: models.VulnerabilityResults{ - ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{ - Licenses: models.ExperimentalLicenseConfig{ - Summary: true, - Allowlist: []models.License{}, - }, - }, - Results: []models.PackageSource{ - { - Source: models.SourceInfo{ - Path: "dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-1", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - { - ID: "GHSA-123", - Aliases: []string{"CVE-123"}, - }, - { - ID: "CVE-123", - }, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"CVE-123", "GHSA-123"}, - Aliases: []string{"CVE-123", "GHSA-123"}, - }, - }, - Licenses: makeLicenses([]string{"MIT", "0BSD"}), - }, { - Package: models.PackageInfo{ - Name: "pkg-2", - Ecosystem: "npm", - Version: "1.0.0", - }, - Licenses: makeLicenses([]string{"MIT"}), - }, - }, - }, - { - Source: models.SourceInfo{ - Path: "other-dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-3", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - {ID: "GHSA-456"}, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"GHSA-456"}, - Aliases: []string{"GHSA-456"}, - }, - }, - Licenses: makeLicenses([]string{"UNKNOWN"}), - }, - }, - }, - }, + config: &config.ConfigManager{}, }, }, { - name: "group vulnerabilities with license allowlist", + name: "group_vulnerabilities_with_license_allowlist", args: args{ r: &reporter.VoidReporter{}, packages: packages, vulnsResp: vulnsResp, - licensesResp: licensesResp, + licensesResp: makeLicensesResp(), actions: ScannerActions{ ExperimentalScannerActions: ExperimentalScannerActions{ ShowAllPackages: false, @@ -333,80 +148,44 @@ func Test_assembleResult(t *testing.T) { }, CallAnalysisStates: callAnalysisStates, }, + + config: &config.ConfigManager{}, }, - want: models.VulnerabilityResults{ - ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{ - Licenses: models.ExperimentalLicenseConfig{ - Allowlist: []models.License{models.License("MIT"), models.License("0BSD")}, + }, { + name: "group_vulnerabilities_with_license_allowlist_and_license_override", + args: args{ + r: &reporter.VoidReporter{}, + packages: packages, + vulnsResp: vulnsResp, + licensesResp: makeLicensesResp(), + actions: ScannerActions{ + ExperimentalScannerActions: ExperimentalScannerActions{ + ShowAllPackages: false, + ScanLicensesAllowlist: []string{"MIT", "0BSD"}, }, + CallAnalysisStates: callAnalysisStates, }, - Results: []models.PackageSource{ - { - Source: models.SourceInfo{ - Path: "dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-1", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - { - ID: "GHSA-123", - Aliases: []string{"CVE-123"}, - }, - { - ID: "CVE-123", - }, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"CVE-123", "GHSA-123"}, - Aliases: []string{"CVE-123", "GHSA-123"}, - }, - }, - Licenses: makeLicenses([]string{"MIT", "0BSD"}), - }, - }, - }, - { - Source: models.SourceInfo{ - Path: "other-dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ + config: &config.ConfigManager{ + OverrideConfig: &config.Config{ + PackageOverrides: []config.PackageOverrideEntry{ { - Package: models.PackageInfo{ - Name: "pkg-3", - Ecosystem: "npm", - Version: "1.0.0", + Name: "pkg-3", + Ecosystem: "npm", + License: config.License{ + Override: []string{"MIT"}, }, - Vulnerabilities: []models.Vulnerability{ - {ID: "GHSA-456"}, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"GHSA-456"}, - Aliases: []string{"GHSA-456"}, - }, - }, - Licenses: makeLicenses([]string{"UNKNOWN"}), - LicenseViolations: makeLicenses([]string{"UNKNOWN"}), }, }, }, }, }, }, { - name: "group vulnerabilities, with license allowlist and all packages", + name: "group_vulnerabilities_with_license_allowlist_and_all_packages", args: args{ r: &reporter.VoidReporter{}, packages: packages, vulnsResp: vulnsResp, - licensesResp: licensesResp, + licensesResp: makeLicensesResp(), actions: ScannerActions{ ExperimentalScannerActions: ExperimentalScannerActions{ ShowAllPackages: true, @@ -414,97 +193,15 @@ func Test_assembleResult(t *testing.T) { }, CallAnalysisStates: callAnalysisStates, }, - }, - want: models.VulnerabilityResults{ - ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{ - Licenses: models.ExperimentalLicenseConfig{ - Allowlist: []models.License{models.License("MIT"), models.License("0BSD")}, - }, - }, - Results: []models.PackageSource{ - { - Source: models.SourceInfo{ - Path: "dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-1", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - { - ID: "GHSA-123", - Aliases: []string{"CVE-123"}, - }, - { - ID: "CVE-123", - }, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"CVE-123", "GHSA-123"}, - Aliases: []string{"CVE-123", "GHSA-123"}, - }, - }, - Licenses: makeLicenses([]string{"MIT", "0BSD"}), - }, { - Package: models.PackageInfo{ - Name: "pkg-2", - Ecosystem: "npm", - Version: "1.0.0", - }, - Licenses: makeLicenses([]string{"MIT"}), - }, - }, - }, - { - Source: models.SourceInfo{ - Path: "other-dir/package-lock.json", - Type: "lockfile", - }, - Packages: []models.PackageVulns{ - { - Package: models.PackageInfo{ - Name: "pkg-3", - Ecosystem: "npm", - Version: "1.0.0", - }, - Vulnerabilities: []models.Vulnerability{ - {ID: "GHSA-456"}, - }, - Groups: []models.GroupInfo{ - { - IDs: []string{"GHSA-456"}, - Aliases: []string{"GHSA-456"}, - }, - }, - Licenses: makeLicenses([]string{"UNKNOWN"}), - LicenseViolations: makeLicenses([]string{"UNKNOWN"}), - }, - }, - }, - }, + config: &config.ConfigManager{}, }, }} for _, tt := range tests { tt := tt // Reinitialize for t.Parallel() t.Run(tt.name, func(t *testing.T) { t.Parallel() - if got := buildVulnerabilityResults(tt.args.r, tt.args.packages, tt.args.vulnsResp, tt.args.licensesResp, tt.args.actions); !reflect.DeepEqual(got, tt.want) { - t.Errorf("buildVulnerabilityResults() = %v,\nwant %v", got, tt.want) - } + got := buildVulnerabilityResults(tt.args.r, tt.args.packages, tt.args.vulnsResp, tt.args.licensesResp, tt.args.actions, tt.args.config) + testutility.NewSnapshot().MatchJSON(t, got) }) } } - -func makeLicenses(strLicenses []string) []models.License { - licenses := make([]models.License, len(strLicenses)) - for i, l := range strLicenses { - licenses[i] = models.License(l) - } - - return licenses -}