Skip to content

Commit

Permalink
feat: support composite-based package overrides (#1214)
Browse files Browse the repository at this point in the history
This rewrites the package overrides logic to be composition based,
granting a lot more flexibility:

```
# ignore everything
[[PackageOverrides]]
ignore = true

# ignore everything in this group
[[PackageOverrides]]
group = "dev"
ignore = true

# ignore everything in this ecosystem
[[PackageOverrides]]
ecosystem = "go"
ignore = true

# ignore all packages named "axios" regardless of ecosystem or group
[[PackageOverrides]]
name = "axios"
ignore = true

# ignore all packages named "axios" in the npm ecosystem that are in the dev group
[[PackageOverrides]]
name = "axios"
ecosystem = "npm"
group = "dev"
ignore = true

# ... and so on
```

While some of these might seem a bit extreme, ultimately I think this is
probably the way to go as the logic itself is very straightforward and
it gives a lot more power to the people.

Since `config` is a public package, I've had to deprecated the related
existing public methods and there's a bit of naming & structural yuck
but I figure that's not a big deal since v2 is right around the corner
and again the logic itself is very straightforward.

Resolves #1211
Resolves #1155
  • Loading branch information
G-Rath authored Sep 9, 2024
1 parent 1c086df commit 0cd2051
Show file tree
Hide file tree
Showing 8 changed files with 719 additions and 22 deletions.
47 changes: 47 additions & 0 deletions cmd/osv-scanner/__snapshots__/main_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,53 @@ Attempted to scan lockfile but failed: <rootdir>/fixtures/locks-many-with-invali

---

[TestRun/config_file_can_be_broad - 1]
Scanning dir ./fixtures/locks-many
Scanned <rootdir>/fixtures/locks-many/Gemfile.lock file and found 1 package
Scanned <rootdir>/fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 14 packages
Scanned <rootdir>/fixtures/locks-many/composer.lock file and found 1 package
Scanned <rootdir>/fixtures/locks-many/package-lock.json file and found 1 package
Scanned <rootdir>/fixtures/locks-many/yarn.lock file and found 1 package
Scanning dir ./fixtures/locks-insecure
Scanned <rootdir>/fixtures/locks-insecure/composer.lock file and found 1 package
Package npm/ansi-html/0.0.1 has been filtered out because:
Package npm/balanced-match/1.0.2 has been filtered out because:
Filtered 2 ignored package/s from the scan.
overriding license for package Alpine/alpine-baselayout/3.4.0-r0 with MIT
overriding license for package Alpine/alpine-baselayout-data/3.4.0-r0 with MIT
overriding license for package Alpine/alpine-keys/2.4-r1 with MIT
overriding license for package Alpine/apk-tools/2.12.10-r1 with MIT
overriding license for package Alpine/busybox-binsh/1.36.1-r27 with MIT
overriding license for package Alpine/ca-certificates-bundle/20220614-r4 with MIT
overriding license for package Alpine/libc-utils/0.7.2-r3 with MIT
overriding license for package Alpine/libcrypto3/3.0.8-r0 with MIT
overriding license for package Alpine/libssl3/3.0.8-r0 with MIT
overriding license for package Alpine/musl/1.2.3-r4 with MIT
overriding license for package Alpine/musl-utils/1.2.3-r4 with MIT
overriding license for package Alpine/scanelf/1.3.5-r1 with MIT
overriding license for package Alpine/ssl_client/1.36.1-r27 with MIT
overriding license for package Alpine/zlib/1.2.13-r0 with MIT
overriding license for package Packagist/sentry/sdk/2.0.4 with 0BSD
overriding license for package Packagist/league/flysystem/1.0.8 with 0BSD
+-------------------------------------+------+-----------+------------------+---------+---------------------------------------+
| OSV URL | CVSS | ECOSYSTEM | PACKAGE | VERSION | SOURCE |
+-------------------------------------+------+-----------+------------------+---------+---------------------------------------+
| https://osv.dev/GHSA-9f46-5r25-5wfm | 9.8 | Packagist | league/flysystem | 1.0.8 | fixtures/locks-insecure/composer.lock |
+-------------------------------------+------+-----------+------------------+---------+---------------------------------------+
+-------------------+-----------+------------------+---------+---------------------------------------+
| LICENSE VIOLATION | ECOSYSTEM | PACKAGE | VERSION | SOURCE |
+-------------------+-----------+------------------+---------+---------------------------------------+
| 0BSD | Packagist | league/flysystem | 1.0.8 | fixtures/locks-insecure/composer.lock |
| UNKNOWN | RubyGems | ast | 2.4.2 | fixtures/locks-many/Gemfile.lock |
| 0BSD | Packagist | sentry/sdk | 2.0.4 | fixtures/locks-many/composer.lock |
+-------------------+-----------+------------------+---------+---------------------------------------+

---

[TestRun/config_file_can_be_broad - 2]

---

[TestRun/cyclonedx_1.4_output - 1]
{
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
Expand Down
11 changes: 11 additions & 0 deletions cmd/osv-scanner/fixtures/osv-scanner-composite-config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[PackageOverrides]]
ecosystem = "npm"
ignore = true

[[PackageOverrides]]
ecosystem = "Packagist"
license.override = ["0BSD"]

[[PackageOverrides]]
ecosystem = "Alpine"
license.override = ["MIT"]
6 changes: 6 additions & 0 deletions cmd/osv-scanner/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,12 @@ func TestRun(t *testing.T) {
args: []string{"", "--config=./fixtures/go-project/go-version-config.toml", "./fixtures/go-project"},
exit: 0,
},
// broad config file that overrides a whole ecosystem
{
name: "config file can be broad",
args: []string{"", "--config=./fixtures/osv-scanner-composite-config.toml", "--experimental-licenses", "MIT", "./fixtures/locks-many", "./fixtures/locks-insecure"},
exit: 1,
},
}
for _, tt := range tests {
tt := tt
Expand Down
52 changes: 42 additions & 10 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,54 @@ reason = "No external http servers are written in Go lang."

Ignoring a vulnerability will also ignore vulnerabilities that are considered aliases of that vulnerability.

## Override specific package
## Override packages

To ignore a specific a package, or manually set its license, enter the package name and ecosystem under the `PackageOverrides` key.
You can specify overrides for particular packages to have them either ignored entirely or to set their license using the `PackageOverrides` key:

```toml
[[PackageOverrides]]
# The package name, version, and ecosystem to match against
# One or more fields to match each package against:
name = "lib"
# If version is not set or empty, it will match every version
version = "1.0.0"
ecosystem = "Go"
# Ignore this package entirely, including license scanning
group = "dev"

# Actions to take for matching packages:
ignore = true # Ignore this package entirely, including license scanning
license.override = ["MIT", "0BSD"] # Override the license of the package, if it is not ignored

effectiveUntil = 2022-11-09 # Optional exception expiry date, after which the override will no longer apply
reason = "abc" # Optional reason for the override, to explain why it was added
```

Overrides are applied if all the configured fields match, enabling you to create very broad or very specific overrides based on your needs:

```toml
# ignore everything in the current directory
[[PackageOverrides]]
ignore = true

# ignore a particular group
[[PackageOverrides]]
group = "dev"
ignore = true
# Override the license of the package
# This is not used if ignore = true
license.override = ["MIT", "0BSD"]
# effectiveUntil = 2022-11-09 # Optional exception expiry date
reason = "abc"

# ignore a particular ecosystem
[[PackageOverrides]]
ecosystem = "go"
ignore = true

# ignore packages named "axios" regardless of ecosystem or group
[[PackageOverrides]]
name = "axios"
ignore = true

# ignore packages named "axios" in the npm ecosystem that are in the dev group
[[PackageOverrides]]
name = "axios"
ecosystem = "npm"
group = "dev"
ignore = true

# ... and so on
```
59 changes: 49 additions & 10 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/BurntSushi/toml"
"github.com/google/osv-scanner/pkg/models"
"github.com/google/osv-scanner/pkg/reporter"
)

