Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add report summary table #8177

Open
wants to merge 27 commits into
base: main
Choose a base branch
from

Conversation

knqyf263
Copy link
Collaborator

@knqyf263 knqyf263 commented Dec 25, 2024

This PR introduces a summary table that displays all scan results, including those with zero findings, such as vulnerabilities and misconfiguration, to improve visibility and reduce user anxiety about the scanning process.

Background

Historically, Trivy followed the Unix philosophy where "silence is golden" and didn't output anything when no vulnerabilities were found. However, this led to user confusion about whether scans were working correctly. While we previously addressed this by showing results for OS packages with zero vulnerabilities, the inconsistency between OS and language-specific package outputs has created new confusion.

Proposed Changes

This PR adds a summary table at the beginning of the output that:

  • Shows all scanned targets regardless of security finding count
  • Maintains the current behavior of only showing detailed tables for items with findings
  • Provides a clear overview of the entire scan scope

Example output:

trivy fs . --scanners vuln,misconfig,secret --skip-dirs pkg,integration --severity CRITICAL,HIGH

image

Considerations

  1. Aggregated Results for Individual Files

    • Currently, archive files (like JARs) and individual files (like .gemspec) show aggregated results without displaying vulnerability counts for individual files
    • Implementing per-file vulnerability counts would require significant changes
    • Proposal: Address this enhancement in a separate PR to keep the current changes focused
  2. Result Aggregation Strategy

    • Current implementation shows separate rows for different finding types (e.g., vulnerabilities and secrets) in the same file
    • We could potentially aggregate these into a single row
    • However, this raises questions about:
      • How to represent multiple types in a single row
    • This PR maintains the current separation to keep the implementation straightforward
  3. Default Behavior

    • Proposal: Enable summary table by default with an opt-out flag (e.g. --no-summary-table)
    • Rationale: Reduces user confusion and provides immediate feedback about scan coverage
    • Alternative: Opt-in approach with --show-summary-table

Implementation Notes

  • This is a minimal PoC implementation focused on establishing the basic functionality
  • Future enhancements can build upon this foundation to address the discussion points above

TODOs

  • Add summary table
  • Add --no-summary-table
  • Error out on --no-summary-table with the formats other than --format table
  • Clarify 0 and - in the summary table
  • Add tests
  • Update documentation

Related issues

Checklist

  • I've read the guidelines for contributing to this repository.
  • I've followed the conventions in the PR title.
  • I've added tests that prove my fix is effective or that my feature works.
  • I've updated the documentation with the relevant information (if needed).
  • I've added usage information (if the PR introduces new options)
  • I've included a "before" and "after" example to the description (if the PR is a user interface change).

@knqyf263
Copy link
Collaborator Author

knqyf263 commented Dec 25, 2024

@DmitriyLewen @nikpivkin Before further development, I'd like to hear your thoughts.

@DmitriyLewen
Copy link
Contributor

Aggregated Results for Individual Files
Proposal: Address this enhancement in a separate PR to keep the current changes focused

I agree with you. I think we need to discuss this. Initially (I may be wrong) Trivy was crippled by aggregating individual packages to avoid having too many tables in a report.
After adding summary table, we can consider removing the logic for aggregating packages entirely. This will simplify the code more and avoid difficulties when converting reports between formats.

Current implementation shows separate rows for different finding types (e.g., vulnerabilities and secrets) in the same file
This PR maintains the current separation to keep the implementation straightforward

It seems that having multiple types for a single file is rare, so I suggest using this logic and waiting for feedback from users.

Proposal: Enable summary table by default with an opt-out flag (e.g. --no-summary-table)

I prefer this way.

I also have one idea - some users don't need to see vulnerability/misconfiguration numbers (CVE etc). So we can hide other tables by default and show them only when using a new flag (--show-report-tables or something like that). Or vice versa hide them by flag if users only need summary numbers.
But I think we need to ask users about it.


As a future update:

  • What if we check the enabled scanners and show only the required columns?
    eg by default the summary table will only include the vulnerability and secret columns.
    or for misconfig mode only the misconfigutation column.

