feat(vuln): Add --detection-priority flag for accuracy tuning (#7288)

Signed-off-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
Teppei Fukuda
2024-08-02 14:41:56 +04:00
committed by GitHub
parent e95152f796
commit fd8348d610
30 changed files with 675 additions and 221 deletions

View File

@@ -11,9 +11,9 @@ The following scanners are supported.
The following table provides an outline of the features Trivy offers. The following table provides an outline of the features Trivy offers.
| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | | Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] |
|-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| |-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:|
| [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | | [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | ✓ |
## Dart ## Dart
In order to detect dependencies, Trivy searches for `pubspec.lock`. 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. So Trivy includes all dependencies in report.
### SDK dependencies ### 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 ```yaml
flutter: flutter:
dependency: "direct main" dependency: "direct main"
@@ -40,6 +42,7 @@ sdks:
### Dependency tree ### Dependency tree
To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only). To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only).
!!! note !!! note
Make sure the cache directory contains all the dependencies installed in your application. To download missing dependencies, use `dart pub get` command. 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/ [dart-repository]: https://pub.dev/
[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies
[cache-directory]: https://dart.dev/tools/pub/glossary#system-cache [cache-directory]: https://dart.dev/tools/pub/glossary#system-cache
[detection-priority]: ../../scanner/vulnerability.md#detection-priority

View File

@@ -16,10 +16,10 @@ The following scanners are supported.
The table below provides an outline of the features Trivy offers. The table below provides an outline of the features Trivy offers.
| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | | Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | [Detection Priority][detection-priority] |
|----------|:-----------:|:-----------------|:------------------------------------:|:------:| |----------|:-----------:|:-----------------|:------------------------------------:|:------:|:----------------------------------------:|
| Modules | ✅ | Include | ✅[^2] | - | | Modules | ✅ | Include | ✅[^2] | - | - |
| Binaries | ✅ | Exclude | - | ✅[^4] | | Binaries | ✅ | Exclude | - | ✅[^4] | Not needed |
!!! note !!! note
Trivy scans only dependencies of the Go project. 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 [^5]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604
[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies
[detection-priority]: ../../scanner/vulnerability.md#detection-priority

View File

@@ -12,12 +12,12 @@ Each artifact supports the following scanners:
The following table provides an outline of the features Trivy offers. The following table provides an outline of the features Trivy offers.
| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | | Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] |
|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:| |------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:|
| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | | JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed |
| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | | pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | - |
| *gradle.lockfile | - | Exclude | ✓ | ✓ | | *gradle.lockfile | - | Exclude | ✓ | ✓ | Not needed |
| *.sbt.lock | - | Exclude | - | ✓ | | *.sbt.lock | - | Exclude | - | ✓ | Not needed |
These may be enabled or disabled depending on the target. These may be enabled or disabled depending on the target.
See [here](./index.md) for the detail. 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-central]: https://repo.maven.apache.org/maven2/
[maven-pom-repos]: https://maven.apache.org/settings.html#repositories [maven-pom-repos]: https://maven.apache.org/settings.html#repositories
[sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock [sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock
[detection-priority]: ../../scanner/vulnerability.md#detection-priority

View File

@@ -21,11 +21,11 @@ The following scanners are supported for Python packages.
The following table provides an outline of the features Trivy offers. The following table provides an outline of the features Trivy offers.
| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | | Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] |
|-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| |-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:|
| pip | requirements.txt | - | Include | - | ✓ | | pip | requirements.txt | - | Include | - | ✓ | - |
| Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | | Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | Not needed |
| Poetry | poetry.lock | ✓ | Exclude | ✓ | - | | Poetry | poetry.lock | ✓ | Exclude | ✓ | - | Not needed |
| Packaging | Dependency graph | | 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. [^1]: Trivy checks `python`, `python3`, `python2` and `python.exe` file names.
[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies
[detection-priority]: ../../scanner/vulnerability.md#detection-priority

View File

@@ -8,6 +8,9 @@ Trivy supports the following scanners for Conda packages.
| Vulnerability | - | | Vulnerability | - |
| License | ✓ | | License | ✓ |
| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] |
|-----------------|-----------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:|
| Conda | environment.yml | - | Include | - | ✓ | - |
## `<package>.json` ## `<package>.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 [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 [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 [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

View File

@@ -30,6 +30,10 @@ trivy filesystem [flags] PATH
--custom-headers strings custom headers in client mode --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") --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 --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-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 --download-java-db-only download/update Java index database but don't run a scan
--enable-modules strings [EXPERIMENTAL] module names to enable --enable-modules strings [EXPERIMENTAL] module names to enable

View File

@@ -44,6 +44,10 @@ trivy image [flags] IMAGE_NAME
--custom-headers strings custom headers in client mode --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") --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 --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 --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-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 --download-java-db-only download/update Java index database but don't run a scan

View File

@@ -39,6 +39,10 @@ trivy kubernetes [flags] [CONTEXT]
--config-data strings specify paths from which data for the Rego checks will be recursively loaded --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") --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 --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. --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-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 --download-java-db-only download/update Java index database but don't run a scan

View File

@@ -30,6 +30,10 @@ trivy repository [flags] (REPO_PATH | REPO_URL)
--custom-headers strings custom headers in client mode --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") --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 --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-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 --download-java-db-only download/update Java index database but don't run a scan
--enable-modules strings [EXPERIMENTAL] module names to enable --enable-modules strings [EXPERIMENTAL] module names to enable

View File

@@ -32,6 +32,10 @@ trivy rootfs [flags] ROOTDIR
--custom-headers strings custom headers in client mode --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") --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 --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-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 --download-java-db-only download/update Java index database but don't run a scan
--enable-modules strings [EXPERIMENTAL] module names to enable --enable-modules strings [EXPERIMENTAL] module names to enable

View File

@@ -25,6 +25,10 @@ trivy sbom [flags] SBOM_PATH
--compliance string compliance report to generate --compliance string compliance report to generate
--custom-headers strings custom headers in client mode --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") --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-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 --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 --exit-code int specify exit code when any security issues are found

View File

@@ -28,6 +28,10 @@ trivy vm [flags] VM_IMAGE
--custom-headers strings custom headers in client mode --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") --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 --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-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 --download-java-db-only download/update Java index database but don't run a scan
--enable-modules strings [EXPERIMENTAL] module names to enable --enable-modules strings [EXPERIMENTAL] module names to enable

View File

@@ -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`. If authentication is required, you need to run `docker login YOUR_REGISTRY`.
Currently, specifying a username and password is not supported. 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 ## Configuration
This section describes vulnerability-specific configuration. This section describes vulnerability-specific configuration.
Other common options are documented [here](../configuration/index.md). Other common options are documented [here](../configuration/index.md).
@@ -307,6 +355,25 @@ By default, all relationships are included in the scan.
!!! warning !!! warning
As it may not provide a complete package list, `--pkg-relationships` cannot be used with `--dependency-tree`, `--vex` or SBOM generation. 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 [^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 [nvd]: https://nvd.nist.gov/vuln
[k8s-cve]: https://kubernetes.io/docs/reference/issues-security/official-cve-feed/ [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

View File

@@ -287,7 +287,7 @@ func TestClientServer(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 != "" { if tt.args.secretConfig != "" {
osArgs = append(osArgs, "--secret-config", 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.Run(tt.name, func(t *testing.T) {
t.Setenv("AWS_REGION", "test-region") t.Setenv("AWS_REGION", "test-region")
t.Setenv("AWS_ACCOUNT_ID", "123456789012") 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{ runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{
override: overrideUID, override: overrideUID,
@@ -435,7 +435,7 @@ func TestClientServerWithCycloneDX(t *testing.T) {
addr, cacheDir := setup(t, setupOptions{}) addr, cacheDir := setup(t, setupOptions{})
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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{ runTest(t, osArgs, tt.golden, "", types.FormatCycloneDX, runOptions{
fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", fakeUUID: "3ff14136-e09f-4df9-80ea-%012d",
}) })
@@ -488,7 +488,7 @@ func TestClientServerWithToken(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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{ runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{
override: overrideUID, override: overrideUID,
wantErr: tt.wantErr, wantErr: tt.wantErr,
@@ -515,7 +515,7 @@ func TestClientServerWithRedis(t *testing.T) {
golden := "testdata/alpine-39.json.golden" golden := "testdata/alpine-39.json.golden"
t.Run("alpine 3.9", func(t *testing.T) { 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 // Run Trivy client
runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{ runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{
@@ -527,7 +527,7 @@ func TestClientServerWithRedis(t *testing.T) {
require.NoError(t, redisC.Terminate(ctx)) require.NoError(t, redisC.Terminate(ctx))
t.Run("sad path", func(t *testing.T) { t.Run("sad path", func(t *testing.T) {
osArgs := setupClient(t, testArgs, addr, cacheDir, golden) osArgs := setupClient(t, testArgs, addr, cacheDir)
// Run Trivy client // Run Trivy client
runTest(t, osArgs, "", "", types.FormatJSON, runOptions{ runTest(t, osArgs, "", "", types.FormatJSON, runOptions{
@@ -592,7 +592,7 @@ func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []stri
return osArgs 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 == "" { if c.Command == "" {
c.Command = "image" c.Command = "image"
} }

View File

@@ -8,6 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -15,13 +16,14 @@ import (
func TestTar(t *testing.T) { func TestTar(t *testing.T) {
type args struct { type args struct {
IgnoreUnfixed bool IgnoreUnfixed bool
Severity []string Severity []string
IgnoreIDs []string IgnoreIDs []string
Format types.Format Format types.Format
Input string Input string
SkipDirs []string SkipDirs []string
SkipFiles []string SkipFiles []string
DetectionPriority ftypes.DetectionPriority
} }
tests := []struct { tests := []struct {
name string name string
@@ -240,7 +242,7 @@ func TestTar(t *testing.T) {
golden: "testdata/centos-7.json.golden", golden: "testdata/centos-7.json.golden",
}, },
{ {
name: "centos 7with --ignore-unfixed option", name: "centos 7 with --ignore-unfixed option",
args: args{ args: args{
IgnoreUnfixed: true, IgnoreUnfixed: true,
Format: types.FormatJSON, Format: types.FormatJSON,
@@ -274,6 +276,15 @@ func TestTar(t *testing.T) {
}, },
golden: "testdata/ubi-7.json.golden", 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", name: "almalinux 8",
args: args{ args: args{
@@ -380,7 +391,7 @@ func TestTar(t *testing.T) {
"-q", "-q",
"--format", "--format",
string(tt.args.Format), string(tt.args.Format),
"--skip-update", "--skip-db-update",
} }
if tt.args.IgnoreUnfixed { 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 // Run Trivy
runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{}) runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{})
}) })

View File

@@ -14,3 +14,11 @@
- 0.11.6 - 0.11.6
VulnerableVersions: VulnerableVersions:
- < 0.11.6 - < 0.11.6
- bucket: setuptools
pairs:
- key: CVE-2022-40897
value:
PatchedVersions:
- 65.5.1
VulnerableVersions:
- < 65.5.1

View File

@@ -1399,4 +1399,23 @@
- "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155" - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155"
- "https://nvd.nist.gov/vuln/detail/CVE-2020-14155" - "https://nvd.nist.gov/vuln/detail/CVE-2020-14155"
PublishedDate: "2020-06-15T17:15:00Z" PublishedDate: "2020-06-15T17:15:00Z"
LastModifiedDate: "2022-04-28T15:06:00Z" 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"

View File

@@ -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"
}
]
}
]
}

24
pkg/cache/key.go vendored
View File

@@ -13,6 +13,7 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "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) { 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 // Write ID, analyzer/handler versions, skipped files/dirs and file patterns
keyBase := struct { keyBase := struct {
ID string ID string
AnalyzerVersions analyzer.Versions AnalyzerVersions analyzer.Versions
HookVersions map[string]int HookVersions map[string]int
SkipFiles []string SkipFiles []string
SkipDirs []string SkipDirs []string
FilePatterns []string `json:",omitempty"` FilePatterns []string `json:",omitempty"`
}{id, analyzerVersions, hookVersions, artifactOpt.WalkerOption.SkipFiles, artifactOpt.WalkerOption.SkipDirs, artifactOpt.FilePatterns} 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 { if err := json.NewEncoder(h).Encode(keyBase); err != nil {
return "", xerrors.Errorf("json encode error: %w", err) return "", xerrors.Errorf("json encode error: %w", err)

51
pkg/cache/key_test.go vendored
View File

@@ -8,21 +8,23 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "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/fanal/walker"
"github.com/aquasecurity/trivy/pkg/misconf" "github.com/aquasecurity/trivy/pkg/misconf"
) )
func TestCalcKey(t *testing.T) { func TestCalcKey(t *testing.T) {
type args struct { type args struct {
key string key string
analyzerVersions analyzer.Versions analyzerVersions analyzer.Versions
hookVersions map[string]int hookVersions map[string]int
skipFiles []string skipFiles []string
skipDirs []string skipDirs []string
patterns []string patterns []string
policy []string policy []string
data []string data []string
secretConfigPath string secretConfigPath string
detectionPriority types.DetectionPriority
} }
tests := []struct { tests := []struct {
name string name string
@@ -115,7 +117,10 @@ func TestCalcKey(t *testing.T) {
"debian": 1, "debian": 1,
}, },
}, },
patterns: []string{"test", ""}, patterns: []string{
"test",
"",
},
}, },
want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103",
}, },
@@ -129,7 +134,10 @@ func TestCalcKey(t *testing.T) {
"debian": 1, "debian": 1,
}, },
}, },
patterns: []string{"", "test"}, patterns: []string{
"",
"test",
},
}, },
want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103",
}, },
@@ -177,6 +185,23 @@ func TestCalcKey(t *testing.T) {
}, },
want: "sha256:363f70f4ee795f250873caea11c2fc94ef12945444327e7e2f8a99e3884695e0", 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", name: "secret config",
@@ -231,7 +256,8 @@ func TestCalcKey(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
artifactOpt := artifact.Option{ artifactOpt := artifact.Option{
FilePatterns: tt.args.patterns, FilePatterns: tt.args.patterns,
DetectionPriority: tt.args.detectionPriority,
MisconfScannerOption: misconf.ScannerOption{ MisconfScannerOption: misconf.ScannerOption{
PolicyPaths: tt.args.policy, 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) got, err := CalcKey(tt.args.key, tt.args.analyzerVersions, tt.args.hookVersions, artifactOpt)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err)
assert.ErrorContains(t, err, tt.wantErr) assert.ErrorContains(t, err, tt.wantErr)
return return
} }

View File

@@ -568,6 +568,10 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan
fileChecksum = true 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{ return ScannerConfig{
Target: target, Target: target,
CacheOptions: opts.CacheOpts(), CacheOptions: opts.CacheOpts(),
@@ -579,6 +583,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan
}, },
ArtifactOption: artifact.Option{ ArtifactOption: artifact.Option{
DisabledAnalyzers: disabledAnalyzers(opts), DisabledAnalyzers: disabledAnalyzers(opts),
DisabledHandlers: disabledHandlers,
FilePatterns: opts.FilePatterns, FilePatterns: opts.FilePatterns,
Parallel: opts.Parallel, Parallel: opts.Parallel,
Offline: opts.OfflineScan, Offline: opts.OfflineScan,
@@ -592,6 +597,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan
AWSRegion: opts.Region, AWSRegion: opts.Region,
AWSEndpoint: opts.Endpoint, AWSEndpoint: opts.Endpoint,
FileChecksum: fileChecksum, FileChecksum: fileChecksum,
DetectionPriority: opts.DetectionPriority,
// For image scanning // For image scanning
ImageOption: ftypes.ImageOptions{ ImageOption: ftypes.ImageOptions{

View File

@@ -19,12 +19,14 @@ const (
// Parser is a parser for pubspec.lock // Parser is a parser for pubspec.lock
type Parser struct { type Parser struct {
logger *log.Logger logger *log.Logger
useMinVersion bool
} }
func NewParser() *Parser { func NewParser(useMinVersion bool) *Parser {
return &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 var pkgs []ftypes.Package
for name, dep := range l.Packages { for name, dep := range l.Packages {
version := dep.Version 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) 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 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. // If the constraint is not found, it returns the original version.
func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string { func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string {
// Some dependencies use one of the SDK versions. // Some dependencies use one of the SDK versions.
// In this case dep.Version == `0.0.0`. // In this case dep.Version == `0.0.0`.
// We can't get versions for these dependencies. // 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 // See https://github.com/aquasecurity/trivy/issues/6017
constraint, ok := l.Sdks[string(dep.Description)] constraint, ok := l.Sdks[string(dep.Description)]
if !ok { if !ok {
return dep.Version return dep.Version
} }
v, err := firstVersionOfConstrain(constraint) v, err := minVersionOfConstrain(constraint)
if err != nil { if err != nil {
p.logger.Warn("Unable to get sdk version from constraint", log.Err(err)) p.logger.Warn("Unable to get sdk version from constraint", log.Err(err))
return dep.Version return dep.Version
} else if v == "" { } else if v == "" {
return dep.Version 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)) log.String("constraint", constraint))
return v return v
} }
@@ -106,8 +108,8 @@ func (p Parser) relationship(dep string) ftypes.Relationship {
return ftypes.RelationshipUnknown return ftypes.RelationshipUnknown
} }
// firstVersionOfConstrain returns the first acceptable version for constraint // minVersionOfConstrain returns the minimum acceptable version for constraint
func firstVersionOfConstrain(constraint string) (string, error) { func minVersionOfConstrain(constraint string) (string, error) {
css, err := goversion.NewConstraints(constraint) css, err := goversion.NewConstraints(constraint)
if err != nil { if err != nil {
return "", xerrors.Errorf("unable to parse constraints: %w", err) 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 { if len(constraints) == 0 || len(constraints[0]) == 0 {
return "", nil 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() != "^" { if constraints[0][0].Operator() != ">=" && constraints[0][0].Operator() != "^" {
return "", nil return "", nil
} }

View File

@@ -15,14 +15,42 @@ import (
func TestParser_Parse(t *testing.T) { func TestParser_Parse(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
inputFile string useMinVersion bool
want []ftypes.Package inputFile string
wantErr assert.ErrorAssertionFunc want []ftypes.Package
wantErr assert.ErrorAssertionFunc
}{ }{
{ {
name: "happy path", name: "not use minimum version",
inputFile: "testdata/happy.lock", 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{ want: []ftypes.Package{
{ {
ID: "crypto@3.0.2", ID: "crypto@3.0.2",
@@ -63,7 +91,7 @@ func TestParser_Parse(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer f.Close() 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)) { if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.inputFile)) {
return return
} }

View File

@@ -44,6 +44,7 @@ type AnalyzerOptions struct {
Parallel int Parallel int
FilePatterns []string FilePatterns []string
DisabledAnalyzers []Type DisabledAnalyzers []Type
DetectionPriority types.DetectionPriority
MisconfScannerOption misconf.ScannerOption MisconfScannerOption misconf.ScannerOption
SecretScannerOption SecretScannerOption SecretScannerOption SecretScannerOption
LicenseScannerOption LicenseScannerOption LicenseScannerOption LicenseScannerOption
@@ -120,10 +121,11 @@ type CustomGroup interface {
type Opener func() (xio.ReadSeekCloserAt, error) type Opener func() (xio.ReadSeekCloserAt, error)
type AnalyzerGroup struct { type AnalyzerGroup struct {
logger *log.Logger logger *log.Logger
analyzers []analyzer analyzers []analyzer
postAnalyzers []PostAnalyzer postAnalyzers []PostAnalyzer
filePatterns map[Type][]*regexp.Regexp filePatterns map[Type][]*regexp.Regexp
detectionPriority types.DetectionPriority
} }
/////////////////////////// ///////////////////////////
@@ -312,17 +314,18 @@ func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type,
const separator = ":" const separator = ":"
func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { func NewAnalyzerGroup(opts AnalyzerOptions) (AnalyzerGroup, error) {
groupName := opt.Group groupName := opts.Group
if groupName == "" { if groupName == "" {
groupName = GroupBuiltin groupName = GroupBuiltin
} }
group := AnalyzerGroup{ group := AnalyzerGroup{
logger: log.WithPrefix("analyzer"), logger: log.WithPrefix("analyzer"),
filePatterns: make(map[Type][]*regexp.Regexp), filePatterns: make(map[Type][]*regexp.Regexp),
detectionPriority: opts.DetectionPriority,
} }
for _, p := range opt.FilePatterns { for _, p := range opts.FilePatterns {
// e.g. "dockerfile:my_dockerfile_*" // e.g. "dockerfile:my_dockerfile_*"
s := strings.SplitN(p, separator, 2) s := strings.SplitN(p, separator, 2)
if len(s) != 2 { if len(s) != 2 {
@@ -343,12 +346,12 @@ func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) {
} }
for analyzerType, a := range analyzers { for analyzerType, a := range analyzers {
if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { if !belongToGroup(groupName, analyzerType, opts.DisabledAnalyzers, a) {
continue continue
} }
// Initialize only scanners that have Init() // Initialize only scanners that have Init()
if ini, ok := a.(Initializer); ok { 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) return AnalyzerGroup{}, xerrors.Errorf("analyzer initialization error: %w", err)
} }
} }
@@ -356,11 +359,11 @@ func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) {
} }
for analyzerType, init := range postAnalyzers { for analyzerType, init := range postAnalyzers {
a, err := init(opt) a, err := init(opts)
if err != nil { if err != nil {
return AnalyzerGroup{}, xerrors.Errorf("post-analyzer init error: %w", err) 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 continue
} }
group.postAnalyzers = append(group.postAnalyzers, a) group.postAnalyzers = append(group.postAnalyzers, a)
@@ -473,6 +476,11 @@ func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeF
} }
skippedFiles := result.SystemInstalledFiles 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 { for _, app := range result.Applications {
skippedFiles = append(skippedFiles, app.FilePath) skippedFiles = append(skippedFiles, app.FilePath)
for _, pkg := range app.Packages { for _, pkg := range app.Packages {

View File

@@ -37,10 +37,10 @@ type pubSpecLockAnalyzer struct {
parser language.Parser parser language.Parser
} }
func newPubSpecLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { func newPubSpecLockAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) {
return pubSpecLockAnalyzer{ return pubSpecLockAnalyzer{
logger: log.WithPrefix("pub"), logger: log.WithPrefix("pub"),
parser: pub.NewParser(), parser: pub.NewParser(opts.DetectionPriority == types.PriorityComprehensive),
}, nil }, nil
} }

View File

@@ -28,6 +28,7 @@ type Option struct {
AWSRegion string AWSRegion string
AWSEndpoint string AWSEndpoint string
FileChecksum bool // For SPDX FileChecksum bool // For SPDX
DetectionPriority types.DetectionPriority
// Git repositories // Git repositories
RepoBranch string RepoBranch string
@@ -50,6 +51,7 @@ func (o *Option) AnalyzerOptions() analyzer.AnalyzerOptions {
FilePatterns: o.FilePatterns, FilePatterns: o.FilePatterns,
Parallel: o.Parallel, Parallel: o.Parallel,
DisabledAnalyzers: o.DisabledAnalyzers, DisabledAnalyzers: o.DisabledAnalyzers,
DetectionPriority: o.DetectionPriority,
MisconfScannerOption: o.MisconfScannerOption, MisconfScannerOption: o.MisconfScannerOption,
SecretScannerOption: o.SecretScannerOption, SecretScannerOption: o.SecretScannerOption,
LicenseScannerOption: o.LicenseScannerOption, LicenseScannerOption: o.LicenseScannerOption,

View File

@@ -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"

View File

@@ -5,6 +5,7 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/types"
xstrings "github.com/aquasecurity/trivy/pkg/x/strings" xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
@@ -96,43 +97,59 @@ var (
Default: "https://rekor.sigstore.dev", Default: "https://rekor.sigstore.dev",
Usage: "[EXPERIMENTAL] address of rekor STL server", 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 { type ScanFlagGroup struct {
SkipDirs *Flag[[]string] SkipDirs *Flag[[]string]
SkipFiles *Flag[[]string] SkipFiles *Flag[[]string]
OfflineScan *Flag[bool] OfflineScan *Flag[bool]
Scanners *Flag[[]string] Scanners *Flag[[]string]
FilePatterns *Flag[[]string] FilePatterns *Flag[[]string]
Slow *Flag[bool] // deprecated Slow *Flag[bool] // deprecated
Parallel *Flag[int] Parallel *Flag[int]
SBOMSources *Flag[[]string] SBOMSources *Flag[[]string]
RekorURL *Flag[string] RekorURL *Flag[string]
DetectionPriority *Flag[string]
} }
type ScanOptions struct { type ScanOptions struct {
Target string Target string
SkipDirs []string SkipDirs []string
SkipFiles []string SkipFiles []string
OfflineScan bool OfflineScan bool
Scanners types.Scanners Scanners types.Scanners
FilePatterns []string FilePatterns []string
Parallel int Parallel int
SBOMSources []string SBOMSources []string
RekorURL string RekorURL string
DetectionPriority ftypes.DetectionPriority
} }
func NewScanFlagGroup() *ScanFlagGroup { func NewScanFlagGroup() *ScanFlagGroup {
return &ScanFlagGroup{ return &ScanFlagGroup{
SkipDirs: SkipDirsFlag.Clone(), SkipDirs: SkipDirsFlag.Clone(),
SkipFiles: SkipFilesFlag.Clone(), SkipFiles: SkipFilesFlag.Clone(),
OfflineScan: OfflineScanFlag.Clone(), OfflineScan: OfflineScanFlag.Clone(),
Scanners: ScannersFlag.Clone(), Scanners: ScannersFlag.Clone(),
FilePatterns: FilePatternsFlag.Clone(), FilePatterns: FilePatternsFlag.Clone(),
Parallel: ParallelFlag.Clone(), Parallel: ParallelFlag.Clone(),
SBOMSources: SBOMSourcesFlag.Clone(), SBOMSources: SBOMSourcesFlag.Clone(),
RekorURL: RekorURLFlag.Clone(), RekorURL: RekorURLFlag.Clone(),
Slow: SlowFlag.Clone(), Slow: SlowFlag.Clone(),
DetectionPriority: DetectionPriority.Clone(),
} }
} }
@@ -151,6 +168,7 @@ func (f *ScanFlagGroup) Flags() []Flagger {
f.Parallel, f.Parallel,
f.SBOMSources, f.SBOMSources,
f.RekorURL, f.RekorURL,
f.DetectionPriority,
} }
} }
@@ -171,14 +189,15 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) {
} }
return ScanOptions{ return ScanOptions{
Target: target, Target: target,
SkipDirs: f.SkipDirs.Value(), SkipDirs: f.SkipDirs.Value(),
SkipFiles: f.SkipFiles.Value(), SkipFiles: f.SkipFiles.Value(),
OfflineScan: f.OfflineScan.Value(), OfflineScan: f.OfflineScan.Value(),
Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()),
FilePatterns: f.FilePatterns.Value(), FilePatterns: f.FilePatterns.Value(),
Parallel: parallel, Parallel: parallel,
SBOMSources: f.SBOMSources.Value(), SBOMSources: f.SBOMSources.Value(),
RekorURL: f.RekorURL.Value(), RekorURL: f.RekorURL.Value(),
DetectionPriority: ftypes.DetectionPriority(f.DetectionPriority.Value()),
}, nil }, nil
} }

View File

@@ -1,9 +1,100 @@
package types package types
import ( import (
"slices"
"github.com/aquasecurity/trivy/pkg/fanal/types" "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. // ScanTarget holds the attributes for scanning.
type ScanTarget struct { type ScanTarget struct {
Name string // container image name, file path, etc Name string // container image name, file path, etc

View File

@@ -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
}