Expand Down Expand Up @@ -40,12 +41,30 @@ type PackageOverrideEntry struct {
// If the version is empty, the entry applies to all versions.
Version string `toml:"version"`
Ecosystem string `toml:"ecosystem"`
Group string `toml:"group"`
Ignore bool `toml:"ignore"`
License License `toml:"license"`
EffectiveUntil time.Time `toml:"effectiveUntil"`
Reason string `toml:"reason"`
}

func (e PackageOverrideEntry) matches(pkg models.PackageVulns) bool {
if e.Name != "" && e.Name != pkg.Package.Name {
return false
}
if e.Version != "" && e.Version != pkg.Package.Version {
return false
}
if e.Ecosystem != "" && e.Ecosystem != pkg.Package.Ecosystem {
return false
}
if e.Group != "" && !slices.Contains(pkg.DepGroups, e.Group) {
return false
}

return true
}

type License struct {
Override []string `toml:"override"`
}
Expand All @@ -60,13 +79,9 @@ func (c *Config) ShouldIgnore(vulnID string) (bool, IgnoreEntry) {
return shouldIgnoreTimestamp(ignoredLine.IgnoreUntil), ignoredLine
}

func (c *Config) filterPackageVersionEntries(name string, version string, ecosystem string, condition func(PackageOverrideEntry) bool) (bool, PackageOverrideEntry) {
func (c *Config) filterPackageVersionEntries(pkg models.PackageVulns, 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)
return e.matches(pkg) && condition(e)
})
if index == -1 {
return false, PackageOverrideEntry{}
Expand All @@ -76,18 +91,42 @@ func (c *Config) filterPackageVersionEntries(name string, version string, ecosys
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 {
// ShouldIgnorePackage determines if the given package should be ignored based on override entries in the config
func (c *Config) ShouldIgnorePackage(pkg models.PackageVulns) (bool, PackageOverrideEntry) {
return c.filterPackageVersionEntries(pkg, 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 {
// Deprecated: Use ShouldIgnorePackage instead
func (c *Config) ShouldIgnorePackageVersion(name, version, ecosystem string) (bool, PackageOverrideEntry) {
return c.ShouldIgnorePackage(models.PackageVulns{
Package: models.PackageInfo{
Name: name,
Version: version,
Ecosystem: ecosystem,
},
})
}

// ShouldOverridePackageLicense determines if the given package should have its license changed based on override entries in the config
func (c *Config) ShouldOverridePackageLicense(pkg models.PackageVulns) (bool, PackageOverrideEntry) {
return c.filterPackageVersionEntries(pkg, func(e PackageOverrideEntry) bool {
return len(e.License.Override) > 0
})
}

// Deprecated: Use ShouldOverridePackageLicense instead
func (c *Config) ShouldOverridePackageVersionLicense(name, version, ecosystem string) (bool, PackageOverrideEntry) {
return c.ShouldOverridePackageLicense(models.PackageVulns{
Package: models.PackageInfo{
Name: name,
Version: version,
Ecosystem: ecosystem,
},
})
}

func shouldIgnoreTimestamp(ignoreUntil time.Time) bool {
if ignoreUntil.IsZero() {
// If IgnoreUntil is not set, should ignore.
Expand Down
Loading

0 comments on commit 0cd2051

Please sign in to comment.