@nikpivkin
Copy link
Contributor

I like the idea, the summary table looks good. Should the results be sorted by file name or scan type?

I noticed that terraform scan results contain directories that we should get rid of.

@knqyf263
Copy link
Collaborator Author

After adding summary table, we can consider removing the logic for aggregating packages entirely. This will simplify the code more and avoid difficulties when converting reports between formats.

Exactly. It will simplify our logic. However, it will probably break something. We should carefully announce it. That's why I didn't want to add the change into this PR.

What if we check the enabled scanners and show only the required columns?

I already implemented it in this PR unless I'm making a mistake. In the screenshot, Vulnerabilities, Misconfigurations and Secrets are shown because --scanners vuln,misconfig,secret is specified.

@knqyf263
Copy link
Collaborator Author

Should the results be sorted by file name or scan type?

The current summary table is ordered by scanners, vulnerabilities -> misconfigurations -> secrets -> licenses. Do you want to sort by sub-scanners, such as terraform?

I noticed that terraform scan results contain directories that we should get rid of.

Yes, I actually saw . was detected as terraform-plan.

@DmitriyLewen
Copy link
Contributor

I already implemented it in this PR unless I'm making a mistake. In the screenshot, Vulnerabilities, Misconfigurations and Secrets are shown because --scanners vuln,misconfig,secret is specified.

Great!

@itaysk
Copy link
Contributor

itaysk commented Dec 30, 2024

looks great. this is very similar to the k8s summary report. Do you think we should consider reusing the experience, code or flags? (either way, new, existing or something in between)

image

@simar7
Copy link
Member

simar7 commented Jan 2, 2025

Looks nice. What's the difference between a - and a 0?

I think I understand it as not evaluated and evaluated but nothing found respectively but I think we should make it explicit.

looks great. this is very similar to the k8s summary report. Do you think we should consider reusing the experience, code or flags? (either way, new, existing or something in between)

I think we should change k8s summary output to be more simpler rather than the other way around. To me the k8s summary output is a bit too much especially with so many numbers that it isn't very helpful.

@knqyf263
Copy link
Collaborator Author

knqyf263 commented Jan 6, 2025

Do you think we should consider reusing the experience, code or flags? (either way, new, existing or something in between)

I actually considered reusing the K8s summary table, but ultimately decided to keep them separate for several reasons:

First, as @simar7 pointed out, the K8s summary table contains much information. This isn't necessarily a negative aspect - it's designed this way because K8s doesn't display details in the summary table by default (requires --report all flag for detailed view). However, the approach is different for image and filesystem scans: we show a simple summary table first, followed by detailed result tables. Therefore, I believe the summary table should be kept as simple as possible. This represents a slightly different purpose from the K8s use case.

Second, since we plan to make a separate plugin for the K8s cluster scan similar to AWS account scanning, I wanted to avoid core dependencies on K8s. If we were to share code, having the dependency direction flow from K8s to core would be preferable, not the other way around. However, given the different purposes mentioned above, whether we should standardize these tables at all requires further discussion.

I think I understand it as not evaluated and evaluated but nothing found respectively but I think we should make it explicit.

Yes, that's correct. I made this distinction because we frequently receive user feedback asking to clarify whether something wasn't scanned at all or was scanned but returned zero findings. I'll add a note beneath the table to clarify this distinction.

@@ -64,6 +80,69 @@ func (tw Writer) Write(_ context.Context, report types.Report) error {
return nil
}

