diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md
index 5a4a9df540b7..2f67f3401211 100644
--- a/docs/docs/configuration/reporting.md
+++ b/docs/docs/configuration/reporting.md
@@ -19,10 +19,153 @@ Trivy supports the following formats:
| Secret | ✓ |
| License | ✓ |
+```bash
+$ trivy image -f table golang:1.22.11-alpine3.20
```
-$ trivy image -f table golang:1.12-alpine
+
+
+Result
+
+```
+...
+
+Report Summary
+
+┌─────────────────────────────────────────────┬──────────┬─────────────────┬─────────┐
+│ Target │ Type │ Vulnerabilities │ Secrets │
+├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
+│ golang:1.22.11-alpine3.20 (alpine 3.20.5) │ alpine │ 6 │ - │
+├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
+│ usr/local/go/bin/go │ gobinary │ 1 │ - │
+├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
+...
+├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
+│ usr/local/go/pkg/tool/linux_amd64/vet │ gobinary │ 1 │ - │
+└─────────────────────────────────────────────┴──────────┴─────────────────┴─────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+
+golang:1.22.11-alpine3.20 (alpine 3.20.5)
+
+Total: 6 (UNKNOWN: 2, LOW: 0, MEDIUM: 2, HIGH: 2, CRITICAL: 0)
+
+┌────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┐
+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
+├────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
+│ libcrypto3 │ CVE-2024-12797 │ HIGH │ fixed │ 3.3.2-r1 │ 3.3.3-r0 │ openssl: RFC7250 handshakes with unauthenticated servers │
+│ │ │ │ │ │ │ don't abort as expected │
+│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-12797 │
+│ ├────────────────┼──────────┤ │ ├───────────────┼─────────────────────────────────────────────────────────────┤
+│ │ CVE-2024-13176 │ MEDIUM │ │ │ 3.3.2-r2 │ openssl: Timing side-channel in ECDSA signature computation │
+│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-13176 │
+├────────────┼────────────────┼──────────┤ │ ├───────────────┼─────────────────────────────────────────────────────────────┤
+│ libssl3 │ CVE-2024-12797 │ HIGH │ │ │ 3.3.3-r0 │ openssl: RFC7250 handshakes with unauthenticated servers │
+│ │ │ │ │ │ │ don't abort as expected │
+│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-12797 │
+│ ├────────────────┼──────────┤ │ ├───────────────┼─────────────────────────────────────────────────────────────┤
+│ │ CVE-2024-13176 │ MEDIUM │ │ │ 3.3.2-r2 │ openssl: Timing side-channel in ECDSA signature computation │
+│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-13176 │
+├────────────┼────────────────┼──────────┤ ├───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
+│ musl │ CVE-2025-26519 │ UNKNOWN │ │ 1.2.5-r0 │ 1.2.5-r1 │ musl libc 0.9.13 through 1.2.5 before 1.2.6 has an │
+│ │ │ │ │ │ │ out-of-bounds write ...... │
+│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-26519 │
+├────────────┤ │ │ │ │ │ │
+│ musl-utils │ │ │ │ │ │ │
+│ │ │ │ │ │ │ │
+│ │ │ │ │ │ │ │
+└────────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘
+
+usr/local/go/bin/go (gobinary)
+
+Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
+
+┌─────────┬────────────────┬──────────┬────────┬───────────────────┬──────────────────────────────┬──────────────────────────────────────────────────────────────┐
+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
+├─────────┼────────────────┼──────────┼────────┼───────────────────┼──────────────────────────────┼──────────────────────────────────────────────────────────────┤
+│ stdlib │ CVE-2025-22866 │ MEDIUM │ fixed │ v1.22.11 │ 1.22.12, 1.23.6, 1.24.0-rc.3 │ crypto/internal/nistec: golang: Timing sidechannel for P-256 │
+│ │ │ │ │ │ │ on ppc64le in crypto/internal/nistec │
+│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-22866 │
+└─────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────┘
+
+...
+```
+
+
+
+#### Table mode
+!!! warning "EXPERIMENTAL"
+ This feature might change without preserving backwards compatibility.
+
+Trivy supports the following modes for `table` format:
+
+| Mode | Enabled by default |
+|:----------------------------:|:-----------------:|
+| [summary](#summary-table) | ✓[^1] |
+| [detailed](#detailed-tables) | ✓ |
+
+You can use `--table-mode` flag to enable/disable table mode(s).
+
+
+##### Summary table
+Summary table contains general information about the scan performed.
+
+Nuances of table contents:
+
+- Table includes columns for enabled [scanners](../references/terminology.md#scanner) only. Use `--scanners` flag to enable/disable scanners.
+- Table includes separate lines for the same targets but different scanners.
+ - `-` means that the scanner didn't scan this target.
+ - `0` means that the scanner scanned this target, but found no security issues.
+
+
+Report Summary
+
+```
+┌───────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐
+│ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │
+├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ test (alpine 3.20.3) │ alpine │ 2 │ - │ - │ - │
+├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ Java │ jar │ 2 │ - │ - │ - │
+├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ app/Dockerfile │ dockerfile │ - │ 2 │ - │ - │
+├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ requirements.txt │ text │ 0 │ - │ - │ - │
+├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ requirements.txt │ text │ - │ - │ 1 │ - │
+├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ OS Packages │ - │ - │ - │ - │ 1 │
+├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ Java │ - │ - │ - │ - │ 0 │
+└───────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘
```
+
+
+##### Detailed tables
+Detailed tables contain information about found security issues for each target with more detailed information (CVE-ID, severity, version, etc.).
+
+
+Detailed tables
+
+```
+
+usr/local/go/bin/go (gobinary)
+
+Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
+
+┌─────────┬────────────────┬──────────┬────────┬───────────────────┬──────────────────────────────┬──────────────────────────────────────────────────────────────┐
+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
+├─────────┼────────────────┼──────────┼────────┼───────────────────┼──────────────────────────────┼──────────────────────────────────────────────────────────────┤
+│ stdlib │ CVE-2025-22866 │ MEDIUM │ fixed │ v1.22.11 │ 1.22.12, 1.23.6, 1.24.0-rc.3 │ crypto/internal/nistec: golang: Timing sidechannel for P-256 │
+│ │ │ │ │ │ │ on ppc64le in crypto/internal/nistec │
+│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-22866 │
+└─────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────┘
+
+```
+
+
#### Show origins of vulnerable dependencies
| Scanner | Supported |
@@ -474,7 +617,7 @@ $ trivy convert --format cyclonedx --output result.cdx result.json
```
!!! note
- Please note that if you want to convert to a format that requires a list of packages,
+ Please note that if you want to convert to a format that requires a list of packages,
such as SBOM, you need to add the `--list-all-pkgs` flag when outputting in JSON.
[Filtering options](./filtering.md) such as `--severity` are also available with `convert`.
@@ -518,3 +661,5 @@ $ trivy convert --format table --severity CRITICAL result.json
[sbt-lockfile]: ../coverage/language/java.md#sbt
[pubspec-lock]: ../coverage/language/dart.md#dart
[cargo-binaries]: ../coverage/language/rust.md#binaries
+
+[^1]: To show summary table in `convert` mode - you need to enable the scanners used during JSON report generation.
\ No newline at end of file
diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md
index 9915d15283a7..2d977a4f1d18 100644
--- a/docs/docs/references/configuration/cli/trivy_config.md
+++ b/docs/docs/references/configuration/cli/trivy_config.md
@@ -51,6 +51,7 @@ trivy config [flags] DIR
--skip-check-update skip fetching rego check updates
--skip-dirs strings specify the directories or glob patterns to skip
--skip-files strings specify the files or glob patterns to skip
+ --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed])
-t, --template string output template
--tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules
--tf-vars strings specify paths to override the Terraform tfvars files
diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md
index d76d303e3b03..f396e92a8fdd 100644
--- a/docs/docs/references/configuration/cli/trivy_convert.md
+++ b/docs/docs/references/configuration/cli/trivy_convert.md
@@ -30,8 +30,10 @@ trivy convert [flags] RESULT_JSON
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
--report string specify a report format for the output (all,summary) (default "all")
+ --scanners strings List of scanners included when generating the json report. Used only for rendering the summary table. (vuln,misconfig,secret,license)
-s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL])
--show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities
+ --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed])
-t, --template string output template
```
diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md
index 154b7ddea2ea..9e99c441f268 100644
--- a/docs/docs/references/configuration/cli/trivy_filesystem.md
+++ b/docs/docs/references/configuration/cli/trivy_filesystem.md
@@ -92,6 +92,7 @@ trivy filesystem [flags] PATH
--skip-files strings specify the files or glob patterns to skip
--skip-java-db-update skip updating Java index database
--skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update
+ --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed])
-t, --template string output template
--tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules
--tf-vars strings specify paths to override the Terraform tfvars files
diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md
index 6cf60b526208..4e90a2e19471 100644
--- a/docs/docs/references/configuration/cli/trivy_image.md
+++ b/docs/docs/references/configuration/cli/trivy_image.md
@@ -114,6 +114,7 @@ trivy image [flags] IMAGE_NAME
--skip-files strings specify the files or glob patterns to skip
--skip-java-db-update skip updating Java index database
--skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update
+ --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed])
-t, --template string output template
--tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules
--token string for authentication in client/server mode
diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md
index 98e54bb98c72..4265b790cd74 100644
--- a/docs/docs/references/configuration/cli/trivy_repository.md
+++ b/docs/docs/references/configuration/cli/trivy_repository.md
@@ -90,6 +90,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL)
--skip-files strings specify the files or glob patterns to skip
--skip-java-db-update skip updating Java index database
--skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update
+ --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed])
--tag string pass the tag name to be scanned
-t, --template string output template
--tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules
diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md
index 2ab6043da4d1..4607a25f56a7 100644
--- a/docs/docs/references/configuration/cli/trivy_rootfs.md
+++ b/docs/docs/references/configuration/cli/trivy_rootfs.md
@@ -93,6 +93,7 @@ trivy rootfs [flags] ROOTDIR
--skip-files strings specify the files or glob patterns to skip
--skip-java-db-update skip updating Java index database
--skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update
+ --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed])
-t, --template string output template
--tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules
--tf-vars strings specify paths to override the Terraform tfvars files
diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md
index 454f8f5d8cfe..42df969bfefd 100644
--- a/docs/docs/references/configuration/cli/trivy_sbom.md
+++ b/docs/docs/references/configuration/cli/trivy_sbom.md
@@ -68,6 +68,7 @@ trivy sbom [flags] SBOM_PATH
--skip-files strings specify the files or glob patterns to skip
--skip-java-db-update skip updating Java index database
--skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update
+ --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed])
-t, --template string output template
--token string for authentication in client/server mode
--token-header string specify a header name for token in client/server mode (default "Trivy-Token")
diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md
index 2aef1a8864ac..49010ac26e85 100644
--- a/docs/docs/references/configuration/cli/trivy_vm.md
+++ b/docs/docs/references/configuration/cli/trivy_vm.md
@@ -81,6 +81,7 @@ trivy vm [flags] VM_IMAGE
--skip-files strings specify the files or glob patterns to skip
--skip-java-db-update skip updating Java index database
--skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update
+ --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed])
-t, --template string output template
--tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules
--token string for authentication in client/server mode
diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md
index b246a687da11..6a2cd1e47dea 100644
--- a/docs/docs/references/configuration/config-file.md
+++ b/docs/docs/references/configuration/config-file.md
@@ -553,6 +553,11 @@ severity:
- HIGH
- CRITICAL
+# Same as '--table-mode'
+table-mode:
+ - summary
+ - detailed
+
# Same as '--template'
template: ""
diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden
index 44cf87a6c095..ad5fc250401e 100644
--- a/integration/testdata/license-cyclonedx.json.golden
+++ b/integration/testdata/license-cyclonedx.json.golden
@@ -16,10 +16,6 @@
}
},
"Results": [
- {
- "Target": "OS Packages",
- "Class": "license"
- },
{
"Target": "Java",
"Class": "license",
@@ -30,6 +26,7 @@
"PkgName": "org.eclipse.sisu:org.eclipse.sisu.plexus",
"FilePath": "",
"Name": "EPL-1.0",
+ "Text": "",
"Confidence": 1,
"Link": ""
},
@@ -39,6 +36,7 @@
"PkgName": "org.ow2.asm:asm",
"FilePath": "",
"Name": "BSD-3-Clause",
+ "Text": "",
"Confidence": 1,
"Link": ""
},
@@ -48,18 +46,11 @@
"PkgName": "org.slf4j:slf4j-api",
"FilePath": "",
"Name": "MIT License",
+ "Text": "",
"Confidence": 1,
"Link": ""
}
]
- },
- {
- "Target": "pom.xml",
- "Class": "license"
- },
- {
- "Target": "Loose File License(s)",
- "Class": "license-file"
}
]
}
diff --git a/pkg/commands/app.go b/pkg/commands/app.go
index 7bedce99ff65..86e3428e5925 100644
--- a/pkg/commands/app.go
+++ b/pkg/commands/app.go
@@ -520,6 +520,13 @@ func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
ReportFlagGroup: flag.NewReportFlagGroup(),
}
+ // To display the summary table, we need to enable scanners (to build columns).
+ // We can't get scanner information from the report (we don't include empty licenses and secrets in the report).
+ // So we need to ask the user to configure scanners (if needed).
+ convertFlags.ScanFlagGroup.Scanners = flag.ScannersFlag.Clone()
+ convertFlags.ScanFlagGroup.Scanners.Default = nil // disable default scanners
+ convertFlags.ScanFlagGroup.Scanners.Usage = "List of scanners included when generating the json report. Used only for rendering the summary table."
+
cmd := &cobra.Command{
Use: "convert [flags] RESULT_JSON",
Aliases: []string{"conv"},
@@ -977,6 +984,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
}
reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand.
reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol'
+ reportFlagGroup.TableMode = nil // disable '--table-mode'
formatFlag := flag.FormatFlag.Clone()
formatFlag.Values = xstrings.ToStringSlice([]types.Format{
diff --git a/pkg/commands/convert/run.go b/pkg/commands/convert/run.go
index 584bc6f6bddc..928b6a65f40d 100644
--- a/pkg/commands/convert/run.go
+++ b/pkg/commands/convert/run.go
@@ -4,7 +4,9 @@ import (
"context"
"encoding/json"
"os"
+ "slices"
+ "github.com/samber/lo"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/commands/operation"
@@ -18,6 +20,7 @@ import (
)
func Run(ctx context.Context, opts flag.Options) (err error) {
+ logger := log.WithPrefix("convert")
ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
defer cancel()
@@ -42,7 +45,14 @@ func Run(ctx context.Context, opts flag.Options) (err error) {
return xerrors.Errorf("unable to filter results: %w", err)
}
- log.Debug("Writing report to output...")
+ if len(opts.Scanners) == 0 && opts.Format == types.FormatTable && slices.Contains(opts.TableModes, types.Summary) {
+ logger.Info("To display the summary table, enable the scanners used during JSON report generation.")
+ opts.TableModes = lo.Filter(opts.TableModes, func(mode types.TableMode, _ int) bool {
+ return mode != types.Summary
+ })
+ }
+
+ logger.Debug("Writing report to output...")
if err = report.Write(ctx, r, opts); err != nil {
return xerrors.Errorf("unable to write results: %w", err)
}
diff --git a/pkg/flag/options.go b/pkg/flag/options.go
index 0b7772a091f3..cd8ffc8f7777 100644
--- a/pkg/flag/options.go
+++ b/pkg/flag/options.go
@@ -460,6 +460,7 @@ func (o *Options) ScanOpts() types.ScanOptions {
ImageConfigScanners: o.ImageConfigScanners, // this is valid only for 'image' subcommand
ScanRemovedPackages: o.ScanRemovedPkgs, // this is valid only for 'image' subcommand
LicenseCategories: o.LicenseCategories,
+ LicenseFull: o.LicenseFull,
FilePatterns: o.FilePatterns,
IncludeDevDeps: o.IncludeDevDeps,
Distro: o.Distro,
diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go
index d69443e89547..90ac077abcee 100644
--- a/pkg/flag/report_flags.go
+++ b/pkg/flag/report_flags.go
@@ -109,6 +109,13 @@ var (
ConfigName: "scan.show-suppressed",
Usage: "[EXPERIMENTAL] show suppressed vulnerabilities",
}
+ TableModeFlag = Flag[[]string]{
+ Name: "table-mode",
+ ConfigName: "table-mode",
+ Default: xstrings.ToStringSlice(types.SupportedTableModes),
+ Values: xstrings.ToStringSlice(types.SupportedTableModes),
+ Usage: "[EXPERIMENTAL] tables that will be displayed in 'table' format",
+ }
)
// ReportFlagGroup composes common printer flag structs
@@ -128,6 +135,7 @@ type ReportFlagGroup struct {
Severity *Flag[[]string]
Compliance *Flag[string]
ShowSuppressed *Flag[bool]
+ TableMode *Flag[[]string]
}
type ReportOptions struct {
@@ -145,6 +153,7 @@ type ReportOptions struct {
Severities []dbTypes.Severity
Compliance spec.ComplianceSpec
ShowSuppressed bool
+ TableModes []types.TableMode
}
func NewReportFlagGroup() *ReportFlagGroup {
@@ -163,6 +172,7 @@ func NewReportFlagGroup() *ReportFlagGroup {
Severity: SeverityFlag.Clone(),
Compliance: ComplianceFlag.Clone(),
ShowSuppressed: ShowSuppressedFlag.Clone(),
+ TableMode: TableModeFlag.Clone(),
}
}
@@ -186,6 +196,7 @@ func (f *ReportFlagGroup) Flags() []Flagger {
f.Severity,
f.Compliance,
f.ShowSuppressed,
+ f.TableMode,
}
}
@@ -198,6 +209,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) {
template := f.Template.Value()
dependencyTree := f.DependencyTree.Value()
listAllPkgs := f.ListAllPkgs.Value()
+ tableModes := f.TableMode.Value()
if template != "" {
if format == "" {
@@ -227,6 +239,11 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) {
}
}
+ // "--table-mode" option is available only with "--format table".
+ if viper.IsSet(TableModeFlag.ConfigName) && format != types.FormatTable {
+ return ReportOptions{}, xerrors.New(`"--table-mode" can be used only with "--format table".`)
+ }
+
cs, err := loadComplianceTypes(f.Compliance.Value())
if err != nil {
return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err)
@@ -259,6 +276,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) {
Severities: toSeverity(f.Severity.Value()),
Compliance: cs,
ShowSuppressed: f.ShowSuppressed.Value(),
+ TableModes: xstrings.ToTSlice[types.TableMode](tableModes),
}, nil
}
diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go
index ab4baa53fbff..7999ab6c84bd 100644
--- a/pkg/flag/report_flags_test.go
+++ b/pkg/flag/report_flags_test.go
@@ -13,6 +13,7 @@ import (
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
+ xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
)
func TestReportFlagGroup_ToOptions(t *testing.T) {
@@ -32,11 +33,13 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
compliance string
debug bool
pkgTypes string
+ tableModes []string
}
tests := []struct {
name string
fields fields
want flag.ReportOptions
+ wantErr string
wantLogs []string
}{
{
@@ -160,6 +163,14 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
},
},
+ {
+ name: "invalid option combination: --table-modes with --format json",
+ fields: fields{
+ format: "json",
+ tableModes: xstrings.ToStringSlice(types.SupportedTableModes),
+ },
+ wantErr: `"--table-mode" can be used only with "--format table".`,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -184,6 +195,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
setValue(flag.OutputPluginArgFlag.ConfigName, tt.fields.outputPluginArgs)
setValue(flag.SeverityFlag.ConfigName, tt.fields.severities)
setValue(flag.ComplianceFlag.ConfigName, tt.fields.compliance)
+ setSliceValue(flag.TableModeFlag.ConfigName, tt.fields.tableModes)
// Assert options
f := &flag.ReportFlagGroup{
@@ -199,10 +211,15 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
OutputPluginArg: flag.OutputPluginArgFlag.Clone(),
Severity: flag.SeverityFlag.Clone(),
Compliance: flag.ComplianceFlag.Clone(),
+ TableMode: flag.TableModeFlag.Clone(),
}
got, err := f.ToOptions()
- require.NoError(t, err)
+ if tt.wantErr != "" {
+ require.Contains(t, err.Error(), tt.wantErr)
+ return
+ }
+
assert.EqualExportedValuesf(t, tt.want, got, "ToOptions()")
// Assert log messages
diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go
new file mode 100644
index 000000000000..c726605ae78e
--- /dev/null
+++ b/pkg/report/table/summary.go
@@ -0,0 +1,305 @@
+package table
+
+import (
+ "bytes"
+ "fmt"
+ "slices"
+ "sort"
+
+ "github.com/fatih/color"
+ "github.com/samber/lo"
+
+ "github.com/aquasecurity/table"
+ "github.com/aquasecurity/tml"
+ ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
+ "github.com/aquasecurity/trivy/pkg/log"
+ "github.com/aquasecurity/trivy/pkg/scanner/langpkg"
+ "github.com/aquasecurity/trivy/pkg/types"
+)
+
+type Scanner interface {
+ Header() string
+ Alignment() table.Alignment
+
+ // Count returns the number of findings, but -1 if the scanner is not applicable
+ Count(result types.Result) int
+
+ String() string // Required to show correct logs
+}
+
+func NewScanner(scanner types.Scanner) Scanner {
+ switch scanner {
+ case types.VulnerabilityScanner:
+ return VulnerabilityScanner{}
+ case types.MisconfigScanner:
+ return MisconfigScanner{}
+ case types.SecretScanner:
+ return SecretScanner{}
+ case types.LicenseScanner:
+ return LicenseScanner{}
+ }
+ return nil
+}
+
+type scannerAlignment struct{}
+
+func (s scannerAlignment) Alignment() table.Alignment {
+ return table.AlignCenter
+}
+
+type VulnerabilityScanner struct{ scannerAlignment }
+
+func (s VulnerabilityScanner) Header() string {
+ return "Vulnerabilities"
+}
+
+func (s VulnerabilityScanner) Count(result types.Result) int {
+ if result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg {
+ return len(result.Vulnerabilities)
+ }
+ return -1
+}
+
+func (s VulnerabilityScanner) String() string {
+ return string(types.VulnerabilityScanner)
+}
+
+type MisconfigScanner struct{ scannerAlignment }
+
+func (s MisconfigScanner) Header() string {
+ return "Misconfigurations"
+}
+
+func (s MisconfigScanner) Count(result types.Result) int {
+ if result.Class == types.ClassConfig {
+ return len(result.Misconfigurations)
+ }
+ return -1
+}
+
+func (s MisconfigScanner) String() string {
+ return string(types.MisconfigScanner)
+}
+
+type SecretScanner struct{ scannerAlignment }
+
+func (s SecretScanner) Header() string {
+ return "Secrets"
+}
+
+func (s SecretScanner) Count(result types.Result) int {
+ if result.Class == types.ClassSecret {
+ return len(result.Secrets)
+ }
+ return -1
+}
+
+func (s SecretScanner) String() string {
+ return string(types.SecretScanner)
+}
+
+type LicenseScanner struct{ scannerAlignment }
+
+func (s LicenseScanner) Header() string {
+ return "Licenses"
+}
+
+func (s LicenseScanner) Count(result types.Result) int {
+ if result.Class == types.ClassLicense || result.Class == types.ClassLicenseFile {
+ return len(result.Licenses)
+ }
+ return -1
+}
+
+func (s LicenseScanner) String() string {
+ return string(types.LicenseScanner)
+}
+
+type summaryRenderer struct {
+ w *bytes.Buffer
+ isTerminal bool
+ scanners []Scanner
+ logger *log.Logger
+}
+
+func NewSummaryRenderer(buf *bytes.Buffer, isTerminal bool, scanners types.Scanners) *summaryRenderer {
+ if !isTerminal {
+ tml.DisableFormatting()
+ }
+
+ var ss []Scanner
+ for _, scanner := range scanners {
+ s := NewScanner(scanner)
+ if lo.IsNil(s) {
+ continue
+ }
+ ss = append(ss, s)
+ }
+
+ return &summaryRenderer{
+ w: buf,
+ isTerminal: isTerminal,
+ scanners: ss,
+ logger: log.WithPrefix("report"),
+ }
+}
+
+func (r *summaryRenderer) Render(report types.Report) {
+ if len(r.scanners) == 0 {
+ r.logger.Warn("No enabled scanners found. Summary table will not be displayed.")
+ return
+ }
+
+ r.printf("\nReport Summary\n\n")
+
+ t := newTableWriter(r.w, r.isTerminal)
+ t.SetAutoMerge(false)
+ t.SetColumnMaxWidth(80)
+
+ headers := []string{
+ "Target",
+ "Type",
+ }
+ alignments := []table.Alignment{
+ table.AlignLeft,
+ table.AlignCenter,
+ }
+ for _, scanner := range r.scanners {
+ headers = append(headers, scanner.Header())
+ alignments = append(alignments, scanner.Alignment())
+ }
+ t.SetHeaders(headers...)
+ t.SetAlignment(alignments...)
+
+ for _, result := range splitAggregatedPackages(report.Results) {
+ resultType := string(result.Type)
+ if result.Class == types.ClassSecret {
+ resultType = "text"
+ } else if result.Class == types.ClassLicense || result.Class == types.ClassLicenseFile {
+ resultType = "-"
+ }
+ rows := []string{
+ result.Target,
+ resultType,
+ }
+ for _, scanner := range r.scanners {
+ rows = append(rows, r.colorizeCount(scanner.Count(result)))
+ }
+ t.AddRows(rows)
+ }
+
+ if len(report.Results) == 0 {
+ r.showEmptyResultsWarning()
+ alignments[0] = table.AlignCenter
+ t.SetAlignment(alignments...)
+ t.AddRows(slices.Repeat([]string{"-"}, len(r.scanners)+2))
+ }
+
+ t.Render()
+
+ // Show legend
+ r.printf("Legend:\n" +
+ "- '-': Not scanned\n" +
+ "- '0': Clean (no security findings detected)\n\n")
+
+ return
+}
+
+func (r *summaryRenderer) printf(format string, args ...any) {
+ // nolint
+ _ = tml.Fprintf(r.w, format, args...)
+}
+
+// showEmptyResultsWarning shows WARN why the results array is empty based on the enabled scanners.
+// We need to separate the vuln/misconfig and secret/license scanners,
+// because the results array contains results without findings for vulns/misconfig only.
+func (r *summaryRenderer) showEmptyResultsWarning() {
+ resultByFiles := []Scanner{
+ NewScanner(types.VulnerabilityScanner),
+ NewScanner(types.MisconfigScanner),
+ }
+ resultByFindings := []Scanner{
+ NewScanner(types.SecretScanner),
+ NewScanner(types.LicenseScanner),
+ }
+
+ if scanners := lo.Intersect(resultByFiles, r.scanners); len(scanners) > 0 {
+ r.logger.Warn("Supported files for scanner(s) not found.", log.Any("scanners", scanners))
+ }
+ if scanners := lo.Intersect(resultByFindings, r.scanners); len(scanners) > 0 {
+ r.logger.Info("No issues detected with scanner(s).", log.Any("scanners", scanners))
+ }
+}
+
+// splitAggregatedPackages splits aggregated packages into different results with path as target.
+// Other results will be returned as is.
+func splitAggregatedPackages(results types.Results) types.Results {
+ var newResults types.Results
+
+ for _, result := range results {
+ if !slices.Contains(ftypes.AggregatingTypes, result.Type) &&
+ // License results from applications don't have `Type`.
+ (result.Class != types.ClassLicense || !slices.Contains(lo.Values(langpkg.PkgTargets), result.Target)) {
+ newResults = append(newResults, result)
+ continue
+ }
+
+ newResults = append(newResults, splitAggregatedVulns(result)...)
+ newResults = append(newResults, splitAggregatedLicenses(result)...)
+
+ }
+ return newResults
+}
+
+func splitAggregatedVulns(result types.Result) types.Results {
+ // Save packages to display them in the table even if no vulnerabilities were found
+ resultMap := lo.SliceToMap(result.Packages, func(pkg ftypes.Package) (string, *types.Result) {
+ filePath := rootJarFromPath(pkg.FilePath)
+ return filePath, &types.Result{
+ Target: lo.Ternary(filePath != "", filePath, result.Target),
+ Class: result.Class,
+ Type: result.Type,
+ }
+ })
+ for _, vuln := range result.Vulnerabilities {
+ pkgPath := rootJarFromPath(vuln.PkgPath)
+ resultMap[pkgPath].Vulnerabilities = append(resultMap[pkgPath].Vulnerabilities, vuln)
+ }
+ newResults := lo.Values(resultMap)
+ sort.Slice(newResults, func(i, j int) bool {
+ return newResults[i].Target < newResults[j].Target
+ })
+ return lo.FromSlicePtr(newResults)
+}
+
+func splitAggregatedLicenses(result types.Result) types.Results {
+ var newResults types.Results
+
+ licenses := make(map[string][]types.DetectedLicense)
+ for _, license := range result.Licenses {
+ licenses[license.FilePath] = append(licenses[license.FilePath], license)
+ }
+ for filePath, l := range licenses {
+ newResult := result
+ newResult.Target = lo.Ternary(filePath != "", filePath, result.Target)
+ newResult.Licenses = l
+
+ newResults = append(newResults, newResult)
+ }
+
+ sort.Slice(newResults, func(i, j int) bool {
+ return newResults[i].Target < newResults[j].Target
+ })
+ return newResults
+}
+
+func (r *summaryRenderer) colorizeCount(count int) string {
+ if count < 0 {
+ return "-"
+ }
+ sprintf := fmt.Sprintf
+ if count != 0 && r.isTerminal {
+ sprintf = color.New(color.FgHiRed).SprintfFunc()
+ }
+ return sprintf("%d", count)
+}
diff --git a/pkg/report/table/summary_test.go b/pkg/report/table/summary_test.go
new file mode 100644
index 000000000000..341cde115240
--- /dev/null
+++ b/pkg/report/table/summary_test.go
@@ -0,0 +1,342 @@
+package table_test
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
+ "github.com/aquasecurity/trivy/pkg/report/table"
+ "github.com/aquasecurity/trivy/pkg/types"
+)
+
+var (
+ osVuln = types.Result{
+ Target: "test (alpine 3.20.3)",
+ Class: types.ClassOSPkg,
+ Type: ftypes.Alpine,
+ Vulnerabilities: []types.DetectedVulnerability{
+ {
+ VulnerabilityID: "CVE-2024-9143",
+ PkgName: "libcrypto3",
+ },
+ {
+ VulnerabilityID: "CVE-2024-9143",
+ PkgName: "libssl3",
+ },
+ },
+ }
+ jarVulns = types.Result{
+ Target: "Java",
+ Class: types.ClassLangPkg,
+ Type: ftypes.Jar,
+ Packages: []ftypes.Package{
+ {
+ Name: "com.fasterxml.jackson.core:jackson-databind",
+ FilePath: "app/jackson-databind-2.13.4.1.jar",
+ },
+ {
+ Name: "com.google.code.gson:gson",
+ FilePath: "app/gson-2.11.0.jar",
+ },
+ {
+ Name: "org.apache.logging.log4j:log4j-core",
+ FilePath: "app/jackson-databind-2.13.4.1.jar/nested/app2/log4j-core-2.17.0.jar",
+ },
+ },
+ Vulnerabilities: []types.DetectedVulnerability{
+ {
+ VulnerabilityID: "CVE-2022-42003",
+ PkgName: "com.fasterxml.jackson.core:jackson-databind",
+ PkgPath: "app/jackson-databind-2.13.4.1.jar",
+ },
+ {
+ VulnerabilityID: "CVE-2021-44832",
+ PkgName: "org.apache.logging.log4j:log4j-core",
+ PkgPath: "app/jackson-databind-2.13.4.1.jar/nested/app2/log4j-core-2.17.0.jar",
+ },
+ },
+ }
+
+ npmVulns = types.Result{
+ Target: "Node.js",
+ Class: types.ClassLangPkg,
+ Type: ftypes.NodePkg,
+ Packages: []ftypes.Package{
+ {
+ Name: "loader-utils@2.0.2",
+ FilePath: "loader-utils/package.json",
+ },
+ {
+ Name: "nanoid@3.1.25",
+ FilePath: "nanoid/package.json",
+ },
+ },
+ Vulnerabilities: []types.DetectedVulnerability{
+ {
+ VulnerabilityID: "CVE-2022-37601",
+ PkgName: "loader-utils",
+ PkgPath: "loader-utils/package.json",
+ },
+ {
+ VulnerabilityID: "CVE-2022-37599",
+ PkgName: "loader-utils",
+ PkgPath: "loader-utils/package.json",
+ },
+ {
+ VulnerabilityID: "CVE-2021-23566",
+ PkgName: "nanoid",
+ PkgPath: "nanoid/package.json",
+ },
+ {
+ VulnerabilityID: "CVE-2024-55565",
+ PkgName: "nanoid",
+ PkgPath: "nanoid/package.json",
+ },
+ },
+ }
+
+ noVuln = types.Result{
+ Target: "requirements.txt",
+ Class: types.ClassLangPkg,
+ Type: ftypes.Pip,
+ }
+
+ dockerfileMisconfig = types.Result{
+ Target: "app/Dockerfile",
+ Class: types.ClassConfig,
+ Type: ftypes.Dockerfile,
+ Misconfigurations: []types.DetectedMisconfiguration{
+ {
+ ID: "DS002",
+ },
+ {
+ ID: "DS026",
+ },
+ },
+ }
+ secret = types.Result{
+ Target: "requirements.txt",
+ Class: types.ClassSecret,
+ Secrets: []types.DetectedSecret{
+ {
+ RuleID: "aws-access-key-id",
+ },
+ },
+ }
+ osLicense = types.Result{
+ Target: "OS Packages",
+ Class: types.ClassLicense,
+ Licenses: []types.DetectedLicense{
+ {
+ Name: "GPL-2.0-only",
+ },
+ },
+ }
+
+ jarLicense = types.Result{
+ Target: "Java",
+ Class: types.ClassLicense,
+ }
+
+ npmLicenses = types.Result{
+ Target: "Node.js",
+ Class: types.ClassLicense,
+ Licenses: []types.DetectedLicense{
+ {
+ Name: "MIT",
+ FilePath: "loader-utils/package.json",
+ Category: "notice",
+ },
+ {
+ Name: "MIT",
+ FilePath: "nanoid/package.json",
+ Category: "notice",
+ },
+ },
+ }
+
+ fileLicense = types.Result{
+ Target: "Loose File License(s)",
+ Class: types.ClassLicenseFile,
+ Licenses: []types.DetectedLicense{
+ {
+ FilePath: "LICENSE",
+ },
+ },
+ }
+)
+
+func Test_renderSummary(t *testing.T) {
+ tests := []struct {
+ name string
+ scanners types.Scanners
+ report types.Report
+ want string
+ }{
+ {
+ name: "happy path all scanners",
+ scanners: []types.Scanner{
+ types.VulnerabilityScanner,
+ types.MisconfigScanner,
+ types.SecretScanner,
+ types.LicenseScanner,
+ },
+ report: types.Report{
+ Results: []types.Result{
+ osVuln,
+ jarVulns,
+ npmVulns,
+ dockerfileMisconfig,
+ secret,
+ osLicense,
+ jarLicense,
+ npmLicenses,
+ fileLicense,
+ },
+ },
+ want: `
+Report Summary
+
+┌───────────────────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐
+│ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ test (alpine 3.20.3) │ alpine │ 2 │ - │ - │ - │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ app/gson-2.11.0.jar │ jar │ 0 │ - │ - │ - │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ app/jackson-databind-2.13.4.1.jar │ jar │ 2 │ - │ - │ - │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ loader-utils/package.json │ node-pkg │ 2 │ - │ - │ - │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ nanoid/package.json │ node-pkg │ 2 │ - │ - │ - │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ app/Dockerfile │ dockerfile │ - │ 2 │ - │ - │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ requirements.txt │ text │ - │ - │ 1 │ - │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ OS Packages │ - │ - │ - │ - │ 1 │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ loader-utils/package.json │ - │ - │ - │ - │ 1 │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ nanoid/package.json │ - │ - │ - │ - │ 1 │
+├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
+│ Loose File License(s) │ - │ - │ - │ - │ 1 │
+└───────────────────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+`,
+ },
+ {
+ name: "happy path vuln scanner only",
+ scanners: []types.Scanner{
+ types.VulnerabilityScanner,
+ },
+ report: types.Report{
+ Results: []types.Result{
+ osVuln,
+ jarVulns,
+ },
+ },
+ want: `
+Report Summary
+
+┌───────────────────────────────────┬────────┬─────────────────┐
+│ Target │ Type │ Vulnerabilities │
+├───────────────────────────────────┼────────┼─────────────────┤
+│ test (alpine 3.20.3) │ alpine │ 2 │
+├───────────────────────────────────┼────────┼─────────────────┤
+│ app/gson-2.11.0.jar │ jar │ 0 │
+├───────────────────────────────────┼────────┼─────────────────┤
+│ app/jackson-databind-2.13.4.1.jar │ jar │ 2 │
+└───────────────────────────────────┴────────┴─────────────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+`,
+ },
+ {
+ name: "happy path no vulns + secret",
+ scanners: []types.Scanner{
+ types.VulnerabilityScanner,
+ types.SecretScanner,
+ },
+ report: types.Report{
+ Results: []types.Result{
+ noVuln,
+ secret,
+ },
+ },
+ want: `
+Report Summary
+
+┌──────────────────┬──────┬─────────────────┬─────────┐
+│ Target │ Type │ Vulnerabilities │ Secrets │
+├──────────────────┼──────┼─────────────────┼─────────┤
+│ requirements.txt │ pip │ 0 │ - │
+├──────────────────┼──────┼─────────────────┼─────────┤
+│ requirements.txt │ text │ - │ 1 │
+└──────────────────┴──────┴─────────────────┴─────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+`,
+ },
+ {
+ name: "happy path without supported files for vulns + secret",
+ scanners: []types.Scanner{
+ types.VulnerabilityScanner,
+ types.SecretScanner,
+ },
+ want: `
+Report Summary
+
+┌────────┬──────┬─────────────────┬─────────┐
+│ Target │ Type │ Vulnerabilities │ Secrets │
+├────────┼──────┼─────────────────┼─────────┤
+│ - │ - │ - │ - │
+└────────┴──────┴─────────────────┴─────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+`,
+ },
+ {
+ name: "happy path without supported files for all scanners",
+ scanners: []types.Scanner{
+ types.VulnerabilityScanner,
+ types.SecretScanner,
+ types.MisconfigScanner,
+ types.LicenseScanner,
+ },
+ want: `
+Report Summary
+
+┌────────┬──────┬─────────────────┬─────────┬───────────────────┬──────────┐
+│ Target │ Type │ Vulnerabilities │ Secrets │ Misconfigurations │ Licenses │
+├────────┼──────┼─────────────────┼─────────┼───────────────────┼──────────┤
+│ - │ - │ - │ - │ - │ - │
+└────────┴──────┴─────────────────┴─────────┴───────────────────┴──────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+`,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ buf := bytes.NewBuffer([]byte{})
+ r := table.NewSummaryRenderer(buf, false, tt.scanners)
+ r.Render(tt.report)
+ require.Equal(t, tt.want, buf.String())
+ })
+ }
+}
diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go
index 751f86f6cccc..8f0d0902ff46 100644
--- a/pkg/report/table/table.go
+++ b/pkg/report/table/table.go
@@ -34,6 +34,7 @@ type Writer struct {
// Use one buffer for all renderers
buf *bytes.Buffer
+ summaryRenderer *summaryRenderer
vulnerabilityRenderer Renderer
misconfigRenderer Renderer
secretRenderer Renderer
@@ -44,6 +45,7 @@ type Writer struct {
}
type Options struct {
+ Scanners types.Scanners
Severities []dbTypes.Severity
Output io.Writer
@@ -53,6 +55,9 @@ type Options struct {
// Show suppressed findings
ShowSuppressed bool
+ // Show/hide summary/detailed tables
+ TableModes []types.TableMode
+
// For misconfigurations
IncludeNonFailures bool
Trace bool
@@ -65,13 +70,16 @@ type Options struct {
func NewWriter(options Options) *Writer {
buf := bytes.NewBuffer([]byte{})
+ isTerminal := IsOutputToTerminal(options.Output)
return &Writer{
- buf: buf,
- vulnerabilityRenderer: NewVulnerabilityRenderer(buf, IsOutputToTerminal(options.Output), options.Tree, options.ShowSuppressed, options.Severities),
- misconfigRenderer: NewMisconfigRenderer(buf, options.Severities, options.Trace, options.IncludeNonFailures, IsOutputToTerminal(options.Output), options.RenderCause),
- secretRenderer: NewSecretRenderer(buf, IsOutputToTerminal(options.Output), options.Severities),
- pkgLicenseRenderer: NewPkgLicenseRenderer(buf, IsOutputToTerminal(options.Output), options.Severities),
- fileLicenseRenderer: NewFileLicenseRenderer(buf, IsOutputToTerminal(options.Output), options.Severities),
+ buf: buf,
+
+ summaryRenderer: NewSummaryRenderer(buf, isTerminal, options.Scanners),
+ vulnerabilityRenderer: NewVulnerabilityRenderer(buf, isTerminal, options.Tree, options.ShowSuppressed, options.Severities),
+ misconfigRenderer: NewMisconfigRenderer(buf, options.Severities, options.Trace, options.IncludeNonFailures, isTerminal, options.RenderCause),
+ secretRenderer: NewSecretRenderer(buf, isTerminal, options.Severities),
+ pkgLicenseRenderer: NewPkgLicenseRenderer(buf, isTerminal, options.Severities),
+ fileLicenseRenderer: NewFileLicenseRenderer(buf, isTerminal, options.Severities),
options: options,
}
}
@@ -82,12 +90,18 @@ type Renderer interface {
// Write writes the result on standard output
func (tw *Writer) Write(_ context.Context, report types.Report) error {
- for _, result := range report.Results {
- // Not display a table of custom resources
- if result.Class == types.ClassCustom {
- continue
+ if slices.Contains(tw.options.TableModes, types.Summary) {
+ tw.summaryRenderer.Render(report)
+ }
+
+ if slices.Contains(tw.options.TableModes, types.Detailed) {
+ for _, result := range report.Results {
+ // Not display a table of custom resources
+ if result.Class == types.ClassCustom {
+ continue
+ }
+ tw.render(result)
}
- tw.render(result)
}
tw.flush()
diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go
index 98456fb95930..42934ccdcb58 100644
--- a/pkg/report/table/table_test.go
+++ b/pkg/report/table/table_test.go
@@ -5,7 +5,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
@@ -16,16 +15,30 @@ import (
func TestWriter_Write(t *testing.T) {
testCases := []struct {
name string
+ scanners types.Scanners
+ tableModes []types.TableMode
results types.Results
- expectedOutput string
+ wantOutput string
includeNonFailures bool
}{
{
name: "vulnerability and custom resource",
+ scanners: types.Scanners{
+ types.VulnerabilityScanner,
+ },
+ tableModes: types.SupportedTableModes,
results: types.Results{
{
Target: "test",
+ Type: ftypes.Jar,
Class: types.ClassLangPkg,
+ Packages: []ftypes.Package{
+ {
+ Name: "foo",
+ Version: "1.2.3",
+ FilePath: "test.jar",
+ },
+ },
Vulnerabilities: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-0001",
@@ -33,6 +46,7 @@ func TestWriter_Write(t *testing.T) {
InstalledVersion: "1.2.3",
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001",
Status: dbTypes.StatusWillNotFix,
+ PkgPath: "test.jar",
Vulnerability: dbTypes.Vulnerability{
Title: "foobar",
Description: "baz",
@@ -48,28 +62,172 @@ func TestWriter_Write(t *testing.T) {
},
},
},
- expectedOutput: `
-test ()
-=======
+ wantOutput: `
+Report Summary
+
+┌──────────┬──────┬─────────────────┐
+│ Target │ Type │ Vulnerabilities │
+├──────────┼──────┼─────────────────┤
+│ test.jar │ jar │ 1 │
+└──────────┴──────┴─────────────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+
+test (jar)
+==========
Total: 1 (MEDIUM: 0, HIGH: 1)
-┌─────────┬───────────────┬──────────┬──────────────┬───────────────────┬───────────────┬───────────────────────────────────────────┐
-│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
-├─────────┼───────────────┼──────────┼──────────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤
-│ foo │ CVE-2020-0001 │ HIGH │ will_not_fix │ 1.2.3 │ │ foobar │
-│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-0001 │
-└─────────┴───────────────┴──────────┴──────────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘
+┌────────────────┬───────────────┬──────────┬──────────────┬───────────────────┬───────────────┬───────────────────────────────────────────┐
+│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
+├────────────────┼───────────────┼──────────┼──────────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤
+│ foo (test.jar) │ CVE-2020-0001 │ HIGH │ will_not_fix │ 1.2.3 │ │ foobar │
+│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-0001 │
+└────────────────┴───────────────┴──────────┴──────────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘
`,
},
{
name: "no vulns",
+ scanners: types.Scanners{
+ types.VulnerabilityScanner,
+ },
+ tableModes: types.SupportedTableModes,
+ results: types.Results{
+ {
+ Target: "test",
+ Class: types.ClassLangPkg,
+ Type: ftypes.Jar,
+ Packages: []ftypes.Package{
+ {
+ Name: "foo",
+ Version: "1.2.3",
+ FilePath: "test.jar",
+ },
+ },
+ },
+ },
+ wantOutput: `
+Report Summary
+
+┌──────────┬──────┬─────────────────┐
+│ Target │ Type │ Vulnerabilities │
+├──────────┼──────┼─────────────────┤
+│ test.jar │ jar │ 0 │
+└──────────┴──────┴─────────────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+`,
+ },
+ {
+ name: "no summary",
+ scanners: types.Scanners{
+ types.VulnerabilityScanner,
+ },
+ tableModes: []types.TableMode{
+ types.Detailed,
+ },
+ results: types.Results{
+ {
+ Target: "test",
+ Class: types.ClassLangPkg,
+ Type: ftypes.Jar,
+ Packages: []ftypes.Package{
+ {
+ Name: "foo",
+ Version: "1.2.3",
+ FilePath: "test.jar",
+ },
+ },
+ },
+ },
+ wantOutput: ``,
+ },
+ {
+ name: "no detailed",
+ scanners: types.Scanners{
+ types.VulnerabilityScanner,
+ },
+ tableModes: []types.TableMode{
+ types.Summary,
+ },
+ results: types.Results{
+ {
+ Target: "test",
+ Type: ftypes.Jar,
+ Class: types.ClassLangPkg,
+ Packages: []ftypes.Package{
+ {
+ Name: "foo",
+ Version: "1.2.3",
+ FilePath: "test.jar",
+ },
+ },
+ Vulnerabilities: []types.DetectedVulnerability{
+ {
+ VulnerabilityID: "CVE-2020-0001",
+ PkgName: "foo",
+ InstalledVersion: "1.2.3",
+ PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001",
+ Status: dbTypes.StatusWillNotFix,
+ PkgPath: "test.jar",
+ Vulnerability: dbTypes.Vulnerability{
+ Title: "foobar",
+ Description: "baz",
+ Severity: "HIGH",
+ },
+ },
+ },
+ },
+ },
+ wantOutput: `
+Report Summary
+
+┌──────────┬──────┬─────────────────┐
+│ Target │ Type │ Vulnerabilities │
+├──────────┼──────┼─────────────────┤
+│ test.jar │ jar │ 1 │
+└──────────┴──────┴─────────────────┘
+Legend:
+- '-': Not scanned
+- '0': Clean (no security findings detected)
+
+`,
+ },
+ {
+ name: "no tables",
+ scanners: types.Scanners{
+ types.VulnerabilityScanner,
+ },
+ tableModes: []types.TableMode{},
+ results: types.Results{
+ {
+ Target: "test",
+ Class: types.ClassLangPkg,
+ Type: ftypes.Jar,
+ Packages: []ftypes.Package{
+ {
+ Name: "foo",
+ Version: "1.2.3",
+ FilePath: "test.jar",
+ },
+ },
+ },
+ },
+ wantOutput: ``,
+ },
+ {
+ name: "no scanners",
results: types.Results{
{
Target: "test",
Class: types.ClassLangPkg,
+ Type: ftypes.Jar,
},
},
- expectedOutput: ``,
+ wantOutput: ``,
},
}
@@ -85,10 +243,11 @@ Total: 1 (MEDIUM: 0, HIGH: 1)
dbTypes.SeverityHigh,
dbTypes.SeverityMedium,
},
+ Scanners: tc.scanners,
+ TableModes: tc.tableModes,
})
- err := writer.Write(nil, types.Report{Results: tc.results})
- require.NoError(t, err)
- assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name)
+ _ = writer.Write(nil, types.Report{Results: tc.results})
+ assert.Equal(t, tc.wantOutput, tableWritten.String(), tc.name)
})
}
}
diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go
index c19aa0d103a2..548f4871e0b5 100644
--- a/pkg/report/table/vulnerability.go
+++ b/pkg/report/table/vulnerability.go
@@ -71,9 +71,8 @@ func NewVulnerabilityRenderer(buf *bytes.Buffer, isTerminal, tree, suppressed bo
func (r *vulnerabilityRenderer) Render(result types.Result) {
// There are 3 cases when we show the vulnerability table (or only target and `Total: 0...`):
// When Result contains vulnerabilities;
- // When Result target is OS packages even if no vulnerabilities are found;
// When we show non-empty `Suppressed Vulnerabilities` table.
- if len(result.Vulnerabilities) > 0 || result.Class == types.ClassOSPkg || (r.showSuppressed && len(result.ModifiedFindings) > 0) {
+ if len(result.Vulnerabilities) > 0 || (r.showSuppressed && len(result.ModifiedFindings) > 0) {
r.renderDetectedVulnerabilities(result)
if r.tree {
@@ -136,8 +135,6 @@ func (r *vulnerabilityRenderer) setVulnerabilityRows(tw *table.Table, vulns []ty
for _, v := range vulns {
lib := v.PkgName
if v.PkgPath != "" {
- // get path to root jar
- // for other languages return unchanged path
pkgPath := rootJarFromPath(v.PkgPath)
fileName := filepath.Base(pkgPath)
lib = fmt.Sprintf("%s (%s)", v.PkgName, fileName)
@@ -370,6 +367,8 @@ var jarExtensions = []string{
".ear",
}
+// rootJarFromPath returns path to root jar.
+// For other languages return unchanged path
func rootJarFromPath(path string) string {
// File paths are always forward-slashed in Trivy
paths := strings.Split(path, "/")
diff --git a/pkg/report/table/vulnerability_test.go b/pkg/report/table/vulnerability_test.go
index 5ac1310282d9..57f50b01b3f6 100644
--- a/pkg/report/table/vulnerability_test.go
+++ b/pkg/report/table/vulnerability_test.go
@@ -426,12 +426,7 @@ Suppressed Vulnerabilities (Total: 1)
},
},
showSuppressed: false,
- want: `
-test
-====
-Total: 0 (MEDIUM: 0, HIGH: 0)
-
-`,
+ want: "",
},
{
name: "suppressed all language package vulnerabilities without `showSuppressed` flag",
diff --git a/pkg/report/writer.go b/pkg/report/writer.go
index 12caeb74ff85..6a7e83d4a093 100644
--- a/pkg/report/writer.go
+++ b/pkg/report/writer.go
@@ -45,6 +45,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e
switch option.Format {
case types.FormatTable:
writer = table.NewWriter(table.Options{
+ Scanners: option.Scanners,
Output: output,
Severities: option.Severities,
Tree: option.DependencyTree,
@@ -54,6 +55,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e
RenderCause: option.RenderCause,
LicenseRiskThreshold: option.LicenseRiskThreshold,
IgnoredLicenses: option.IgnoredLicenses,
+ TableModes: option.TableModes,
})
case types.FormatJSON:
writer = &JSONWriter{
diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go
index 328d880e0779..3c964c51a88e 100644
--- a/pkg/rpc/client/client.go
+++ b/pkg/rpc/client/client.go
@@ -97,6 +97,7 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys
PkgRelationships: xstrings.ToStringSlice(opts.PkgRelationships),
Scanners: xstrings.ToStringSlice(opts.Scanners),
LicenseCategories: licenseCategories,
+ LicenseFull: opts.LicenseFull,
IncludeDevDeps: opts.IncludeDevDeps,
Distro: distro,
VulnSeveritySources: xstrings.ToStringSlice(opts.VulnSeveritySources),
diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go
index 39d5a7615474..4dcfcf02114b 100644
--- a/pkg/rpc/server/server.go
+++ b/pkg/rpc/server/server.go
@@ -96,6 +96,7 @@ func (s *ScanServer) ToOptions(in *rpcScanner.ScanOptions) types.ScanOptions {
Scanners: scanners,
IncludeDevDeps: in.IncludeDevDeps,
LicenseCategories: licenseCategories,
+ LicenseFull: in.LicenseFull,
Distro: distro,
VulnSeveritySources: vulnSeveritySources,
}
diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go
index 124587487d0d..35fe90f4a7df 100644
--- a/pkg/scanner/local/scan.go
+++ b/pkg/scanner/local/scan.go
@@ -261,21 +261,44 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions
var results types.Results
scanner := licensing.NewScanner(options.LicenseCategories)
- // License - OS packages
- var osPkgLicenses []types.DetectedLicense
- for _, pkg := range target.Packages {
+ // Scan licenses for OS packages
+ if result := s.scanOSPackageLicenses(target.Packages, scanner); result != nil {
+ results = append(results, *result)
+ }
+
+ // Scan licenses for language-specific packages
+ results = append(results, s.scanApplicationLicenses(target.Applications, scanner)...)
+
+ // Scan licenses in file headers or license files
+ if result := s.scanFileLicenses(target.Licenses, scanner, options); result != nil {
+ results = append(results, *result)
+ }
+
+ return results
+}
+
+func (s Scanner) scanOSPackageLicenses(packages []ftypes.Package, scanner licensing.Scanner) *types.Result {
+ if len(packages) == 0 {
+ return nil
+ }
+
+ var licenses []types.DetectedLicense
+ for _, pkg := range packages {
for _, license := range pkg.Licenses {
- osPkgLicenses = append(osPkgLicenses, toDetectedLicense(scanner, license, pkg.Name, ""))
+ licenses = append(licenses, toDetectedLicense(scanner, license, pkg.Name, ""))
}
}
- results = append(results, types.Result{
+ return &types.Result{
Target: "OS Packages",
Class: types.ClassLicense,
- Licenses: osPkgLicenses,
- })
+ Licenses: licenses,
+ }
+}
- // License - language-specific packages
- for _, app := range target.Applications {
+func (s Scanner) scanApplicationLicenses(apps []ftypes.Application, scanner licensing.Scanner) []types.Result {
+ var results []types.Result
+
+ for _, app := range apps {
var langLicenses []types.DetectedLicense
for _, lib := range app.Packages {
for _, license := range lib.Licenses {
@@ -292,19 +315,29 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions
// When the file path is empty, we will overwrite it with the pre-defined value.
targetName = t
}
- results = append(results, types.Result{
- Target: targetName,
- Class: types.ClassLicense,
- Licenses: langLicenses,
- })
+
+ if len(langLicenses) != 0 {
+ results = append(results, types.Result{
+ Target: targetName,
+ Class: types.ClassLicense,
+ Licenses: langLicenses,
+ })
+ }
+
}
- // License - file header or license file
- var fileLicenses []types.DetectedLicense
- for _, license := range target.Licenses {
+ return results
+}
+
+func (s Scanner) scanFileLicenses(licenses []ftypes.LicenseFile, scanner licensing.Scanner, options types.ScanOptions) *types.Result {
+ if !options.LicenseFull {
+ return nil
+ }
+ var detectedLicenses []types.DetectedLicense
+ for _, license := range licenses {
for _, finding := range license.Findings {
category, severity := scanner.Scan(finding.Name)
- fileLicenses = append(fileLicenses, types.DetectedLicense{
+ detectedLicenses = append(detectedLicenses, types.DetectedLicense{
Severity: severity,
Category: category,
FilePath: license.FilePath,
@@ -312,16 +345,14 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions
Confidence: finding.Confidence,
Link: finding.Link,
})
-
}
}
- results = append(results, types.Result{
+
+ return &types.Result{
Target: "Loose File License(s)",
Class: types.ClassLicenseFile,
- Licenses: fileLicenses,
- })
-
- return results
+ Licenses: detectedLicenses,
+ }
}
func toDetectedMisconfiguration(res ftypes.MisconfResult, defaultSeverity dbTypes.Severity,
diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go
index 34cdbf00c754..ea01fe1eecb6 100644
--- a/pkg/scanner/local/scan_test.go
+++ b/pkg/scanner/local/scan_test.go
@@ -344,6 +344,7 @@ func TestScanner_Scan(t *testing.T) {
options: types.ScanOptions{
PkgRelationships: ftypes.Relationships,
Scanners: types.Scanners{types.LicenseScanner},
+ LicenseFull: true,
},
},
fixtures: []string{"testdata/fixtures/happy.yaml"},
diff --git a/pkg/types/report.go b/pkg/types/report.go
index 3ada68a942d8..3ba97171ca4c 100644
--- a/pkg/types/report.go
+++ b/pkg/types/report.go
@@ -105,6 +105,18 @@ var (
}
)
+type TableMode string
+
+const (
+ Summary TableMode = "summary"
+ Detailed TableMode = "detailed"
+)
+
+var SupportedTableModes = []TableMode{
+ Summary,
+ Detailed,
+}
+
// Result holds a target and detected vulnerabilities
type Result struct {
Target string `json:"Target"`
diff --git a/pkg/types/scan.go b/pkg/types/scan.go
index 1d1586082b4e..6dec84d74e4e 100644
--- a/pkg/types/scan.go
+++ b/pkg/types/scan.go
@@ -112,7 +112,7 @@ type ScanTarget struct {
CustomResources []types.CustomResource
}
-// ScanOptions holds the attributes for scanning vulnerabilities
+// ScanOptions holds the attributes for scanning vulnerabilities/licenses
type ScanOptions struct {
PkgTypes []string
PkgRelationships []types.Relationship
@@ -120,6 +120,7 @@ type ScanOptions struct {
ImageConfigScanners Scanners // Scanners for container image configuration
ScanRemovedPackages bool
LicenseCategories map[types.LicenseCategory][]string
+ LicenseFull bool
FilePatterns []string
IncludeDevDeps bool
Distro types.OS // Forced OS
diff --git a/rpc/scanner/service.pb.go b/rpc/scanner/service.pb.go
index 3927282ad880..89464829ec7b 100644
--- a/rpc/scanner/service.pb.go
+++ b/rpc/scanner/service.pb.go
@@ -153,6 +153,7 @@ type ScanOptions struct {
PkgRelationships []string `protobuf:"bytes,6,rep,name=pkg_relationships,json=pkgRelationships,proto3" json:"pkg_relationships,omitempty"`
Distro *common.OS `protobuf:"bytes,7,opt,name=distro,proto3" json:"distro,omitempty"`
VulnSeveritySources []string `protobuf:"bytes,8,rep,name=vuln_severity_sources,json=vulnSeveritySources,proto3" json:"vuln_severity_sources,omitempty"`
+ LicenseFull bool `protobuf:"varint,9,opt,name=license_full,json=licenseFull,proto3" json:"license_full,omitempty"`
}
func (x *ScanOptions) Reset() {
@@ -236,6 +237,13 @@ func (x *ScanOptions) GetVulnSeveritySources() []string {
return nil
}
+func (x *ScanOptions) GetLicenseFull() bool {
+ if x != nil {
+ return x.LicenseFull
+ }
+ return false
+}
+
type ScanResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -422,7 +430,7 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{
0x53, 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x22, 0x20, 0x0a, 0x08, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73,
0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
- 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xc8, 0x03, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f,
+ 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xeb, 0x03, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x67, 0x5f, 0x74, 0x79,
0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6b, 0x67, 0x54, 0x79,
0x70, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18,
@@ -444,58 +452,61 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{
0x69, 0x73, 0x74, 0x72, 0x6f, 0x12, 0x32, 0x0a, 0x15, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x73, 0x65,
0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x08,
0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x76, 0x75, 0x6c, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69,
- 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0x60, 0x0a, 0x16, 0x4c, 0x69, 0x63,
- 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e,
- 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
- 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61,
- 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73,
- 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10,
- 0x04, 0x22, 0x64, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
- 0x65, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e,
- 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52,
- 0x02, 0x6f, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03,
- 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61,
- 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07,
- 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75,
- 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x76, 0x75,
- 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20,
- 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
- 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,
- 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65,
- 0x73, 0x12, 0x54, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72,
- 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74,
- 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65,
- 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75,
- 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73,
- 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x12, 0x0a,
- 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70,
- 0x65, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20,
- 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
- 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b,
- 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72,
- 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c,
- 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x75,
- 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0f, 0x63, 0x75,
- 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x35, 0x0a,
- 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b,
- 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65,
- 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x73, 0x65, 0x63,
- 0x72, 0x65, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73,
- 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63,
- 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, 0x69,
- 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x32,
- 0x50, 0x0a, 0x07, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x04, 0x53, 0x63,
- 0x61, 0x6e, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e,
- 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
- 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65,
- 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
- 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
- 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69,
- 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x3b, 0x73,
- 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x69, 0x63,
+ 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52,
+ 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x75, 0x6c, 0x6c, 0x1a, 0x60, 0x0a, 0x16,
+ 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65,
+ 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
+ 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e,
+ 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e,
+ 0x73, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04,
+ 0x08, 0x03, 0x10, 0x04, 0x22, 0x64, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+ 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
+ 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e,
+ 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c,
+ 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52,
+ 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a,
+ 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73,
+ 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c,
+ 0x69, 0x74, 0x79, 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69,
+ 0x74, 0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+ 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
+ 0x26, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44,
+ 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+ 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66,
+ 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c,
+ 0x61, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73,
+ 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+ 0x74, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73,
+ 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70,
+ 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f,
+ 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28,
+ 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+ 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,
+ 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
+ 0x12, 0x35, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28,
+ 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+ 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07,
+ 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e,
+ 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76,
+ 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65,
+ 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73,
+ 0x65, 0x73, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a,
+ 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63,
+ 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61,
+ 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f,
+ 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65,
+ 0x72, 0x3b, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x33,
}
var (
diff --git a/rpc/scanner/service.proto b/rpc/scanner/service.proto
index 3602875473c7..49dfe497cd46 100644
--- a/rpc/scanner/service.proto
+++ b/rpc/scanner/service.proto
@@ -30,6 +30,7 @@ message ScanOptions {
repeated string pkg_relationships = 6;
common.OS distro = 7;
repeated string vuln_severity_sources = 8;
+ bool license_full = 9;
reserved 3; // deleted 'list_all_packages'
}
diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go
index d763d6a0804d..d49372d0dd88 100644
--- a/rpc/scanner/service.twirp.go
+++ b/rpc/scanner/service.twirp.go
@@ -1094,50 +1094,51 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error)
}
var twirpFileDescriptor0 = []byte{
- // 710 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xcd, 0x6e, 0xe3, 0x36,
- 0x10, 0x86, 0x2d, 0xc7, 0x96, 0xc7, 0x45, 0xe3, 0xb0, 0x4d, 0xa0, 0x38, 0x4d, 0x6b, 0xf8, 0x50,
- 0x18, 0x28, 0x60, 0x37, 0x4e, 0x8b, 0xfe, 0xdd, 0x9a, 0xa4, 0x45, 0x8a, 0x16, 0x09, 0xe8, 0xa0,
- 0x87, 0x5e, 0x54, 0x9a, 0x9a, 0x28, 0x84, 0x65, 0x49, 0x21, 0x29, 0x01, 0x7e, 0x95, 0x7d, 0xa2,
- 0x7d, 0x81, 0x7d, 0x9f, 0x05, 0x49, 0xc9, 0x88, 0xed, 0x64, 0x4f, 0xd2, 0xcc, 0xf7, 0xcd, 0xcc,
- 0x47, 0xf2, 0x23, 0xe1, 0x54, 0xe6, 0x7c, 0xaa, 0x38, 0x4b, 0x53, 0x94, 0x53, 0x85, 0xb2, 0x14,
- 0x1c, 0x27, 0xb9, 0xcc, 0x74, 0x46, 0xfa, 0x5a, 0x8a, 0x72, 0x3d, 0xa9, 0xc0, 0x49, 0x79, 0x31,
- 0x08, 0x0c, 0x99, 0x67, 0xab, 0x55, 0x96, 0x6e, 0x73, 0x47, 0xef, 0x1a, 0xd0, 0x9b, 0x73, 0x96,
- 0x52, 0x7c, 0x2e, 0x50, 0x69, 0x72, 0x02, 0x6d, 0xcd, 0x64, 0x8c, 0x3a, 0x68, 0x0c, 0x1b, 0xe3,
- 0x2e, 0xad, 0x22, 0xf2, 0x0d, 0xf4, 0x98, 0xd4, 0xe2, 0x91, 0x71, 0x1d, 0x8a, 0x28, 0x68, 0x5a,
- 0x10, 0xea, 0xd4, 0x6d, 0x44, 0x4e, 0xc1, 0x5f, 0x24, 0xd9, 0x22, 0x14, 0x91, 0x0a, 0xbc, 0xa1,
- 0x37, 0xee, 0xd2, 0x8e, 0x89, 0x6f, 0x23, 0x45, 0x7e, 0x82, 0x4e, 0x96, 0x6b, 0x91, 0xa5, 0x2a,
- 0x68, 0x0d, 0x1b, 0xe3, 0xde, 0xec, 0x7c, 0xb2, 0xab, 0x70, 0x62, 0x34, 0xdc, 0x39, 0x12, 0xad,
- 0xd9, 0xa3, 0x21, 0xf8, 0x7f, 0x0b, 0x8e, 0xa9, 0x42, 0x45, 0xbe, 0x84, 0x83, 0x94, 0xad, 0x50,
- 0x05, 0x0d, 0xdb, 0xdc, 0x05, 0xa3, 0xf7, 0x9e, 0x93, 0x5f, 0x95, 0x92, 0x33, 0xe8, 0xe6, 0xcb,
- 0x38, 0xd4, 0xeb, 0x7c, 0xc3, 0xf4, 0xf3, 0x65, 0xfc, 0x60, 0x62, 0x32, 0x00, 0xbf, 0x9a, 0xa8,
- 0x82, 0xa6, 0xc3, 0xea, 0x98, 0x70, 0x20, 0x89, 0x1b, 0x15, 0x72, 0xa6, 0x31, 0xce, 0xa4, 0x40,
- 0x23, 0xd7, 0x1b, 0xf7, 0x66, 0x3f, 0x7c, 0x52, 0xee, 0xa4, 0x92, 0x78, 0xb5, 0x29, 0xbb, 0x49,
- 0xb5, 0x5c, 0xd3, 0xa3, 0x64, 0x37, 0x4f, 0xc6, 0xd0, 0x17, 0x29, 0x4f, 0x8a, 0x08, 0xc3, 0x08,
- 0xcb, 0x30, 0xc2, 0x5c, 0x05, 0x07, 0xc3, 0xc6, 0xd8, 0xa7, 0x9f, 0x57, 0xf9, 0x6b, 0x2c, 0xaf,
- 0x31, 0x57, 0xe4, 0x3b, 0x38, 0x32, 0xeb, 0x90, 0x98, 0x30, 0x3b, 0xe4, 0x49, 0xe4, 0x2a, 0x68,
- 0x5b, 0xcd, 0xfd, 0x7c, 0x19, 0xd3, 0x97, 0x79, 0x32, 0x86, 0x76, 0x24, 0x94, 0x96, 0x59, 0xd0,
- 0xb1, 0xdb, 0xdb, 0xaf, 0xf4, 0xba, 0x03, 0x9f, 0xdc, 0xcd, 0x69, 0x85, 0x93, 0x19, 0x1c, 0x97,
- 0x45, 0x92, 0x86, 0x0a, 0x4b, 0x94, 0x42, 0xaf, 0x43, 0x95, 0x15, 0x92, 0xa3, 0x0a, 0x7c, 0xdb,
- 0xfa, 0x0b, 0x03, 0xce, 0x2b, 0x6c, 0xee, 0xa0, 0xc1, 0xff, 0x70, 0xf2, 0xfa, 0x0a, 0x49, 0x1f,
- 0xbc, 0x25, 0xae, 0x2b, 0xa3, 0x98, 0x5f, 0xf2, 0x3d, 0x1c, 0x94, 0x2c, 0x29, 0xd0, 0xfa, 0xa3,
- 0x37, 0x1b, 0xec, 0x6f, 0x5c, 0x7d, 0x9e, 0xd4, 0x11, 0x7f, 0x6d, 0xfe, 0xdc, 0xf8, 0xab, 0xe5,
- 0x7b, 0xfd, 0xd6, 0x28, 0x82, 0xcf, 0x9c, 0x11, 0x55, 0x9e, 0xa5, 0x0a, 0xc9, 0x10, 0x9a, 0x99,
- 0xb2, 0xcd, 0x5f, 0x5b, 0x51, 0x33, 0x53, 0x64, 0x06, 0x1d, 0x89, 0xaa, 0x48, 0xb4, 0x73, 0x5c,
- 0x6f, 0x16, 0xec, 0xcf, 0xa3, 0x96, 0x40, 0x6b, 0xe2, 0xe8, 0x83, 0x07, 0x6d, 0x97, 0x7b, 0xd3,
- 0xea, 0x37, 0x70, 0x68, 0xf6, 0x01, 0x25, 0x5b, 0x88, 0x44, 0x68, 0xe3, 0x83, 0xa6, 0x6d, 0x7f,
- 0xb6, 0xad, 0xe2, 0xdf, 0x17, 0xa4, 0x35, 0xdd, 0xad, 0x21, 0x0f, 0x70, 0xb4, 0x12, 0x8a, 0x67,
- 0xe9, 0xa3, 0x88, 0x0b, 0xc9, 0x6a, 0xff, 0x9b, 0x46, 0xdf, 0x6e, 0x37, 0xba, 0x46, 0x8d, 0x5c,
- 0x63, 0xf4, 0xcf, 0x0e, 0x9d, 0xee, 0x37, 0x30, 0xd7, 0x80, 0x27, 0x4c, 0x19, 0x33, 0x18, 0xcd,
- 0x2e, 0x20, 0x04, 0x5a, 0xc6, 0xf2, 0x81, 0x67, 0x93, 0xf6, 0x9f, 0x5c, 0x80, 0x9f, 0x33, 0xbe,
- 0x64, 0x31, 0x1a, 0x93, 0x99, 0xb1, 0xc7, 0xdb, 0x63, 0xef, 0x1d, 0x4a, 0x37, 0x34, 0xf2, 0x27,
- 0xf4, 0x79, 0xa1, 0x74, 0xb6, 0x0a, 0x25, 0xd6, 0xce, 0xe8, 0xd8, 0xd2, 0xaf, 0xb6, 0x4b, 0xaf,
- 0x2c, 0x8b, 0x56, 0x24, 0x7a, 0xc8, 0xb7, 0x62, 0x45, 0x7e, 0x84, 0x8e, 0x42, 0x2e, 0x51, 0x3b,
- 0x67, 0xed, 0x6d, 0xdd, 0xdc, 0x82, 0x7f, 0x88, 0x34, 0x12, 0x69, 0x4c, 0x6b, 0x2e, 0xf9, 0x05,
- 0xfc, 0xea, 0xd2, 0xa8, 0xa0, 0x6b, 0xeb, 0xce, 0x5f, 0xdf, 0xa9, 0xca, 0x45, 0x74, 0x43, 0x9f,
- 0xdd, 0x43, 0x67, 0xee, 0x4e, 0x9d, 0xdc, 0x40, 0xcb, 0xfc, 0x92, 0x37, 0x5e, 0x99, 0xea, 0xa5,
- 0x1b, 0x7c, 0xfd, 0x16, 0xec, 0xfc, 0xf7, 0xfb, 0xe5, 0x7f, 0x17, 0xb1, 0xd0, 0x4f, 0xc5, 0xc2,
- 0x0c, 0x9f, 0xb2, 0xe7, 0x82, 0x29, 0xe4, 0x85, 0xb9, 0x19, 0x53, 0x5b, 0x38, 0x7d, 0xf1, 0x00,
- 0xff, 0x56, 0x7d, 0x17, 0x6d, 0xfb, 0xaa, 0x5e, 0x7e, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xce, 0x3a,
- 0x39, 0x26, 0x9e, 0x05, 0x00, 0x00,
+ // 729 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0x5f, 0x6f, 0xe3, 0x44,
+ 0x10, 0x57, 0xe2, 0x34, 0x71, 0x26, 0x27, 0x2e, 0x5d, 0xb8, 0x93, 0x2f, 0xc7, 0x41, 0xc8, 0x03,
+ 0x8a, 0x84, 0x94, 0xd0, 0x1c, 0x88, 0x7f, 0x6f, 0x5c, 0x5b, 0x54, 0x04, 0x6a, 0xb5, 0xa9, 0x78,
+ 0xe0, 0xc5, 0x6c, 0xd6, 0x53, 0x77, 0x15, 0xc7, 0x76, 0x77, 0xd7, 0x96, 0xf2, 0x55, 0xf8, 0x5e,
+ 0x7c, 0x0a, 0xbe, 0x04, 0xda, 0x3f, 0x8e, 0x9a, 0xa4, 0xe5, 0xc9, 0x3b, 0x33, 0xbf, 0x99, 0xf9,
+ 0x79, 0xf6, 0x37, 0x0b, 0x6f, 0x64, 0xc9, 0xe7, 0x8a, 0xb3, 0x3c, 0x47, 0x39, 0x57, 0x28, 0x6b,
+ 0xc1, 0x71, 0x56, 0xca, 0x42, 0x17, 0x64, 0xa8, 0xa5, 0xa8, 0xb7, 0x33, 0x1f, 0x9c, 0xd5, 0x67,
+ 0xa3, 0xc8, 0x80, 0x79, 0xb1, 0xd9, 0x14, 0xf9, 0x3e, 0x76, 0xf2, 0x77, 0x0b, 0x06, 0x4b, 0xce,
+ 0x72, 0x8a, 0x0f, 0x15, 0x2a, 0x4d, 0x5e, 0x43, 0x57, 0x33, 0x99, 0xa2, 0x8e, 0x5a, 0xe3, 0xd6,
+ 0xb4, 0x4f, 0xbd, 0x45, 0x3e, 0x87, 0x01, 0x93, 0x5a, 0xdc, 0x31, 0xae, 0x63, 0x91, 0x44, 0x6d,
+ 0x1b, 0x84, 0xc6, 0x75, 0x95, 0x90, 0x37, 0x10, 0xae, 0xb2, 0x62, 0x15, 0x8b, 0x44, 0x45, 0xc1,
+ 0x38, 0x98, 0xf6, 0x69, 0xcf, 0xd8, 0x57, 0x89, 0x22, 0xdf, 0x41, 0xaf, 0x28, 0xb5, 0x28, 0x72,
+ 0x15, 0x75, 0xc6, 0xad, 0xe9, 0x60, 0xf1, 0x6e, 0x76, 0xc8, 0x70, 0x66, 0x38, 0x5c, 0x3b, 0x10,
+ 0x6d, 0xd0, 0x93, 0x31, 0x84, 0xbf, 0x09, 0x8e, 0xb9, 0x42, 0x45, 0x3e, 0x81, 0x93, 0x9c, 0x6d,
+ 0x50, 0x45, 0x2d, 0x5b, 0xdc, 0x19, 0x93, 0x7f, 0x03, 0x47, 0xdf, 0xa7, 0x92, 0xb7, 0xd0, 0x2f,
+ 0xd7, 0x69, 0xac, 0xb7, 0xe5, 0x0e, 0x19, 0x96, 0xeb, 0xf4, 0xd6, 0xd8, 0x64, 0x04, 0xa1, 0xef,
+ 0xa8, 0xa2, 0xb6, 0x8b, 0x35, 0x36, 0xe1, 0x40, 0x32, 0xd7, 0x2a, 0xe6, 0x4c, 0x63, 0x5a, 0x48,
+ 0x81, 0x86, 0x6e, 0x30, 0x1d, 0x2c, 0xbe, 0xf9, 0x5f, 0xba, 0x33, 0x4f, 0xf1, 0xc3, 0x2e, 0xed,
+ 0x22, 0xd7, 0x72, 0x4b, 0x4f, 0xb3, 0x43, 0x3f, 0x99, 0xc2, 0x50, 0xe4, 0x3c, 0xab, 0x12, 0x8c,
+ 0x13, 0xac, 0xe3, 0x04, 0x4b, 0x15, 0x9d, 0x8c, 0x5b, 0xd3, 0x90, 0x7e, 0xe4, 0xfd, 0xe7, 0x58,
+ 0x9f, 0x63, 0xa9, 0xc8, 0x57, 0x70, 0x6a, 0xfe, 0x43, 0x62, 0xc6, 0x6c, 0x93, 0x7b, 0x51, 0xaa,
+ 0xa8, 0x6b, 0x39, 0x0f, 0xcb, 0x75, 0x4a, 0x1f, 0xfb, 0xc9, 0x14, 0xba, 0x89, 0x50, 0x5a, 0x16,
+ 0x51, 0xcf, 0x8e, 0x77, 0xe8, 0xf9, 0xba, 0x0b, 0x9f, 0x5d, 0x2f, 0xa9, 0x8f, 0x93, 0x05, 0xbc,
+ 0xaa, 0xab, 0x2c, 0x8f, 0x15, 0xd6, 0x28, 0x85, 0xde, 0xc6, 0xaa, 0xa8, 0x24, 0x47, 0x15, 0x85,
+ 0xb6, 0xf4, 0xc7, 0x26, 0xb8, 0xf4, 0xb1, 0xa5, 0x0b, 0x91, 0x2f, 0xe0, 0x45, 0x33, 0x99, 0xbb,
+ 0x2a, 0xcb, 0xa2, 0xbe, 0x25, 0x3c, 0xf0, 0xbe, 0xcb, 0x2a, 0xcb, 0x46, 0x7f, 0xc1, 0xeb, 0xa7,
+ 0x87, 0x40, 0x86, 0x10, 0xac, 0x71, 0xeb, 0xb5, 0x64, 0x8e, 0xe4, 0x6b, 0x38, 0xa9, 0x59, 0x56,
+ 0xa1, 0x95, 0xd0, 0x60, 0x31, 0x3a, 0x9e, 0x6d, 0x73, 0xe5, 0xd4, 0x01, 0x7f, 0x6c, 0x7f, 0xdf,
+ 0xfa, 0xb5, 0x13, 0x06, 0xc3, 0xce, 0x24, 0x81, 0x17, 0x4e, 0xab, 0xaa, 0x2c, 0x72, 0x85, 0x64,
+ 0x0c, 0xed, 0x42, 0xd9, 0xe2, 0x4f, 0xfd, 0x74, 0xbb, 0x50, 0x64, 0x01, 0x3d, 0x89, 0xaa, 0xca,
+ 0xb4, 0x13, 0xe5, 0x60, 0x11, 0x1d, 0xf7, 0xa3, 0x16, 0x40, 0x1b, 0xe0, 0xe4, 0x9f, 0x00, 0xba,
+ 0xce, 0xf7, 0xec, 0x36, 0x5c, 0xc0, 0x4b, 0x33, 0x2a, 0x94, 0x6c, 0x25, 0x32, 0xa1, 0x8d, 0x54,
+ 0xda, 0xb6, 0xfc, 0xdb, 0x7d, 0x16, 0x7f, 0x3c, 0x02, 0x6d, 0xe9, 0x61, 0x0e, 0xb9, 0x85, 0xd3,
+ 0x8d, 0x50, 0xbc, 0xc8, 0xef, 0x44, 0x5a, 0x49, 0xd6, 0xac, 0x88, 0x29, 0xf4, 0xe5, 0x7e, 0xa1,
+ 0x73, 0xd4, 0xc8, 0x35, 0x26, 0xbf, 0x1f, 0xc0, 0xe9, 0x71, 0x01, 0xb3, 0x29, 0x3c, 0x63, 0xca,
+ 0xe8, 0xc5, 0x70, 0x76, 0x06, 0x21, 0xd0, 0x31, 0x5b, 0x11, 0x05, 0xd6, 0x69, 0xcf, 0xe4, 0x0c,
+ 0xc2, 0x92, 0xf1, 0x35, 0x4b, 0xd1, 0xe8, 0xd0, 0xb4, 0x7d, 0xb5, 0xdf, 0xf6, 0xc6, 0x45, 0xe9,
+ 0x0e, 0x46, 0x7e, 0x81, 0x21, 0xaf, 0x94, 0x2e, 0x36, 0xb1, 0xc4, 0x46, 0x3c, 0x3d, 0x9b, 0xfa,
+ 0xe9, 0x7e, 0xea, 0x07, 0x8b, 0xa2, 0x1e, 0x44, 0x5f, 0xf2, 0x3d, 0x5b, 0x91, 0x6f, 0xa1, 0xa7,
+ 0x90, 0x4b, 0xd4, 0x4e, 0x7c, 0x47, 0xa3, 0x5b, 0xda, 0xe0, 0xa5, 0xc8, 0x13, 0x91, 0xa7, 0xb4,
+ 0xc1, 0x92, 0x1f, 0x20, 0xf4, 0xca, 0x53, 0x51, 0xdf, 0xe6, 0xbd, 0x7b, 0x7a, 0x52, 0x5e, 0x45,
+ 0x74, 0x07, 0x5f, 0xdc, 0x40, 0x6f, 0xe9, 0x6e, 0x9d, 0x5c, 0x40, 0xc7, 0x1c, 0xc9, 0x33, 0x0f,
+ 0x91, 0x7f, 0x0c, 0x47, 0x9f, 0x3d, 0x17, 0x76, 0xfa, 0xfb, 0xf9, 0xfd, 0x9f, 0x67, 0xa9, 0xd0,
+ 0xf7, 0xd5, 0xca, 0x34, 0x9f, 0xb3, 0x87, 0x8a, 0x29, 0xe4, 0x95, 0x59, 0x9e, 0xb9, 0x4d, 0x9c,
+ 0x3f, 0x7a, 0xa3, 0x7f, 0xf2, 0xdf, 0x55, 0xd7, 0x3e, 0xbc, 0xef, 0xff, 0x0b, 0x00, 0x00, 0xff,
+ 0xff, 0xe0, 0xa9, 0x3c, 0x91, 0xc1, 0x05, 0x00, 0x00,
}