Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cmd/osv-scanner/scan/source/__snapshots__/command_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,16 @@ Ignored invalid config file at <rootdir>/testdata/config-invalid/osv-scanner-tes

---

[TestCommand/config_file_with_invalid_regex - 1]

---

[TestCommand/config_file_with_invalid_regex - 2]
Failed to read config file: invalid regex "[invalid" in package override: error parsing regexp: missing closing ]: `[invalid$`
invalid regex "[invalid" in package override: error parsing regexp: missing closing ]: `[invalid$`

---

[TestCommand/config_files_cannot_have_unknown_keys - 1]

---
Expand Down
6 changes: 6 additions & 0 deletions cmd/osv-scanner/scan/source/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,12 @@ func TestCommand(t *testing.T) {
Args: []string{"", "source", "--config=./testdata/osv-scanner-unknown-config.toml", "./testdata/locks-many"},
Exit: 127,
},
// config file with invalid regex in package override
{
Name: "config_file_with_invalid_regex",
Args: []string{"", "source", "--config=./testdata/osv-scanner-invalid-regex-config.toml", "./testdata/locks-many"},
Exit: 127,
},
// config file with multiple ignores with the same id
{
Name: "config_files_should_not_have_multiple_ignores_with_the_same_id",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[[PackageOverrides]]
name = "[invalid"
nameIsRegex = true
ignore = true
reason = "this regex is invalid"
10 changes: 10 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ You can specify overrides for particular packages to have them either ignored en
[[PackageOverrides]]
# One or more fields to match each package against:
name = "lib"
# nameIsRegex = true # Optional: treat name as a regular expression pattern
version = "1.0.0"
ecosystem = "Go"
group = "dev"
Expand Down Expand Up @@ -93,9 +94,18 @@ ecosystem = "npm"
group = "dev"
ignore = true

# ignore packages matching a regex pattern (e.g. all internal packages)
[[PackageOverrides]]
name = "internal-.*"
nameIsRegex = true
ignore = true
reason = "internal packages should not be checked"

# ... and so on
```

When `nameIsRegex` is set to `true`, the `name` field is treated as a regular expression pattern. The pattern is automatically anchored to match the full package name (i.e. `^pattern$`). Standard [Go regular expression syntax](https://pkg.go.dev/regexp/syntax) is supported. An invalid regex pattern will cause a config loading error.

## Go Version Override

Use the `GoVersionOverride` key to override the Go version used for scanning. This is useful when the scanner fails to detect the correct Go version or when you want to force a specific version.
Expand Down
17 changes: 15 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/google/osv-scalibr/extractor"
"github.com/google/osv-scanner/v2/internal/cachedregexp"
"github.com/google/osv-scanner/v2/internal/cmdlogger"
"github.com/google/osv-scanner/v2/internal/imodels"
)
Expand Down Expand Up @@ -39,6 +40,7 @@ type PackageOverrideEntry struct {
Version string `toml:"version"`
Ecosystem string `toml:"ecosystem"`
Group string `toml:"group"`
NameIsRegex bool `toml:"nameIsRegex"`
Ignore bool `toml:"ignore"`
Vulnerability Vulnerability `toml:"vulnerability"`
License License `toml:"license"`
Expand All @@ -47,8 +49,19 @@ type PackageOverrideEntry struct {
}

func (e PackageOverrideEntry) matches(pkg *extractor.Package) bool {
if e.Name != "" && e.Name != imodels.Name(pkg) {
return false
if e.Name != "" {
if e.NameIsRegex {
re, err := cachedregexp.Compile("^" + e.Name + "$")
if err != nil {
// This should not happen as regex is validated at config load time
return false
}
if !re.MatchString(imodels.Name(pkg)) {
return false
}
} else if e.Name != imodels.Name(pkg) {
return false
}
}
if e.Version != "" && e.Version != imodels.Version(pkg) {
return false
Expand Down
212 changes: 212 additions & 0 deletions internal/config/config_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ func Test_tryLoadConfig(t *testing.T) {
},
wantErr: true,
},
{
name: "config_with_invalid_regex_in_package_override",
args: args{
configPath: "./testdata/testdatainner/osv-scanner-invalid-regex.toml",
},
want: Config{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -867,6 +875,210 @@ func TestConfig_ShouldIgnorePackage(t *testing.T) {
}
}

func TestConfig_ShouldIgnorePackage_NameIsRegex(t *testing.T) {
t.Parallel()

tests := []struct {
name string
config Config
args *extractor.Package
wantOk bool
wantEntry PackageOverrideEntry
}{
{
name: "Regex_matches_package_name",
config: Config{
PackageOverrides: []PackageOverrideEntry{
{
Name: "lib.*",
NameIsRegex: true,
Ignore: true,
Reason: "internal packages",
},
},
},
args: &extractor.Package{
Name: "lib1",
Version: "1.0.0",
PURLType: purl.TypeGolang,
},
wantOk: true,
wantEntry: PackageOverrideEntry{
Name: "lib.*",
NameIsRegex: true,
Ignore: true,
Reason: "internal packages",
},
},
{
name: "Regex_does_not_match_package_name",
config: Config{
PackageOverrides: []PackageOverrideEntry{
{
Name: "lib.*",
NameIsRegex: true,
Ignore: true,
Reason: "internal packages",
},
},
},
args: &extractor.Package{
Name: "other-pkg",
Version: "1.0.0",
PURLType: purl.TypeGolang,
},
wantOk: false,
wantEntry: PackageOverrideEntry{},
},
{
name: "Invalid_regex_does_not_match",
config: Config{
PackageOverrides: []PackageOverrideEntry{
{
Name: "[invalid",
NameIsRegex: true,
Ignore: true,
Reason: "bad regex",
},
},
},
args: &extractor.Package{
Name: "anything",
Version: "1.0.0",
PURLType: purl.TypeGolang,
},
wantOk: false,
wantEntry: PackageOverrideEntry{},
},
{
name: "NameIsRegex_false_uses_exact_match",
config: Config{
PackageOverrides: []PackageOverrideEntry{
{
Name: "lib.*",
Ignore: true,
Reason: "exact match",
},
},
},
args: &extractor.Package{
Name: "lib1",
Version: "1.0.0",
PURLType: purl.TypeGolang,
},
wantOk: false,
wantEntry: PackageOverrideEntry{},
},
{
name: "Regex_with_empty_name_matches_all",
config: Config{
PackageOverrides: []PackageOverrideEntry{
{
Name: "",
NameIsRegex: true,
Ignore: true,
Reason: "match all",
},
},
},
args: &extractor.Package{
Name: "any-package",
Version: "1.0.0",
PURLType: purl.TypeGolang,
},
wantOk: true,
wantEntry: PackageOverrideEntry{
Name: "",
NameIsRegex: true,
Ignore: true,
Reason: "match all",
},
},
{
name: "Regex_is_anchored_and_does_not_partial_match",
config: Config{
PackageOverrides: []PackageOverrideEntry{
{
Name: "lib",
NameIsRegex: true,
Ignore: true,
Reason: "anchored",
},
},
},
args: &extractor.Package{
Name: "lib1",
Version: "1.0.0",
PURLType: purl.TypeGolang,
},
wantOk: false,
wantEntry: PackageOverrideEntry{},
},
{
name: "Regex_combined_with_ecosystem_filter",
config: Config{
PackageOverrides: []PackageOverrideEntry{
{
Name: "internal-.*",
NameIsRegex: true,
Ecosystem: "Go",
Ignore: true,
Reason: "internal Go packages",
},
},
},
args: &extractor.Package{
Name: "internal-lib",
Version: "1.0.0",
PURLType: purl.TypeGolang,
},
wantOk: true,
wantEntry: PackageOverrideEntry{
Name: "internal-.*",
NameIsRegex: true,
Ecosystem: "Go",
Ignore: true,
Reason: "internal Go packages",
},
},
{
name: "Regex_matches_but_ecosystem_does_not",
config: Config{
PackageOverrides: []PackageOverrideEntry{
{
Name: "internal-.*",
NameIsRegex: true,
Ecosystem: "Go",
Ignore: true,
Reason: "internal Go packages",
},
},
},
args: &extractor.Package{
Name: "internal-lib",
Version: "1.0.0",
PURLType: "npm",
},
wantOk: false,
wantEntry: PackageOverrideEntry{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

gotOk, gotEntry := tt.config.ShouldIgnorePackage(tt.args)
if gotOk != tt.wantOk {
t.Errorf("ShouldIgnorePackage() gotOk = %v, wantOk %v", gotOk, tt.wantOk)
}
if !reflect.DeepEqual(gotEntry, tt.wantEntry) {
t.Errorf("ShouldIgnorePackage() gotEntry = %v, wantEntry %v", gotEntry, tt.wantEntry)
}
})
}
}

func TestConfig_ShouldIgnorePackageVulnerabilities(t *testing.T) {
t.Parallel()

Expand Down
9 changes: 9 additions & 0 deletions internal/config/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/BurntSushi/toml"
"github.com/google/osv-scanner/v2/internal/cachedregexp"
"github.com/google/osv-scanner/v2/internal/cmdlogger"
)

Expand Down Expand Up @@ -127,6 +128,14 @@ func tryLoadConfig(configPath string) (Config, error) {

config.LoadPath = configPath
config.warnAboutDuplicates()

for _, override := range config.PackageOverrides {
if override.NameIsRegex && override.Name != "" {
if _, err := cachedregexp.Compile("^" + override.Name + "$"); err != nil {
return Config{}, fmt.Errorf("invalid regex %q in package override: %w", override.Name, err)
}
}
}
}

return config, err
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[[PackageOverrides]]
name = "[invalid"
nameIsRegex = true
ignore = true
reason = "this regex is invalid"
Loading