func (tw Writer) renderSummary(report types.Report) error {
log.WithPrefix("report").Info("Report Summary table contains special symbols",
Copy link
Contributor

Choose a reason for hiding this comment

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

I added info about - and 0 into logs:

2025-01-17T14:37:53+06:00       INFO    [report] Report Summary table contains special symbols  '-'="Target didn't scanned" '0'="Target scanned, but didn't contain vulns/misconfigs/secrets/licenses"

Report Summary

┌─────────────────────────────────────────────┬──────────┬─────────────────┬─────────┐
│                   Target                    │   Type   │ Vulnerabilities │ Secrets │
├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤

But we can add info before table. e.g.:

Report Summary

Note:
- `-`: Target didn't scanned.
- `0`: Target scanned, but didn't contain vulns/misconfigs/secrets/licenses.

┌─────────────────────────────────────────────┬──────────┬─────────────────┬─────────┐
│                   Target                    │   Type   │ Vulnerabilities │ Secrets │
├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤

@aquasecurity/trivy wdyt?

Copy link
Member

Choose a reason for hiding this comment

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

Should we elaborate on why a certain file "didn't scan"? Is it because it was unable to get parsed, wasn't valid for the scan (then maybe we shouldn't even traverse it) or something else?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can wait for user feedback:

In most cases - will be used for the case when the file is scanned by another scanner.
e.g., we scanned Dockerfile, and - will be for vulnerabilities.

In case the file is invalid (e.g. wrong lock file) - the report will not include the result with this file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I prefer putting it in the footer.

Legend:
- `-`: Not scanned
- `0`: Clean (no security findings detected)

Copy link
Contributor

Choose a reason for hiding this comment

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

changed in e11221a

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It looks like the legend is still in header, not footer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry. Something "stuck" me. I don't know why I added legend before summary table.
Fixed in 7f863b9

Copy link
Contributor

Choose a reason for hiding this comment

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

I saw one case - we show log after summary table:


2025-01-29T17:51:04+06:00       INFO    Detected config files   num=0
2025-01-29T17:51:04+06:00       DEBUG   Specified ignore file does not exist    file=".trivyignore"
2025-01-29T17:51:04+06:00       DEBUG   [vex] VEX filtering is disabled

Report Summary

┌─────────────────┬──────┬─────────────────┬───────────────────┬─────────┬──────────┐
│     Target      │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │
├─────────────────┼──────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ gson-2.11.0.jar │ jar  │        2        │         -         │    -    │    -     │
└─────────────────┴──────┴─────────────────┴───────────────────┴─────────┴──────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)

2025-01-29T17:51:04+06:00       INFO    Table result includes only package filenames. Use '--format json' option to get the full path to the package file.

Java (jar)

Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 1, CRITICAL: 0)

┌───────────────────────────────────────────────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────────────┬─────────────────────────────────────────────────────┐
│                        Library                        │ Vulnerability  │ Severity │ Status │ Installed Version │     Fixed Version     │                        Title                        │
├───────────────────────────────────────────────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────────────┼─────────────────────────────────────────────────────┤
│ com.fasterxml.jackson.core:jackson-databind           │ CVE-2022-42003 │ HIGH     │ fixed  │ 2.13.4.1          │ 2.12.7.1, 2.13.4.2    │ jackson-databind: deep wrapper array nesting wrt    │
│ (gson-2.11.0.jar)                                     │                │          │        │                   │                       │ UNWRAP_SINGLE_VALUE_ARRAYS                          │
│                                                       │                │          │        │                   │                       │ https://avd.aquasec.com/nvd/cve-2022-42003          │
├───────────────────────────────────────────────────────┼────────────────┼──────────┤        ├───────────────────┼───────────────────────┼─────────────────────────────────────────────────────┤
│ org.apache.logging.log4j:log4j-core (gson-2.11.0.jar) │ CVE-2021-44832 │ MEDIUM   │        │ 2.17.0            │ 2.3.2, 2.12.4, 2.17.1 │ log4j-core: remote code execution via JDBC Appender │
│                                                       │                │          │        │                   │                       │ https://avd.aquasec.com/nvd/cve-2021-44832          │
└───────────────────────────────────────────────────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────────────┴─────────────────────────────────────────────────────┘

We might want to add a header (e.g. result tables) before result tables (after the legend) to separate summary table from other tables.

but on the other hand I think it's redundant.

├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ test (alpine 3.20.3) │ alpine │ 2 │ - │ - │ - │
├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ Java │ jar │ 2 │ - │ - │ - │
Copy link
Contributor

Choose a reason for hiding this comment

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

