Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support composite-based package overrides #1214

Merged
merged 12 commits into from
Sep 9, 2024
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the use case for this just scanning for licenses? In that case can we specify that in the comment above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure what you mean - I've only changed the logic around when an override should be applied to a particular package, not the supported "actions"; so the behaviour should be whatever ignore did previously...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, my expectation is that this enables #1155

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant why someone would want to ignore everything. This was before I remembered that it only applies to the file in the same directory, so this will be pretty useful. Can you change the comment above this to specify that this is ignoring everything in the same directory?

Copy link
Collaborator

@another-rex another-rex Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we should probably also change ignore to ignoreVulns, and deprecate ignore, as it is ambiguous as to whether we are still doing license scanning (we are) (turns out it does not), we can do that in a separate PR though. I'll make an issue for this.)


# 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
Loading