From fd8348d610f20c6c33da81cd7b0e7d5504ce26be Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 2 Aug 2024 14:41:56 +0400 Subject: [PATCH] feat(vuln): Add `--detection-priority` flag for accuracy tuning (#7288) Signed-off-by: knqyf263 --- docs/docs/coverage/language/dart.md | 16 +- docs/docs/coverage/language/golang.md | 9 +- docs/docs/coverage/language/java.md | 13 +- docs/docs/coverage/language/python.md | 11 +- docs/docs/coverage/os/conda.md | 5 + .../configuration/cli/trivy_filesystem.md | 4 + .../configuration/cli/trivy_image.md | 4 + .../configuration/cli/trivy_kubernetes.md | 4 + .../configuration/cli/trivy_repository.md | 4 + .../configuration/cli/trivy_rootfs.md | 4 + .../configuration/cli/trivy_sbom.md | 4 + .../references/configuration/cli/trivy_vm.md | 4 + docs/docs/scanner/vulnerability.md | 73 +++++++ integration/client_server_test.go | 14 +- integration/standalone_tar_test.go | 33 ++- integration/testdata/fixtures/db/python.yaml | 8 + .../testdata/fixtures/db/vulnerability.yaml | 21 +- .../testdata/ubi-7-comprehensive.json.golden | 192 ++++++++++++++++++ pkg/cache/key.go | 24 ++- pkg/cache/key_test.go | 51 +++-- pkg/commands/artifact/run.go | 6 + pkg/dependency/parser/dart/pub/parse.go | 24 ++- pkg/dependency/parser/dart/pub/parse_test.go | 42 +++- pkg/fanal/analyzer/analyzer.go | 34 ++-- .../analyzer/language/dart/pub/pubspec.go | 4 +- pkg/fanal/artifact/artifact.go | 2 + pkg/fanal/types/detection.go | 10 + pkg/flag/scan_flags.go | 91 +++++---- pkg/types/scan.go | 91 +++++++++ pkg/types/target.go | 94 --------- 30 files changed, 675 insertions(+), 221 deletions(-) create mode 100644 integration/testdata/ubi-7-comprehensive.json.golden create mode 100644 pkg/fanal/types/detection.go delete mode 100644 pkg/types/target.go diff --git a/docs/docs/coverage/language/dart.md b/docs/docs/coverage/language/dart.md index a64a19eb83c2..5e86b6c399f4 100644 --- a/docs/docs/coverage/language/dart.md +++ b/docs/docs/coverage/language/dart.md @@ -11,9 +11,9 @@ The following scanners are supported. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | ✓ | ## Dart In order to detect dependencies, Trivy searches for `pubspec.lock`. @@ -22,11 +22,13 @@ Trivy marks indirect dependencies, but `pubspec.lock` file doesn't have options So Trivy includes all dependencies in report. ### SDK dependencies -Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). It is not possible to accurately determine the versions of these dependencies. +Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). +It is not possible to accurately determine the versions of these dependencies. +Trivy just treats them as `0.0.0`. -Therefore, we use the first version of the constraint for the SDK. +If [--detection-priority comprehensive][detection-priority] is passed, Trivy uses the minimum version of the constraint for the SDK. +For example, in the following case, the version of `flutter` would be `3.3.0`: -For example in this case the version of `flutter` should be `3.3.0`: ```yaml flutter: dependency: "direct main" @@ -40,6 +42,7 @@ sdks: ### Dependency tree To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only). + !!! note Make sure the cache directory contains all the dependencies installed in your application. To download missing dependencies, use `dart pub get` command. @@ -47,3 +50,4 @@ To build `dependency tree` Trivy parses [cache directory][cache-directory]. Curr [dart-repository]: https://pub.dev/ [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [cache-directory]: https://dart.dev/tools/pub/glossary#system-cache +[detection-priority]: ../../scanner/vulnerability.md#detection-priority \ No newline at end of file diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index 6b3646329318..cf3e160a608b 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -16,10 +16,10 @@ The following scanners are supported. The table below provides an outline of the features Trivy offers. -| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | -|----------|:-----------:|:-----------------|:------------------------------------:|:------:| -| Modules | ✅ | Include | ✅[^2] | - | -| Binaries | ✅ | Exclude | - | ✅[^4] | +| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | [Detection Priority][detection-priority] | +|----------|:-----------:|:-----------------|:------------------------------------:|:------:|:----------------------------------------:| +| Modules | ✅ | Include | ✅[^2] | - | - | +| Binaries | ✅ | Exclude | - | ✅[^4] | Not needed | !!! note Trivy scans only dependencies of the Go project. @@ -95,3 +95,4 @@ empty if it cannot do so[^5]. For the second case, the version of such packages [^5]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index bb90366c1772..67cd8c135b9d 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -12,12 +12,12 @@ Each artifact supports the following scanners: The following table provides an outline of the features Trivy offers. -| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:| -| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | -| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | -| *gradle.lockfile | - | Exclude | ✓ | ✓ | -| *.sbt.lock | - | Exclude | - | ✓ | +| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed | +| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | - | +| *gradle.lockfile | - | Exclude | ✓ | ✓ | Not needed | +| *.sbt.lock | - | Exclude | - | ✓ | Not needed | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. @@ -119,3 +119,4 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend [maven-central]: https://repo.maven.apache.org/maven2/ [maven-pom-repos]: https://maven.apache.org/settings.html#repositories [sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/language/python.md b/docs/docs/coverage/language/python.md index c4f6b6d83e86..15cadd6f96c3 100644 --- a/docs/docs/coverage/language/python.md +++ b/docs/docs/coverage/language/python.md @@ -21,11 +21,11 @@ The following scanners are supported for Python packages. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| pip | requirements.txt | - | Include | - | ✓ | -| Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | -| Poetry | poetry.lock | ✓ | Exclude | ✓ | - | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| pip | requirements.txt | - | Include | - | ✓ | - | +| Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | Not needed | +| Poetry | poetry.lock | ✓ | Exclude | ✓ | - | Not needed | | Packaging | Dependency graph | @@ -130,3 +130,4 @@ Trivy looks for `.dist-info/META-DATA` to identify Python packages. [^1]: Trivy checks `python`, `python3`, `python2` and `python.exe` file names. [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/os/conda.md b/docs/docs/coverage/os/conda.md index 10c1e93c1b8c..773bd8097b6d 100644 --- a/docs/docs/coverage/os/conda.md +++ b/docs/docs/coverage/os/conda.md @@ -8,6 +8,9 @@ Trivy supports the following scanners for Conda packages. | Vulnerability | - | | License | ✓ | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-----------------|-----------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| Conda | environment.yml | - | Include | - | ✓ | - | ## `.json` @@ -41,3 +44,5 @@ To correctly define licenses, make sure your `environment.yml`[^1] contains `pre [environment.yml]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#sharing-an-environment [env-version-range]: https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#examples-of-package-specs [prefix]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#specifying-a-location-for-an-environment +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 99f1df23c069..2202bc27f518 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -30,6 +30,10 @@ trivy filesystem [flags] PATH --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index e7f2e3c70ea1..a1de0595a7bc 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -44,6 +44,10 @@ trivy image [flags] IMAGE_NAME --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --docker-host string unix domain socket path to use for docker scanning --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 6c98a35d9020..4516509d5834 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -39,6 +39,10 @@ trivy kubernetes [flags] [CONTEXT] --config-data strings specify paths from which data for the Rego checks will be recursively loaded --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --disable-node-collector When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node. --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index ba9aa4983d22..5d4bc5ce4161 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -30,6 +30,10 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 39ccef2dd07a..60fdf4e623fa 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -32,6 +32,10 @@ trivy rootfs [flags] ROOTDIR --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index d6d1bf07e1c6..1b0df922c7fc 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -25,6 +25,10 @@ trivy sbom [flags] SBOM_PATH --compliance string compliance report to generate --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --exit-code int specify exit code when any security issues are found diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 110cc0a3ecb7..4bae981c7577 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -28,6 +28,10 @@ trivy vm [flags] VM_IMAGE --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index 9df2b43d8b80..00a9594ead62 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -198,6 +198,54 @@ The default is `ghcr.io/aquasecurity/trivy-java-db`. If authentication is required, you need to run `docker login YOUR_REGISTRY`. Currently, specifying a username and password is not supported. +## Detection Behavior +Trivy prioritizes precision in vulnerability detection, aiming to minimize false positives while potentially accepting some false negatives. +This approach is particularly relevant in two key areas: + +- Handling Software Installed via OS Packages +- Handling Packages with Unspecified Versions + +### Handling Software Installed via OS Packages +For files installed by OS package managers, such as `apt`, Trivy exclusively uses advisories from the OS vendor. +This means that even if a JAR file is present in a container image, if it was installed via an OS package manager (e.g., `apt`), Trivy will not analyze the JAR file itself and use upstream security advisories. + +For example, consider the Python `requests` package in Red Hat Universal Base Image 8: + +```bash +[root@987ee49dc93d /]# head -n 3 /usr/lib/python3.6/site-packages/requests-2.20.0-py3.6.egg-info/PKG-INFO +Metadata-Version: 2.1 +Name: requests +Version: 2.20.0 +``` + +Version 2.20.0 is installed, and this package is installed by `dnf`. + +```bash +[root@987ee49dc93d /]# rpm -ql python3-requests | grep PKG-INFO +/usr/lib/python3.6/site-packages/requests-2.20.0-py3.6.egg-info/PKG-INFO +``` + +At first glance, this might seem vulnerable to [CVE-2023-32681], which affects versions of requests prior to v2.31.0. +However, Red Hat backported the fix to v2.20.0-3 in [RHSA-2023:4520], and the package is not vulnerable. + +- Upstream (PyPI [requests]): Fixed in v2.31.0 +- Red Hat (`python-requests`): Backported fix applied in v2.20.0-3 (RHSA-2023:4520) + +If Trivy were to detect CVE-2023-32681 in this case, it would be a false positive. +This illustrates why using the correct security advisory is crucial to avoid false detections. +To minimize false positives, Trivy trusts the OS vendor's advisory for software installed via OS package managers and does not use upstream advisories for these packages. + +However, this approach may lead to false negatives if the OS vendor's advisories are delayed or missing. +In such cases, using [--detection-priority comprehensive](#detection-priority) allows Trivy to consider upstream advisories (e.g., [GitHub Advisory Database][ghsa]), potentially increasing false positives but reducing false negatives. + +### Handling Packages with Unspecified Versions +When a package version cannot be uniquely determined (e.g., `package-a: ">=3.0"`), Trivy typically skips vulnerability detection for that package to avoid false positives. +If a lock file is present with fixed versions, Trivy will use those for detection. + +To detect potential vulnerabilities even with unspecified versions, use [--detection-priority comprehensive](#detection-priority). +This option makes Trivy use the minimum version in the specified range for vulnerability detection. +While this may increase false positives if the actual version used is not the minimum, it helps reduce false negatives. + ## Configuration This section describes vulnerability-specific configuration. Other common options are documented [here](../configuration/index.md). @@ -307,6 +355,25 @@ By default, all relationships are included in the scan. !!! warning As it may not provide a complete package list, `--pkg-relationships` cannot be used with `--dependency-tree`, `--vex` or SBOM generation. +### Detection Priority + +Trivy provides a `--detection-priority` flag to control the balance between false positives and false negatives in vulnerability detection. +This concept is similar to the relationship between [precision and recall][precision-recall] in machine learning evaluation. + +```bash +$ trivy image --detection-priority {precise|comprehensive} alpine:3.15 +``` + +- `precise`: This mode prioritizes reducing false positives. It results in less noisy vulnerability reports but may miss some potential vulnerabilities. +- `comprehensive`: This mode aims to detect more vulnerabilities, potentially including some that might be false positives. + It provides broader coverage but may increase the noise in the results. + +The default value is `precise`. Also refer to the [detection behavior](#detection-behavior) section for more information. + +Regardless of the chosen mode, user review of detected vulnerabilities is crucial: + +- `precise`: Review thoroughly, considering potential missed vulnerabilities. +- `comprehensive`: Carefully investigate each reported vulnerability due to increased false positive possibility. [^1]: https://github.com/GoogleContainerTools/distroless @@ -353,3 +420,9 @@ By default, all relationships are included in the scan. [nvd]: https://nvd.nist.gov/vuln [k8s-cve]: https://kubernetes.io/docs/reference/issues-security/official-cve-feed/ + +[CVE-2023-32681]: https://nvd.nist.gov/vuln/detail/CVE-2023-32681 +[RHSA-2023:4520]: https://access.redhat.com/errata/RHSA-2023:4520 +[ghsa]: https://github.com/advisories +[requests]: https://pypi.org/project/requests/ +[precision-recall]: https://developers.google.com/machine-learning/crash-course/classification/precision-and-recall \ No newline at end of file diff --git a/integration/client_server_test.go b/integration/client_server_test.go index c6dd7fb74dc5..e0edce7aa6a8 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -287,7 +287,7 @@ func TestClientServer(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) if tt.args.secretConfig != "" { osArgs = append(osArgs, "--secret-config", tt.args.secretConfig) @@ -407,7 +407,7 @@ func TestClientServerWithFormat(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Setenv("AWS_REGION", "test-region") t.Setenv("AWS_ACCOUNT_ID", "123456789012") - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{ override: overrideUID, @@ -435,7 +435,7 @@ func TestClientServerWithCycloneDX(t *testing.T) { addr, cacheDir := setup(t, setupOptions{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", types.FormatCycloneDX, runOptions{ fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", }) @@ -488,7 +488,7 @@ func TestClientServerWithToken(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ override: overrideUID, wantErr: tt.wantErr, @@ -515,7 +515,7 @@ func TestClientServerWithRedis(t *testing.T) { golden := "testdata/alpine-39.json.golden" t.Run("alpine 3.9", func(t *testing.T) { - osArgs := setupClient(t, testArgs, addr, cacheDir, golden) + osArgs := setupClient(t, testArgs, addr, cacheDir) // Run Trivy client runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{ @@ -527,7 +527,7 @@ func TestClientServerWithRedis(t *testing.T) { require.NoError(t, redisC.Terminate(ctx)) t.Run("sad path", func(t *testing.T) { - osArgs := setupClient(t, testArgs, addr, cacheDir, golden) + osArgs := setupClient(t, testArgs, addr, cacheDir) // Run Trivy client runTest(t, osArgs, "", "", types.FormatJSON, runOptions{ @@ -592,7 +592,7 @@ func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []stri return osArgs } -func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden string) []string { +func setupClient(t *testing.T, c csArgs, addr string, cacheDir string) []string { if c.Command == "" { c.Command = "image" } diff --git a/integration/standalone_tar_test.go b/integration/standalone_tar_test.go index 2cb372b86b0a..dce852cf7f3c 100644 --- a/integration/standalone_tar_test.go +++ b/integration/standalone_tar_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" "github.com/stretchr/testify/require" @@ -15,13 +16,14 @@ import ( func TestTar(t *testing.T) { type args struct { - IgnoreUnfixed bool - Severity []string - IgnoreIDs []string - Format types.Format - Input string - SkipDirs []string - SkipFiles []string + IgnoreUnfixed bool + Severity []string + IgnoreIDs []string + Format types.Format + Input string + SkipDirs []string + SkipFiles []string + DetectionPriority ftypes.DetectionPriority } tests := []struct { name string @@ -240,7 +242,7 @@ func TestTar(t *testing.T) { golden: "testdata/centos-7.json.golden", }, { - name: "centos 7with --ignore-unfixed option", + name: "centos 7 with --ignore-unfixed option", args: args{ IgnoreUnfixed: true, Format: types.FormatJSON, @@ -274,6 +276,15 @@ func TestTar(t *testing.T) { }, golden: "testdata/ubi-7.json.golden", }, + { + name: "ubi 7 with comprehensive priority", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/ubi-7.tar.gz", + DetectionPriority: ftypes.PriorityComprehensive, + }, + golden: "testdata/ubi-7-comprehensive.json.golden", + }, { name: "almalinux 8", args: args{ @@ -380,7 +391,7 @@ func TestTar(t *testing.T) { "-q", "--format", string(tt.args.Format), - "--skip-update", + "--skip-db-update", } if tt.args.IgnoreUnfixed { @@ -411,6 +422,10 @@ func TestTar(t *testing.T) { } } + if tt.args.DetectionPriority != "" { + osArgs = append(osArgs, "--detection-priority", string(tt.args.DetectionPriority)) + } + // Run Trivy runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{}) }) diff --git a/integration/testdata/fixtures/db/python.yaml b/integration/testdata/fixtures/db/python.yaml index 2d484feff17c..728f11b67e6d 100644 --- a/integration/testdata/fixtures/db/python.yaml +++ b/integration/testdata/fixtures/db/python.yaml @@ -14,3 +14,11 @@ - 0.11.6 VulnerableVersions: - < 0.11.6 + - bucket: setuptools + pairs: + - key: CVE-2022-40897 + value: + PatchedVersions: + - 65.5.1 + VulnerableVersions: + - < 65.5.1 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/vulnerability.yaml b/integration/testdata/fixtures/db/vulnerability.yaml index 9b16712d0dbc..6fcdcece75bd 100644 --- a/integration/testdata/fixtures/db/vulnerability.yaml +++ b/integration/testdata/fixtures/db/vulnerability.yaml @@ -1399,4 +1399,23 @@ - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155" - "https://nvd.nist.gov/vuln/detail/CVE-2020-14155" PublishedDate: "2020-06-15T17:15:00Z" - LastModifiedDate: "2022-04-28T15:06:00Z" \ No newline at end of file + LastModifiedDate: "2022-04-28T15:06:00Z" + - key: CVE-2022-40897 + value: + Title: "pypa-setuptools: Regular Expression Denial of Service (ReDoS) in package_index.py" + Description: "Python Packaging Authority (PyPA) setuptools before 65.5.1 allows remote attackers to cause a denial of service via HTML in a crafted package or custom PackageIndex page. There is a Regular Expression Denial of Service (ReDoS) in package_index.py." + Severity: MEDIUM + CweIDs: + - CWE-1333 + VendorSeverity: + ghsa: 3 + nvd: 2 + CVSS: + nvd: + V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H" + V3Score: 5.9 + References: + - "https://access.redhat.com/errata/RHSA-2023:0952" + - "https://access.redhat.com/security/cve/CVE-2022-40897" + PublishedDate: "2022-12-23T00:15:13.987Z" + LastModifiedDate: "2024-06-21T19:15:23.877Z" diff --git a/integration/testdata/ubi-7-comprehensive.json.golden b/integration/testdata/ubi-7-comprehensive.json.golden new file mode 100644 index 000000000000..6df4b8241456 --- /dev/null +++ b/integration/testdata/ubi-7-comprehensive.json.golden @@ -0,0 +1,192 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/ubi-7.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "redhat", + "Name": "7.7" + }, + "ImageID": "sha256:6fecccc91c83e11ae4fede6793e9410841221d4779520c2b9e9fb7f7b3830264", + "DiffIDs": [ + "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac", + "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + ], + "ImageConfig": { + "architecture": "amd64", + "created": "2019-09-02T12:56:43.939095Z", + "docker_version": "1.13.1", + "history": [ + { + "created": "2019-09-02T12:56:36.440695936Z", + "comment": "Imported from -" + }, + { + "created": "2019-09-02T12:56:43.939095Z" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac", + "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "container=oci" + ], + "Hostname": "0da2e3774382", + "Image": "2e9103a7b91a7ffe333e9162ce98ea078263747527571655e93bd4d35ee278f0", + "Labels": { + "architecture": "x86_64", + "authoritative-source-url": "registry.access.redhat.com", + "build-date": "2019-09-02T12:56:18.824770", + "com.redhat.build-host": "cpt-1005.osbs.prod.upshift.rdu2.redhat.com", + "com.redhat.component": "ubi7-container", + "com.redhat.license_terms": "https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI", + "description": "The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.", + "distribution-scope": "public", + "io.k8s.description": "The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.", + "io.k8s.display-name": "Red Hat Universal Base Image 7", + "io.openshift.tags": "base rhel7", + "maintainer": "Red Hat, Inc.", + "name": "ubi7", + "release": "140", + "summary": "Provides the latest release of the Red Hat Universal Base Image 7.", + "url": "https://access.redhat.com/containers/#/registry.access.redhat.com/ubi7/images/7.7-140", + "vcs-ref": "4c80c8aa26e69950ab11b87789c8fb7665b1632d", + "vcs-type": "git", + "vendor": "Red Hat, Inc.", + "version": "7.7" + }, + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/ubi-7.tar.gz (redhat 7.7)", + "Class": "os-pkgs", + "Type": "redhat", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@4.2.46-33.el7.x86_64", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:rpm/redhat/bash@4.2.46-33.el7?arch=x86_64\u0026distro=redhat-7.7", + "UID": "f5b786381193ad1b" + }, + "InstalledVersion": "4.2.46-33.el7", + "Status": "will_not_fix", + "Layer": { + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + } + ] + }, + { + "Target": "Python", + "Class": "lang-pkgs", + "Type": "python-pkg", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-40897", + "PkgName": "setuptools", + "PkgPath": "usr/lib/python2.7/site-packages/setuptools-0.9.8-py2.7.egg-info/PKG-INFO", + "PkgIdentifier": { + "PURL": "pkg:pypi/setuptools@0.9.8", + "UID": "3f4c89bf681c1d7a" + }, + "InstalledVersion": "0.9.8", + "FixedVersion": "65.5.1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-40897", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "pypa-setuptools: Regular Expression Denial of Service (ReDoS) in package_index.py", + "Description": "Python Packaging Authority (PyPA) setuptools before 65.5.1 allows remote attackers to cause a denial of service via HTML in a crafted package or custom PackageIndex page. There is a Regular Expression Denial of Service (ReDoS) in package_index.py.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-1333" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 2 + }, + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 5.9 + } + }, + "References": [ + "https://access.redhat.com/errata/RHSA-2023:0952", + "https://access.redhat.com/security/cve/CVE-2022-40897" + ], + "PublishedDate": "2022-12-23T00:15:13.987Z", + "LastModifiedDate": "2024-06-21T19:15:23.877Z" + } + ] + } + ] +} diff --git a/pkg/cache/key.go b/pkg/cache/key.go index 7d6720393554..0ad0fde82fe1 100644 --- a/pkg/cache/key.go +++ b/pkg/cache/key.go @@ -13,6 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[string]int, artifactOpt artifact.Option) (string, error) { @@ -24,13 +25,22 @@ func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[str // Write ID, analyzer/handler versions, skipped files/dirs and file patterns keyBase := struct { - ID string - AnalyzerVersions analyzer.Versions - HookVersions map[string]int - SkipFiles []string - SkipDirs []string - FilePatterns []string `json:",omitempty"` - }{id, analyzerVersions, hookVersions, artifactOpt.WalkerOption.SkipFiles, artifactOpt.WalkerOption.SkipDirs, artifactOpt.FilePatterns} + ID string + AnalyzerVersions analyzer.Versions + HookVersions map[string]int + SkipFiles []string + SkipDirs []string + FilePatterns []string `json:",omitempty"` + DetectionPriority types.DetectionPriority `json:",omitempty"` + }{ + id, + analyzerVersions, + hookVersions, + artifactOpt.WalkerOption.SkipFiles, + artifactOpt.WalkerOption.SkipDirs, + artifactOpt.FilePatterns, + artifactOpt.DetectionPriority, + } if err := json.NewEncoder(h).Encode(keyBase); err != nil { return "", xerrors.Errorf("json encode error: %w", err) diff --git a/pkg/cache/key_test.go b/pkg/cache/key_test.go index 012e9edd407f..d80748567683 100644 --- a/pkg/cache/key_test.go +++ b/pkg/cache/key_test.go @@ -8,21 +8,23 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" ) func TestCalcKey(t *testing.T) { type args struct { - key string - analyzerVersions analyzer.Versions - hookVersions map[string]int - skipFiles []string - skipDirs []string - patterns []string - policy []string - data []string - secretConfigPath string + key string + analyzerVersions analyzer.Versions + hookVersions map[string]int + skipFiles []string + skipDirs []string + patterns []string + policy []string + data []string + secretConfigPath string + detectionPriority types.DetectionPriority } tests := []struct { name string @@ -115,7 +117,10 @@ func TestCalcKey(t *testing.T) { "debian": 1, }, }, - patterns: []string{"test", ""}, + patterns: []string{ + "test", + "", + }, }, want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", }, @@ -129,7 +134,10 @@ func TestCalcKey(t *testing.T) { "debian": 1, }, }, - patterns: []string{"", "test"}, + patterns: []string{ + "", + "test", + }, }, want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", }, @@ -177,6 +185,23 @@ func TestCalcKey(t *testing.T) { }, want: "sha256:363f70f4ee795f250873caea11c2fc94ef12945444327e7e2f8a99e3884695e0", }, + { + name: "detection priority", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + skipFiles: []string{"app/deployment.yaml"}, + skipDirs: []string{"usr/java"}, + policy: []string{"testdata/policy"}, + detectionPriority: types.PriorityComprehensive, + }, + want: "sha256:2f1c898271e84f4382cd48ae7533069cc3dc656c2d688ac108f5db1a0d9fd393", + }, { name: "secret config", @@ -231,7 +256,8 @@ func TestCalcKey(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { artifactOpt := artifact.Option{ - FilePatterns: tt.args.patterns, + FilePatterns: tt.args.patterns, + DetectionPriority: tt.args.detectionPriority, MisconfScannerOption: misconf.ScannerOption{ PolicyPaths: tt.args.policy, @@ -249,7 +275,6 @@ func TestCalcKey(t *testing.T) { } got, err := CalcKey(tt.args.key, tt.args.analyzerVersions, tt.args.hookVersions, artifactOpt) if tt.wantErr != "" { - require.Error(t, err) assert.ErrorContains(t, err, tt.wantErr) return } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index e49829e352fb..6528ec2c3d36 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -568,6 +568,10 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan fileChecksum = true } + // Disable the post handler for filtering system file when detection priority is comprehensive. + disabledHandlers := lo.Ternary(opts.DetectionPriority == ftypes.PriorityComprehensive, + []ftypes.HandlerType{ftypes.SystemFileFilteringPostHandler}, nil) + return ScannerConfig{ Target: target, CacheOptions: opts.CacheOpts(), @@ -579,6 +583,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan }, ArtifactOption: artifact.Option{ DisabledAnalyzers: disabledAnalyzers(opts), + DisabledHandlers: disabledHandlers, FilePatterns: opts.FilePatterns, Parallel: opts.Parallel, Offline: opts.OfflineScan, @@ -592,6 +597,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan AWSRegion: opts.Region, AWSEndpoint: opts.Endpoint, FileChecksum: fileChecksum, + DetectionPriority: opts.DetectionPriority, // For image scanning ImageOption: ftypes.ImageOptions{ diff --git a/pkg/dependency/parser/dart/pub/parse.go b/pkg/dependency/parser/dart/pub/parse.go index 1bd85a56f1d6..f17fedf0a2b0 100644 --- a/pkg/dependency/parser/dart/pub/parse.go +++ b/pkg/dependency/parser/dart/pub/parse.go @@ -19,12 +19,14 @@ const ( // Parser is a parser for pubspec.lock type Parser struct { - logger *log.Logger + logger *log.Logger + useMinVersion bool } -func NewParser() *Parser { +func NewParser(useMinVersion bool) *Parser { return &Parser{ - logger: log.WithPrefix("pub"), + logger: log.WithPrefix("pub"), + useMinVersion: useMinVersion, } } @@ -50,7 +52,7 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency var pkgs []ftypes.Package for name, dep := range l.Packages { version := dep.Version - if version == "0.0.0" && dep.Source == "sdk" { + if version == "0.0.0" && dep.Source == "sdk" && p.useMinVersion { version = p.findSDKVersion(l, name, dep) } @@ -71,27 +73,27 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency return pkgs, nil, nil } -// findSDKVersion detects the first version of the SDK constraint specified in the Description. +// findSDKVersion detects the minimum version of the SDK constraint specified in the Description. // If the constraint is not found, it returns the original version. func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string { // Some dependencies use one of the SDK versions. // In this case dep.Version == `0.0.0`. // We can't get versions for these dependencies. - // Therefore, we use the first version of the SDK constraint specified in the Description. + // Therefore, we use the minimum version of the SDK constraint specified in the Description. // See https://github.com/aquasecurity/trivy/issues/6017 constraint, ok := l.Sdks[string(dep.Description)] if !ok { return dep.Version } - v, err := firstVersionOfConstrain(constraint) + v, err := minVersionOfConstrain(constraint) if err != nil { p.logger.Warn("Unable to get sdk version from constraint", log.Err(err)) return dep.Version } else if v == "" { return dep.Version } - p.logger.Info("Using the first version of the constraint from the sdk source", log.String("dep", name), + p.logger.Info("Using the minimum version of the constraint from the sdk source", log.String("dep", name), log.String("constraint", constraint)) return v } @@ -106,8 +108,8 @@ func (p Parser) relationship(dep string) ftypes.Relationship { return ftypes.RelationshipUnknown } -// firstVersionOfConstrain returns the first acceptable version for constraint -func firstVersionOfConstrain(constraint string) (string, error) { +// minVersionOfConstrain returns the minimum acceptable version for constraint +func minVersionOfConstrain(constraint string) (string, error) { css, err := goversion.NewConstraints(constraint) if err != nil { return "", xerrors.Errorf("unable to parse constraints: %w", err) @@ -119,7 +121,7 @@ func firstVersionOfConstrain(constraint string) (string, error) { if len(constraints) == 0 || len(constraints[0]) == 0 { return "", nil } - // We only need to get the first version from the range + // We only need to get the minimum version from the range if constraints[0][0].Operator() != ">=" && constraints[0][0].Operator() != "^" { return "", nil } diff --git a/pkg/dependency/parser/dart/pub/parse_test.go b/pkg/dependency/parser/dart/pub/parse_test.go index 5f4591201b7c..e884adf28306 100644 --- a/pkg/dependency/parser/dart/pub/parse_test.go +++ b/pkg/dependency/parser/dart/pub/parse_test.go @@ -15,14 +15,42 @@ import ( func TestParser_Parse(t *testing.T) { tests := []struct { - name string - inputFile string - want []ftypes.Package - wantErr assert.ErrorAssertionFunc + name string + useMinVersion bool + inputFile string + want []ftypes.Package + wantErr assert.ErrorAssertionFunc }{ { - name: "happy path", - inputFile: "testdata/happy.lock", + name: "not use minimum version", + useMinVersion: false, + inputFile: "testdata/happy.lock", + want: []ftypes.Package{ + { + ID: "crypto@3.0.2", + Name: "crypto", + Version: "3.0.2", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "flutter_test@0.0.0", + Name: "flutter_test", + Version: "0.0.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "uuid@3.0.6", + Name: "uuid", + Version: "3.0.6", + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantErr: assert.NoError, + }, + { + name: "use minimum version", + useMinVersion: true, + inputFile: "testdata/happy.lock", want: []ftypes.Package{ { ID: "crypto@3.0.2", @@ -63,7 +91,7 @@ func TestParser_Parse(t *testing.T) { require.NoError(t, err) defer f.Close() - gotPkgs, _, err := pub.NewParser().Parse(f) + gotPkgs, _, err := pub.NewParser(tt.useMinVersion).Parse(f) if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.inputFile)) { return } diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index d2d07ffcc733..b5bb8e629acf 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -44,6 +44,7 @@ type AnalyzerOptions struct { Parallel int FilePatterns []string DisabledAnalyzers []Type + DetectionPriority types.DetectionPriority MisconfScannerOption misconf.ScannerOption SecretScannerOption SecretScannerOption LicenseScannerOption LicenseScannerOption @@ -120,10 +121,11 @@ type CustomGroup interface { type Opener func() (xio.ReadSeekCloserAt, error) type AnalyzerGroup struct { - logger *log.Logger - analyzers []analyzer - postAnalyzers []PostAnalyzer - filePatterns map[Type][]*regexp.Regexp + logger *log.Logger + analyzers []analyzer + postAnalyzers []PostAnalyzer + filePatterns map[Type][]*regexp.Regexp + detectionPriority types.DetectionPriority } /////////////////////////// @@ -312,17 +314,18 @@ func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type, const separator = ":" -func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { - groupName := opt.Group +func NewAnalyzerGroup(opts AnalyzerOptions) (AnalyzerGroup, error) { + groupName := opts.Group if groupName == "" { groupName = GroupBuiltin } group := AnalyzerGroup{ - logger: log.WithPrefix("analyzer"), - filePatterns: make(map[Type][]*regexp.Regexp), + logger: log.WithPrefix("analyzer"), + filePatterns: make(map[Type][]*regexp.Regexp), + detectionPriority: opts.DetectionPriority, } - for _, p := range opt.FilePatterns { + for _, p := range opts.FilePatterns { // e.g. "dockerfile:my_dockerfile_*" s := strings.SplitN(p, separator, 2) if len(s) != 2 { @@ -343,12 +346,12 @@ func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { } for analyzerType, a := range analyzers { - if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { + if !belongToGroup(groupName, analyzerType, opts.DisabledAnalyzers, a) { continue } // Initialize only scanners that have Init() if ini, ok := a.(Initializer); ok { - if err := ini.Init(opt); err != nil { + if err := ini.Init(opts); err != nil { return AnalyzerGroup{}, xerrors.Errorf("analyzer initialization error: %w", err) } } @@ -356,11 +359,11 @@ func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { } for analyzerType, init := range postAnalyzers { - a, err := init(opt) + a, err := init(opts) if err != nil { return AnalyzerGroup{}, xerrors.Errorf("post-analyzer init error: %w", err) } - if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { + if !belongToGroup(groupName, analyzerType, opts.DisabledAnalyzers, a) { continue } group.postAnalyzers = append(group.postAnalyzers, a) @@ -473,6 +476,11 @@ func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeF } skippedFiles := result.SystemInstalledFiles + if ag.detectionPriority == types.PriorityComprehensive { + // If the detection priority is comprehensive, system files installed by the OS package manager will not be skipped. + // It can lead to false positives and duplicates, but it may be necessary to detect all possible vulnerabilities. + skippedFiles = nil + } for _, app := range result.Applications { skippedFiles = append(skippedFiles, app.FilePath) for _, pkg := range app.Packages { diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index 9def336d406f..30fc2dadb18b 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -37,10 +37,10 @@ type pubSpecLockAnalyzer struct { parser language.Parser } -func newPubSpecLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { +func newPubSpecLockAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return pubSpecLockAnalyzer{ logger: log.WithPrefix("pub"), - parser: pub.NewParser(), + parser: pub.NewParser(opts.DetectionPriority == types.PriorityComprehensive), }, nil } diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go index e431278f6fc2..b6034cb5ac63 100644 --- a/pkg/fanal/artifact/artifact.go +++ b/pkg/fanal/artifact/artifact.go @@ -28,6 +28,7 @@ type Option struct { AWSRegion string AWSEndpoint string FileChecksum bool // For SPDX + DetectionPriority types.DetectionPriority // Git repositories RepoBranch string @@ -50,6 +51,7 @@ func (o *Option) AnalyzerOptions() analyzer.AnalyzerOptions { FilePatterns: o.FilePatterns, Parallel: o.Parallel, DisabledAnalyzers: o.DisabledAnalyzers, + DetectionPriority: o.DetectionPriority, MisconfScannerOption: o.MisconfScannerOption, SecretScannerOption: o.SecretScannerOption, LicenseScannerOption: o.LicenseScannerOption, diff --git a/pkg/fanal/types/detection.go b/pkg/fanal/types/detection.go new file mode 100644 index 000000000000..38d1634ed2a3 --- /dev/null +++ b/pkg/fanal/types/detection.go @@ -0,0 +1,10 @@ +package types + +// DetectionPriority represents the priority of detection +type DetectionPriority string + +// PriorityPrecise tries to minimize false positives +const PriorityPrecise DetectionPriority = "precise" + +// PriorityComprehensive tries to minimize false negatives +const PriorityComprehensive DetectionPriority = "comprehensive" diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index b413fce01fe9..544d56691883 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -5,6 +5,7 @@ import ( "github.com/samber/lo" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" @@ -96,43 +97,59 @@ var ( Default: "https://rekor.sigstore.dev", Usage: "[EXPERIMENTAL] address of rekor STL server", } + DetectionPriority = Flag[string]{ + Name: "detection-priority", + ConfigName: "scan.detection-priority", + Default: string(ftypes.PriorityPrecise), + Values: xstrings.ToStringSlice([]ftypes.DetectionPriority{ + ftypes.PriorityPrecise, + ftypes.PriorityComprehensive, + }), + Usage: `specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. +`, + } ) type ScanFlagGroup struct { - SkipDirs *Flag[[]string] - SkipFiles *Flag[[]string] - OfflineScan *Flag[bool] - Scanners *Flag[[]string] - FilePatterns *Flag[[]string] - Slow *Flag[bool] // deprecated - Parallel *Flag[int] - SBOMSources *Flag[[]string] - RekorURL *Flag[string] + SkipDirs *Flag[[]string] + SkipFiles *Flag[[]string] + OfflineScan *Flag[bool] + Scanners *Flag[[]string] + FilePatterns *Flag[[]string] + Slow *Flag[bool] // deprecated + Parallel *Flag[int] + SBOMSources *Flag[[]string] + RekorURL *Flag[string] + DetectionPriority *Flag[string] } type ScanOptions struct { - Target string - SkipDirs []string - SkipFiles []string - OfflineScan bool - Scanners types.Scanners - FilePatterns []string - Parallel int - SBOMSources []string - RekorURL string + Target string + SkipDirs []string + SkipFiles []string + OfflineScan bool + Scanners types.Scanners + FilePatterns []string + Parallel int + SBOMSources []string + RekorURL string + DetectionPriority ftypes.DetectionPriority } func NewScanFlagGroup() *ScanFlagGroup { return &ScanFlagGroup{ - SkipDirs: SkipDirsFlag.Clone(), - SkipFiles: SkipFilesFlag.Clone(), - OfflineScan: OfflineScanFlag.Clone(), - Scanners: ScannersFlag.Clone(), - FilePatterns: FilePatternsFlag.Clone(), - Parallel: ParallelFlag.Clone(), - SBOMSources: SBOMSourcesFlag.Clone(), - RekorURL: RekorURLFlag.Clone(), - Slow: SlowFlag.Clone(), + SkipDirs: SkipDirsFlag.Clone(), + SkipFiles: SkipFilesFlag.Clone(), + OfflineScan: OfflineScanFlag.Clone(), + Scanners: ScannersFlag.Clone(), + FilePatterns: FilePatternsFlag.Clone(), + Parallel: ParallelFlag.Clone(), + SBOMSources: SBOMSourcesFlag.Clone(), + RekorURL: RekorURLFlag.Clone(), + Slow: SlowFlag.Clone(), + DetectionPriority: DetectionPriority.Clone(), } } @@ -151,6 +168,7 @@ func (f *ScanFlagGroup) Flags() []Flagger { f.Parallel, f.SBOMSources, f.RekorURL, + f.DetectionPriority, } } @@ -171,14 +189,15 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { } return ScanOptions{ - Target: target, - SkipDirs: f.SkipDirs.Value(), - SkipFiles: f.SkipFiles.Value(), - OfflineScan: f.OfflineScan.Value(), - Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), - FilePatterns: f.FilePatterns.Value(), - Parallel: parallel, - SBOMSources: f.SBOMSources.Value(), - RekorURL: f.RekorURL.Value(), + Target: target, + SkipDirs: f.SkipDirs.Value(), + SkipFiles: f.SkipFiles.Value(), + OfflineScan: f.OfflineScan.Value(), + Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), + FilePatterns: f.FilePatterns.Value(), + Parallel: parallel, + SBOMSources: f.SBOMSources.Value(), + RekorURL: f.RekorURL.Value(), + DetectionPriority: ftypes.DetectionPriority(f.DetectionPriority.Value()), }, nil } diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 09b90e745c58..0fef0028bef3 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -1,9 +1,100 @@ package types import ( + "slices" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) +// PkgType represents package type +type PkgType = string + +// Scanner represents the type of security scanning +type Scanner string + +// Scanners is a slice of scanners +type Scanners []Scanner + +const ( + // PkgTypeUnknown is a package type of unknown + PkgTypeUnknown PkgType = "unknown" + + // PkgTypeOS is a package type of OS packages + PkgTypeOS PkgType = "os" + + // PkgTypeLibrary is a package type of programming language dependencies + PkgTypeLibrary PkgType = "library" + + // UnknownScanner is the scanner of unknown + UnknownScanner Scanner = "unknown" + + // NoneScanner is the scanner of none + NoneScanner Scanner = "none" + + // SBOMScanner is the virtual scanner of SBOM, which cannot be enabled by the user + SBOMScanner Scanner = "sbom" + + // VulnerabilityScanner is the scanner of vulnerabilities + VulnerabilityScanner Scanner = "vuln" + + // MisconfigScanner is the scanner of misconfigurations + MisconfigScanner Scanner = "misconfig" + + // SecretScanner is the scanner of secrets + SecretScanner Scanner = "secret" + + // RBACScanner is the scanner of rbac assessment + RBACScanner Scanner = "rbac" + + // LicenseScanner is the scanner of licenses + LicenseScanner Scanner = "license" +) + +var ( + PkgTypes = []string{ + PkgTypeOS, + PkgTypeLibrary, + } + + AllScanners = Scanners{ + VulnerabilityScanner, + MisconfigScanner, + RBACScanner, + SecretScanner, + LicenseScanner, + NoneScanner, + } + + // AllImageConfigScanners has a list of available scanners on container image config. + // The container image in container registries consists of manifest, config and layers. + // Trivy is also able to detect security issues on the image config. + AllImageConfigScanners = Scanners{ + MisconfigScanner, + SecretScanner, + NoneScanner, + } +) + +func (scanners *Scanners) Enable(s Scanner) { + if !scanners.Enabled(s) { + *scanners = append(*scanners, s) + } +} + +func (scanners *Scanners) Enabled(s Scanner) bool { + return slices.Contains(*scanners, s) +} + +// AnyEnabled returns true if any of the passed scanners is included. +func (scanners *Scanners) AnyEnabled(ss ...Scanner) bool { + for _, s := range ss { + if scanners.Enabled(s) { + return true + } + } + return false +} + // ScanTarget holds the attributes for scanning. type ScanTarget struct { Name string // container image name, file path, etc diff --git a/pkg/types/target.go b/pkg/types/target.go deleted file mode 100644 index a8cccced3de7..000000000000 --- a/pkg/types/target.go +++ /dev/null @@ -1,94 +0,0 @@ -package types - -import ( - "slices" -) - -// PkgType represents package type -type PkgType = string - -// Scanner represents the type of security scanning -type Scanner string - -// Scanners is a slice of scanners -type Scanners []Scanner - -const ( - // PkgTypeUnknown is a package type of unknown - PkgTypeUnknown = PkgType("unknown") - - // PkgTypeOS is a package type of OS packages - PkgTypeOS = PkgType("os") - - // PkgTypeLibrary is a package type of programming language dependencies - PkgTypeLibrary = PkgType("library") - - // UnknownScanner is the scanner of unknown - UnknownScanner = Scanner("unknown") - - // NoneScanner is the scanner of none - NoneScanner = Scanner("none") - - // SBOMScanner is the virtual scanner of SBOM, which cannot be enabled by the user - SBOMScanner = Scanner("sbom") - - // VulnerabilityScanner is the scanner of vulnerabilities - VulnerabilityScanner = Scanner("vuln") - - // MisconfigScanner is the scanner of misconfigurations - MisconfigScanner = Scanner("misconfig") - - // SecretScanner is the scanner of secrets - SecretScanner = Scanner("secret") - - // RBACScanner is the scanner of rbac assessment - RBACScanner = Scanner("rbac") - - // LicenseScanner is the scanner of licenses - LicenseScanner = Scanner("license") -) - -var ( - PkgTypes = []string{ - PkgTypeOS, - PkgTypeLibrary, - } - - AllScanners = Scanners{ - VulnerabilityScanner, - MisconfigScanner, - RBACScanner, - SecretScanner, - LicenseScanner, - NoneScanner, - } - - // AllImageConfigScanners has a list of available scanners on container image config. - // The container image in container registries consists of manifest, config and layers. - // Trivy is also able to detect security issues on the image config. - AllImageConfigScanners = Scanners{ - MisconfigScanner, - SecretScanner, - NoneScanner, - } -) - -func (scanners *Scanners) Enable(s Scanner) { - if !scanners.Enabled(s) { - *scanners = append(*scanners, s) - } -} - -func (scanners *Scanners) Enabled(s Scanner) bool { - return slices.Contains(*scanners, s) -} - -// AnyEnabled returns true if any of the passed scanners is included. -func (scanners *Scanners) AnyEnabled(ss ...Scanner) bool { - for _, s := range ss { - if scanners.Enabled(s) { - return true - } - } - return false -}