The summary table contains aggregated targets.
Do we want to split them by filePath in this PR or just start working on removing the aggregation (in another PR)?
cc. @knqyf263

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didn't split aggregated results, but, actually yes, we can split them in the table writer. I think we should do that in this PR. We will not need the logic after removing the aggregation, though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done - 84169b4 + 892d6de

@DmitriyLewen DmitriyLewen marked this pull request as ready for review January 20, 2025 04:45
@DmitriyLewen
Copy link
Contributor

I have 1 more question:
for case when Report doesn't include Results (e.g. scanned only wrong file).

Do we need to show empty table?

➜  ./trivy -q fs ./pkg/fanal/analyzer/language/nodejs/npm/testdata/sad        

Report Summary

┌────────┬──────┬─────────────────┬─────────┐
│ Target │ Type │ Vulnerabilities │ Secrets │
└────────┴──────┴─────────────────┴─────────┘

Or we can hide table and write Info/warn log or something else.

@knqyf263
Copy link
Collaborator Author

Showing only the header is probably unclear for users. I'd opt for log messages.

}

// showEmptyResultsWarning
func (tw Writer) showEmptyResultsWarning() {
Copy link
Contributor

Choose a reason for hiding this comment

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

examples:

2025-01-29T12:43:10+06:00       WARN    [report] Supported files for vuln scanner(s) not found.

2025-01-29T12:43:39+06:00       WARN    [report] No results found for secret scanner(s).

2025-01-29T12:43:42+06:00       WARN    [report] Supported files for vuln/misconfig scanner(s) not found. No results found for secret scanner(s).

2025-01-29T12:43:47+06:00       WARN    [report] Scanners are not enabled.


for _, pkg := range target.Packages {
for _, license := range pkg.Licenses {
osPkgLicenses = append(osPkgLicenses, toDetectedLicense(scanner, license, pkg.Name, ""))
if len(target.Packages) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

This check is required to hide OS Package licenses from summary table.
This is necessary to avoid confusion - when we show that we were looking for licenses for OS packages, but there were no OS packages in the scanned folder.

e.g.:

➜ ./trivy -d fs ./cmd --scanners license

Report Summary

Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)
┌───────────────────────┬──────┬──────────┐
│        Target         │ Type │ Licenses │
├───────────────────────┼──────┼──────────┤
│ OS Packages           │  -   │    0     │
└───────────────────────┴──────┴──────────┘

But on the other hand it will be a problem for scanning an image where we did not find OS packages.
because the user may think that we did not look for licenses for OS package files.

But the second case is more rare, so I chose this way.

Tell me - if you disagree or have other ideas

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actually, after we introduce the summary table, I don't think we have to show OS packages when Trivy is unable to find OS packages.

Copy link
Contributor

Choose a reason for hiding this comment

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

it make sense.
Removed in 28ebb24

Confidence: finding.Confidence,
Link: finding.Link,
})
if options.LicenseFull {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar case.

We only keep file licenses and dpkg licenses (but we remove them in applier) in this slice.

So the report can only include file licenses.
We don't need to show this result if the --liecense-full flag is not set.

@DmitriyLewen
Copy link
Contributor

Showing only the header is probably unclear for users. I'd opt for log messages.

Added log in 5f61893 + 0166a45

// "--so-summary" option is available only with "--format table".
if noSummaryTable && format != types.FormatTable {
noSummaryTable = false
log.Warn(`"--no-summary-table" can be used only with "--format table".`)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For example, suppose a flag has already been added for a long time, and we don't want to terminate a program for backward compatibility as it will change behavior. In that case, we have to make it a warning, but an error is basically better because the user may not see the warning message.

Suggested change
log.Warn(`"--no-summary-table" can be used only with "--format table".`)
return xerrors.New(`"--no-summary-table" can be used only with "--format table".`)

@@ -64,6 +80,69 @@ func (tw Writer) Write(_ context.Context, report types.Report) error {
return nil
}

func (tw Writer) renderSummary(report types.Report) error {
log.WithPrefix("report").Info("Report Summary table contains special symbols",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It looks like the legend is still in header, not footer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: display summary table
5 participants