Repository: aquasecurity/starboard
Pull request #463 (2021-03-26 17:39:18)

feat(cli): Show top 5 vulnerabilities by score in html report for namespace
Resolves https://github.com/aquasecurity/starboard/issues/459

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/463?src=pr&el=h1) Report
> Merging [#463](https://codecov.io/gh/aquasecurity/starboard/pull/463?src=pr&el=desc) (5cd4639) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/98d5ad2f3d14e8d2422b504f9c2620970d536580?el=desc) (98d5ad2) will **decrease** coverage by `36.60%`.
> The diff coverage is `8.51%`.

[![Impacted file tree graph](https://codecov.io/gh/aquasecurity/starboard/pull/463/graphs/tree.svg?width=650&height=150&src=pr&token=qqTGKAJ0Wb)](https://codecov.io/gh/aquasecurity/starboard/pull/463?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##             main     #463       +/-   ##
===========================================
- Coverage   69.17%   32.57%   -36.61%     
===========================================
  Files          57       56        -1     
  Lines        3478     3420       -58     
===========================================
- Hits         2406     1114     -1292     
- Misses        788     2236     +1448     
+ Partials      284       70      -214     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/463?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/report/html.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL3JlcG9ydC9odG1sLmdv) | `0.00% <0.00%> (ø)` | |
| [pkg/plugin/trivy/converter.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL3BsdWdpbi90cml2eS9jb252ZXJ0ZXIuZ28=) | `73.68% <33.33%> (-16.95%)` | :arrow_down: |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXQuZ28=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/find.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL2NtZC9maW5kLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/root.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL2NtZC9yb290Lmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/scan.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/polaris.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL2NtZC9wb2xhcmlzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/version.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL2NtZC92ZXJzaW9uLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/kube\_bench.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL2NtZC9rdWJlX2JlbmNoLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/kube\_hunter.go](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree#diff-cGtnL2NtZC9rdWJlX2h1bnRlci5nbw==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [34 more](https://codecov.io/gh/aquasecurity/starboard/pull/463/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/463?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/463?src=pr&el=footer). Last update [98d5ad2...5cd4639](https://codecov.io/gh/aquasecurity/starboard/pull/463?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/deploy/crd/vulnerabilityreports.crd.yaml b/deploy/crd/vulnerabilityreports.crd.yaml
index cb67a11..4b03b7f 100644
--- a/deploy/crd/vulnerabilityreports.crd.yaml
+++ b/deploy/crd/vulnerabilityreports.crd.yaml
@@ -106,6 +106,8 @@ spec:
                         type: string
                       fixedVersion:
                         type: string
+                      score:
+                        type: number
                       severity:
                         type: string
                         enum:
diff --git a/pkg/apis/aquasecurity/v1alpha1/vulnerability_types.go b/pkg/apis/aquasecurity/v1alpha1/vulnerability_types.go
index ea170b0..b213e67 100644
--- a/pkg/apis/aquasecurity/v1alpha1/vulnerability_types.go
+++ b/pkg/apis/aquasecurity/v1alpha1/vulnerability_types.go
@@ -186,6 +186,7 @@ var (
 														"title":       {Type: "string"},
 														"description": {Type: "string"},
 														"primaryLink": {Type: "string"},
+														"score":       {Type: "number"},
 														"links": {
 															Type: "array",
 															Items: &apiextensionsv1.JSONSchemaPropsOrArray{
@@ -261,6 +262,7 @@ type Vulnerability struct {
 	Description      string   `json:"description,omitempty"`
 	PrimaryLink      string   `json:"primaryLink,omitempty"`
 	Links            []string `json:"links"`
+	Score            *float64 `json:"score,omitempty"`
 }
 
 // +genclient
diff --git a/pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go
index 083c634..0031c90 100644
--- a/pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go
@@ -473,6 +473,11 @@ func (in *Vulnerability) DeepCopyInto(out *Vulnerability) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.Score != nil {
+		in, out := &in.Score, &out.Score
+		*out = new(float64)
+		**out = **in
+	}
 	return
 }
 
diff --git a/pkg/plugin/trivy/converter.go b/pkg/plugin/trivy/converter.go
index ea0bef2..c9cd676 100644
--- a/pkg/plugin/trivy/converter.go
+++ b/pkg/plugin/trivy/converter.go
@@ -52,6 +52,7 @@ func (c *converter) convert(imageRef string, reports []ScanReport) (v1alpha1.Vul
 				Title:            sr.Title,
 				PrimaryLink:      sr.PrimaryURL,
 				Links:            []string{},
+				Score:            GetScoreFromCvss(sr.Cvss),
 			})
 		}
 	}
@@ -124,3 +125,21 @@ func (c *converter) parseImageRef(imageRef string) (v1alpha1.Registry, v1alpha1.
 
 	return registry, artifact, nil
 }
+
+func GetScoreFromCvss(CVSSs map[string]*CVSS) *float64 {
+	var nvdScore, vendorScore *float64
+
+	for name, cvss := range CVSSs {
+		if name == "nvd" {
+			nvdScore = cvss.V3Score
+		} else {
+			vendorScore = cvss.V3Score
+		}
+	}
+
+	if vendorScore != nil {
+		return vendorScore
+	}
+
+	return nvdScore
+}
diff --git a/pkg/plugin/trivy/model.go b/pkg/plugin/trivy/model.go
index 94ed52e..665cd9d 100644
--- a/pkg/plugin/trivy/model.go
+++ b/pkg/plugin/trivy/model.go
@@ -20,6 +20,11 @@ type Vulnerability struct {
 	Layer            Layer             `json:"Layer"`
 	PrimaryURL       string            `json:"PrimaryURL"`
 	References       []string          `json:"References"`
+	Cvss             map[string]*CVSS  `json:"CVSS"`
+}
+
+type CVSS struct {
+	V3Score *float64 `json:"V3Score,omitempty"`
 }
 
 type Layer struct {
diff --git a/pkg/report/html.go b/pkg/report/html.go
index bc71849..5524dad 100644
--- a/pkg/report/html.go
+++ b/pkg/report/html.go
@@ -105,6 +105,7 @@ func (r *namespaceReporter) RetrieveData(namespace kube.Object) (templates.Names
 		GeneratedAt:          r.clock.Now(),
 		Top5VulnerableImages: r.topNImagesBySeverityCount(vulnerabilityReportList.Items, 5),
 		Top5FailedChecks:     r.topNFailedChecksByAffectedWorkloadsCount(configAuditReportList.Items, 5),
+		Top5Vulnerability:    r.topNVulnerabilitiesByScore(vulnerabilityReportList.Items, 5),
 	}, nil
 }
 
@@ -179,6 +180,54 @@ func (r *namespaceReporter) topNFailedChecksByAffectedWorkloadsCount(reports []v
 	return failedChecks[:ext.MinInt(N, len(failedChecks))]
 }
 
+func (r *namespaceReporter) topNVulnerabilitiesByScore(reports []v1alpha1.VulnerabilityReport, N int) []templates.VulnerabilityWithCount {
+	vulnerabilityMap := make(map[string]templates.VulnerabilityWithCount)
+
+	for _, report := range reports {
+		vulnMap := make(map[string]bool)
+		for _, vulnerability := range report.Report.Vulnerabilities {
+			vulnId := vulnerability.VulnerabilityID
+			if vulnMap[vulnId] {
+				continue
+			}
+			vulnMap[vulnId] = true
+
+			if _, ok := vulnerabilityMap[vulnerability.VulnerabilityID]; ok {
+				tempVuln := vulnerabilityMap[vulnId]
+				tempVuln.AffectedWorkloads++
+				vulnerabilityMap[vulnId] = tempVuln
+			} else {
+				if vulnerability.Score == nil {
+					continue
+				}
+
+				vulnerabilityMap[vulnId] = templates.VulnerabilityWithCount{
+					Vulnerability: v1alpha1.Vulnerability{
+						VulnerabilityID: vulnerability.VulnerabilityID,
+						PrimaryLink:     vulnerability.PrimaryLink,
+						Severity:        vulnerability.Severity,
+						Score:           vulnerability.Score,
+					},
+					AffectedWorkloads: 1,
+				}
+			}
+		}
+	}
+
+	vulnerabilities := make([]templates.VulnerabilityWithCount, len(vulnerabilityMap))
+	i := 0
+	for _, vulnerability := range vulnerabilityMap {
+		vulnerabilities[i] = vulnerability
+		i++
+	}
+
+	sort.SliceStable(vulnerabilities, func(i, j int) bool {
+		return *vulnerabilities[i].Score > *vulnerabilities[j].Score
+	})
+
+	return vulnerabilities[:ext.MinInt(N, len(vulnerabilities))]
+}
+
 func (r *namespaceReporter) Generate(namespace kube.Object, out io.Writer) error {
 	data, err := r.RetrieveData(namespace)
 	if err != nil {
diff --git a/pkg/report/templates/namespace_report.qtpl b/pkg/report/templates/namespace_report.qtpl
index b90e80a..c8ad311 100644
--- a/pkg/report/templates/namespace_report.qtpl
+++ b/pkg/report/templates/namespace_report.qtpl
@@ -72,6 +72,30 @@ Aqua Starboard Namespace Security Report - Namespace: {%s p.Namespace.Name %}
       </table>
     </div>
 
+  <h3>Top 5 vulnerabilities by score</h3>
+    <div class="row">
+      <table class="table table-sm table-bordered">
+        <thead>
+          <tr>
+            <th scope="col">ID</th>
+            <th scope="col">Severity</th>
+            <th scope="col">Score</th>
+            <th scope="col">Affected Workloads</th>
+          </tr>
+        </thead>
+        <tbody>
+        {% for _, vulnerability := range p.Top5Vulnerability %}
+        <tr>
+          <td><a href="{%s vulnerability.PrimaryLink %}">{%s vulnerability.VulnerabilityID %}</a></td>
+          <td>{%s string(vulnerability.Severity) %}</td>
+          <td>{%f *vulnerability.Score %}</td>
+          <td>{%d vulnerability.AffectedWorkloads %}</td>
+        </tr>
+        {% endfor %}
+        </tbody>
+      </table>
+    </div>
+
 </div>
 {% endfunc %}
 
diff --git a/pkg/report/templates/namespace_report.qtpl.go b/pkg/report/templates/namespace_report.qtpl.go
index 658c830..a51719c 100644
--- a/pkg/report/templates/namespace_report.qtpl.go
+++ b/pkg/report/templates/namespace_report.qtpl.go
@@ -197,162 +197,214 @@ func (p *NamespaceReport) StreamBody(qw422016 *qt422016.Writer) {
       </table>
     </div>
 
+  <h3>Top 5 vulnerabilities by score</h3>
+    <div class="row">
+      <table class="table table-sm table-bordered">
+        <thead>
+          <tr>
+            <th scope="col">ID</th>
+            <th scope="col">Severity</th>
+            <th scope="col">Score</th>
+            <th scope="col">Affected Workloads</th>
+          </tr>
+        </thead>
+        <tbody>
+        `)
+//line pkg/report/templates/namespace_report.qtpl:87
+	for _, vulnerability := range p.Top5Vulnerability {
+//line pkg/report/templates/namespace_report.qtpl:87
+		qw422016.N().S(`
+        <tr>
+          <td><a href="`)
+//line pkg/report/templates/namespace_report.qtpl:89
+		qw422016.E().S(vulnerability.PrimaryLink)
+//line pkg/report/templates/namespace_report.qtpl:89
+		qw422016.N().S(`">`)
+//line pkg/report/templates/namespace_report.qtpl:89
+		qw422016.E().S(vulnerability.VulnerabilityID)
+//line pkg/report/templates/namespace_report.qtpl:89
+		qw422016.N().S(`</a></td>
+          <td>`)
+//line pkg/report/templates/namespace_report.qtpl:90
+		qw422016.E().S(string(vulnerability.Severity))
+//line pkg/report/templates/namespace_report.qtpl:90
+		qw422016.N().S(`</td>
+          <td>`)
+//line pkg/report/templates/namespace_report.qtpl:91
+		qw422016.N().F(*vulnerability.Score)
+//line pkg/report/templates/namespace_report.qtpl:91
+		qw422016.N().S(`</td>
+          <td>`)
+//line pkg/report/templates/namespace_report.qtpl:92
+		qw422016.N().D(vulnerability.AffectedWorkloads)
+//line pkg/report/templates/namespace_report.qtpl:92
+		qw422016.N().S(`</td>
+        </tr>
+        `)
+//line pkg/report/templates/namespace_report.qtpl:94
+	}
+//line pkg/report/templates/namespace_report.qtpl:94
+	qw422016.N().S(`
+        </tbody>
+      </table>
+    </div>
+
 </div>
 `)
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 }
 
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 func (p *NamespaceReport) WriteBody(qq422016 qtio422016.Writer) {
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 	p.StreamBody(qw422016)
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 	qt422016.ReleaseWriter(qw422016)
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 }
 
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 func (p *NamespaceReport) Body() string {
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 	qb422016 := qt422016.AcquireByteBuffer()
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 	p.WriteBody(qb422016)
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 	qs422016 := string(qb422016.B)
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 	qt422016.ReleaseByteBuffer(qb422016)
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 	return qs422016
-//line pkg/report/templates/namespace_report.qtpl:76
+//line pkg/report/templates/namespace_report.qtpl:100
 }
 
-//line pkg/report/templates/namespace_report.qtpl:78
+//line pkg/report/templates/namespace_report.qtpl:102
 func streamimageReference(qw422016 *qt422016.Writer, registry v1alpha1.Registry, artifact v1alpha1.Artifact) {
-//line pkg/report/templates/namespace_report.qtpl:78
+//line pkg/report/templates/namespace_report.qtpl:102
 	qw422016.N().S(`
   `)
-//line pkg/report/templates/namespace_report.qtpl:79
+//line pkg/report/templates/namespace_report.qtpl:103
 	if artifact.Tag != "" && artifact.Digest != "" {
-//line pkg/report/templates/namespace_report.qtpl:79
+//line pkg/report/templates/namespace_report.qtpl:103
 		qw422016.N().S(`
     `)
-//line pkg/report/templates/namespace_report.qtpl:80
+//line pkg/report/templates/namespace_report.qtpl:104
 		qw422016.E().S(registry.Server)
-//line pkg/report/templates/namespace_report.qtpl:80
+//line pkg/report/templates/namespace_report.qtpl:104
 		qw422016.N().S(`/`)
-//line pkg/report/templates/namespace_report.qtpl:80
+//line pkg/report/templates/namespace_report.qtpl:104
 		qw422016.E().S(artifact.Repository)
-//line pkg/report/templates/namespace_report.qtpl:80
+//line pkg/report/templates/namespace_report.qtpl:104
 		qw422016.N().S(`:`)
-//line pkg/report/templates/namespace_report.qtpl:80
+//line pkg/report/templates/namespace_report.qtpl:104
 		qw422016.E().S(artifact.Tag)
-//line pkg/report/templates/namespace_report.qtpl:80
+//line pkg/report/templates/namespace_report.qtpl:104
 		qw422016.N().S(`@`)
-//line pkg/report/templates/namespace_report.qtpl:80
+//line pkg/report/templates/namespace_report.qtpl:104
 		qw422016.E().S(artifact.Digest)
-//line pkg/report/templates/namespace_report.qtpl:80
+//line pkg/report/templates/namespace_report.qtpl:104
 		qw422016.N().S(`
     `)
-//line pkg/report/templates/namespace_report.qtpl:81
+//line pkg/report/templates/namespace_report.qtpl:105
 		return
-//line pkg/report/templates/namespace_report.qtpl:82
+//line pkg/report/templates/namespace_report.qtpl:106
 	}
-//line pkg/report/templates/namespace_report.qtpl:82
+//line pkg/report/templates/namespace_report.qtpl:106
 	qw422016.N().S(`
 
   `)
-//line pkg/report/templates/namespace_report.qtpl:84
+//line pkg/report/templates/namespace_report.qtpl:108
 	if artifact.Tag == "" && artifact.Digest != "" {
-//line pkg/report/templates/namespace_report.qtpl:84
+//line pkg/report/templates/namespace_report.qtpl:108
 		qw422016.N().S(`
     `)
-//line pkg/report/templates/namespace_report.qtpl:85
+//line pkg/report/templates/namespace_report.qtpl:109
 		qw422016.E().S(registry.Server)
-//line pkg/report/templates/namespace_report.qtpl:85
+//line pkg/report/templates/namespace_report.qtpl:109
 		qw422016.N().S(`/`)
-//line pkg/report/templates/namespace_report.qtpl:85
+//line pkg/report/templates/namespace_report.qtpl:109
 		qw422016.E().S(artifact.Repository)
-//line pkg/report/templates/namespace_report.qtpl:85
+//line pkg/report/templates/namespace_report.qtpl:109
 		qw422016.N().S(`@`)
-//line pkg/report/templates/namespace_report.qtpl:85
+//line pkg/report/templates/namespace_report.qtpl:109
 		qw422016.E().S(artifact.Digest)
-//line pkg/report/templates/namespace_report.qtpl:85
+//line pkg/report/templates/namespace_report.qtpl:109
 		qw422016.N().S(`
     `)
-//line pkg/report/templates/namespace_report.qtpl:86
+//line pkg/report/templates/namespace_report.qtpl:110
 		return
-//line pkg/report/templates/namespace_report.qtpl:87
+//line pkg/report/templates/namespace_report.qtpl:111
 	}
-//line pkg/report/templates/namespace_report.qtpl:87
+//line pkg/report/templates/namespace_report.qtpl:111
 	qw422016.N().S(`
 
   `)
-//line pkg/report/templates/namespace_report.qtpl:89
+//line pkg/report/templates/namespace_report.qtpl:113
 	if artifact.Tag != "" && artifact.Digest == "" {
-//line pkg/report/templates/namespace_report.qtpl:89
+//line pkg/report/templates/namespace_report.qtpl:113
 		qw422016.N().S(`
     `)
-//line pkg/report/templates/namespace_report.qtpl:90
+//line pkg/report/templates/namespace_report.qtpl:114
 		qw422016.E().S(registry.Server)
-//line pkg/report/templates/namespace_report.qtpl:90
+//line pkg/report/templates/namespace_report.qtpl:114
 		qw422016.N().S(`/`)
-//line pkg/report/templates/namespace_report.qtpl:90
+//line pkg/report/templates/namespace_report.qtpl:114
 		qw422016.E().S(artifact.Repository)
-//line pkg/report/templates/namespace_report.qtpl:90
+//line pkg/report/templates/namespace_report.qtpl:114
 		qw422016.N().S(`:`)
-//line pkg/report/templates/namespace_report.qtpl:90
+//line pkg/report/templates/namespace_report.qtpl:114
 		qw422016.E().S(artifact.Tag)
-//line pkg/report/templates/namespace_report.qtpl:90
+//line pkg/report/templates/namespace_report.qtpl:114
 		qw422016.N().S(`
     `)
-//line pkg/report/templates/namespace_report.qtpl:91
+//line pkg/report/templates/namespace_report.qtpl:115
 		return
-//line pkg/report/templates/namespace_report.qtpl:92
+//line pkg/report/templates/namespace_report.qtpl:116
 	}
-//line pkg/report/templates/namespace_report.qtpl:92
+//line pkg/report/templates/namespace_report.qtpl:116
 	qw422016.N().S(`
 
   `)
-//line pkg/report/templates/namespace_report.qtpl:94
+//line pkg/report/templates/namespace_report.qtpl:118
 	qw422016.E().S(registry.Server)
-//line pkg/report/templates/namespace_report.qtpl:94
+//line pkg/report/templates/namespace_report.qtpl:118
 	qw422016.N().S(`/`)
-//line pkg/report/templates/namespace_report.qtpl:94
+//line pkg/report/templates/namespace_report.qtpl:118
 	qw422016.E().S(artifact.Repository)
-//line pkg/report/templates/namespace_report.qtpl:94
+//line pkg/report/templates/namespace_report.qtpl:118
 	qw422016.N().S(`:`)
-//line pkg/report/templates/namespace_report.qtpl:94
+//line pkg/report/templates/namespace_report.qtpl:118
 	qw422016.E().S(artifact.Tag)
-//line pkg/report/templates/namespace_report.qtpl:94
+//line pkg/report/templates/namespace_report.qtpl:118
 	qw422016.N().S(`
 `)
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 }
 
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 func writeimageReference(qq422016 qtio422016.Writer, registry v1alpha1.Registry, artifact v1alpha1.Artifact) {
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 	streamimageReference(qw422016, registry, artifact)
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 	qt422016.ReleaseWriter(qw422016)
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 }
 
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 func imageReference(registry v1alpha1.Registry, artifact v1alpha1.Artifact) string {
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 	qb422016 := qt422016.AcquireByteBuffer()
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 	writeimageReference(qb422016, registry, artifact)
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 	qs422016 := string(qb422016.B)
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 	qt422016.ReleaseByteBuffer(qb422016)
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 	return qs422016
-//line pkg/report/templates/namespace_report.qtpl:95
+//line pkg/report/templates/namespace_report.qtpl:119
 }
diff --git a/pkg/report/templates/types.go b/pkg/report/templates/types.go
index 913e772..39931dc 100644
--- a/pkg/report/templates/types.go
+++ b/pkg/report/templates/types.go
@@ -26,6 +26,12 @@ type NamespaceReport struct {
 
 	Top5VulnerableImages []v1alpha1.VulnerabilityReport
 	Top5FailedChecks     []CheckWithCount
+	Top5Vulnerability    []VulnerabilityWithCount
+}
+
+type VulnerabilityWithCount struct {
+	v1alpha1.Vulnerability
+	AffectedWorkloads int
 }
 
 type CheckWithCount struct {


Test patch:
diff --git a/pkg/plugin/trivy/converter_test.go b/pkg/plugin/trivy/converter_test.go
index 4391d4c..ba94f85 100644
--- a/pkg/plugin/trivy/converter_test.go
+++ b/pkg/plugin/trivy/converter_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/pointer"
 )
 
 var (
@@ -165,3 +166,69 @@ func TestConverter_Convert(t *testing.T) {
 	}
 
 }
+
+func TestGetScoreFromCvss(t *testing.T) {
+	testCases := []struct {
+		name          string
+		cvss          map[string]*trivy.CVSS
+		expectedScore *float64
+	}{
+		{
+			name: "Should return vendor score when vendor v3 score exist",
+			cvss: map[string]*trivy.CVSS{
+				"nvd": {
+					V3Score: pointer.Float64Ptr(8.1),
+				},
+				"redhat": {
+					V3Score: pointer.Float64Ptr(8.3),
+				},
+			},
+			expectedScore: pointer.Float64Ptr(8.3),
+		},
+		{
+			name: "Should return nvd score when vendor v3 score is nil",
+			cvss: map[string]*trivy.CVSS{
+				"nvd": {
+					V3Score: pointer.Float64Ptr(8.1),
+				},
+				"redhat": {
+					V3Score: nil,
+				},
+			},
+			expectedScore: pointer.Float64Ptr(8.1),
+		},
+		{
+			name: "Should return nvd score when vendor doesn't exist",
+			cvss: map[string]*trivy.CVSS{
+				"nvd": {
+					V3Score: pointer.Float64Ptr(8.1),
+				},
+			},
+			expectedScore: pointer.Float64Ptr(8.1),
+		},
+		{
+			name: "Should return nil when vendor and nvd both v3 scores are nil",
+			cvss: map[string]*trivy.CVSS{
+				"nvd": {
+					V3Score: nil,
+				},
+				"redhat": {
+					V3Score: nil,
+				},
+			},
+			expectedScore: nil,
+		},
+		{
+			name:          "Should return nil when cvss doesn't exist",
+			cvss:          map[string]*trivy.CVSS{},
+			expectedScore: nil,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			score := trivy.GetScoreFromCvss(tc.cvss)
+			assert.Equal(t, tc.expectedScore, score)
+		})
+	}
+}
diff --git a/pkg/report/html_test.go b/pkg/report/html_test.go
index ee60a74..b0bd3c7 100644
--- a/pkg/report/html_test.go
+++ b/pkg/report/html_test.go
@@ -3,6 +3,13 @@ package report
 import (
 	"testing"
 
+	"github.com/aquasecurity/starboard/pkg/report/templates"
+
+	"k8s.io/utils/pointer"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	starboard "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 )
 
@@ -113,3 +120,211 @@ func TestHTMLReporter_GenerateReport(t *testing.T) {
 	//assert.Contains(t, htmlReportStr, "Trivy")
 	//assert.Contains(t, htmlReportStr, "index.docker.io")
 }
+
+func Test_topNVulnerabilitiesByScore(t *testing.T) {
+	testCases := []struct {
+		name           string
+		reports        []v1alpha1.VulnerabilityReport
+		expectedOutput []templates.VulnerabilityWithCount
+	}{
+		{
+			name: "Should return top 5 vulnerabilities with count",
+			reports: []starboard.VulnerabilityReport{
+				{
+					Report: starboard.VulnerabilityScanResult{
+						Vulnerabilities: []starboard.Vulnerability{
+							{
+								VulnerabilityID: "CVE-2019-1549",
+								Severity:        starboard.SeverityCritical,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1549",
+								Score:           pointer.Float64Ptr(8.2),
+							},
+							{
+								VulnerabilityID: "CVE-2019-1547",
+								Severity:        starboard.SeverityLow,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1547",
+								Score:           pointer.Float64Ptr(6.3),
+							},
+							{
+								VulnerabilityID: "CVE-2011-3374",
+								Severity:        starboard.SeverityMedium,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2011-3374",
+								Score:           pointer.Float64Ptr(3.7),
+							},
+						},
+					},
+				},
+				{
+					Report: starboard.VulnerabilityScanResult{
+						Vulnerabilities: []starboard.Vulnerability{
+							{
+								VulnerabilityID: "CVE-2019-1548",
+								Severity:        starboard.SeverityCritical,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1548",
+								Score:           pointer.Float64Ptr(9.1),
+							},
+							{
+								VulnerabilityID: "CVE-2019-1547",
+								Severity:        starboard.SeverityLow,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1547",
+								Score:           pointer.Float64Ptr(6.3),
+							},
+							{
+								VulnerabilityID: "CVE-2020-27350",
+								Severity:        starboard.SeverityMedium,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2020-27350",
+								Score:           pointer.Float64Ptr(5.7),
+							},
+							{
+								VulnerabilityID: "CVE-2011-3374",
+								Severity:        starboard.SeverityMedium,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2011-3374",
+								Score:           pointer.Float64Ptr(3.7),
+							},
+							{
+								VulnerabilityID: "CVE-2011-3375",
+								Severity:        starboard.SeverityMedium,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2011-3374",
+								Score:           pointer.Float64Ptr(3.6),
+							},
+						},
+					},
+				},
+			},
+			expectedOutput: []templates.VulnerabilityWithCount{
+				{
+					Vulnerability: starboard.Vulnerability{
+						VulnerabilityID: "CVE-2019-1548",
+						Severity:        starboard.SeverityCritical,
+						PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1548",
+						Score:           pointer.Float64Ptr(9.1),
+					},
+					AffectedWorkloads: 1,
+				},
+				{
+					Vulnerability: starboard.Vulnerability{
+						VulnerabilityID: "CVE-2019-1549",
+						Severity:        starboard.SeverityCritical,
+						PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1549",
+						Score:           pointer.Float64Ptr(8.2),
+					},
+					AffectedWorkloads: 1,
+				},
+				{
+					Vulnerability: starboard.Vulnerability{
+						VulnerabilityID: "CVE-2019-1547",
+						Severity:        starboard.SeverityLow,
+						PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1547",
+						Score:           pointer.Float64Ptr(6.3),
+					},
+					AffectedWorkloads: 2,
+				},
+				{
+					Vulnerability: starboard.Vulnerability{
+						VulnerabilityID: "CVE-2020-27350",
+						Severity:        starboard.SeverityMedium,
+						PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2020-27350",
+						Score:           pointer.Float64Ptr(5.7),
+					},
+					AffectedWorkloads: 1,
+				},
+				{
+					Vulnerability: starboard.Vulnerability{
+						VulnerabilityID: "CVE-2011-3374",
+						Severity:        starboard.SeverityMedium,
+						PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2011-3374",
+						Score:           pointer.Float64Ptr(3.7),
+					},
+					AffectedWorkloads: 2,
+				},
+			},
+		},
+		{
+			name: "Should return 2 vulnerabilities with count when some input has nil scores",
+			reports: []starboard.VulnerabilityReport{
+				{
+					Report: starboard.VulnerabilityScanResult{
+						Vulnerabilities: []starboard.Vulnerability{
+							{
+								VulnerabilityID: "CVE-2019-1549",
+								Severity:        starboard.SeverityCritical,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1549",
+								Score:           pointer.Float64Ptr(8.2),
+							},
+							{
+								VulnerabilityID: "CVE-2019-1547",
+								Severity:        starboard.SeverityLow,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1547",
+								Score:           nil,
+							},
+							{
+								VulnerabilityID: "CVE-2011-3374",
+								Severity:        starboard.SeverityMedium,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2011-3374",
+								Score:           pointer.Float64Ptr(3.7),
+							},
+						},
+					},
+				},
+				{
+					Report: starboard.VulnerabilityScanResult{
+						Vulnerabilities: []starboard.Vulnerability{
+							{
+								VulnerabilityID: "CVE-2019-1548",
+								Severity:        starboard.SeverityCritical,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1548",
+								Score:           nil,
+							},
+							{
+								VulnerabilityID: "CVE-2019-1547",
+								Severity:        starboard.SeverityLow,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1547",
+								Score:           nil,
+							},
+							{
+								VulnerabilityID: "CVE-2020-27350",
+								Severity:        starboard.SeverityMedium,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2020-27350",
+								Score:           nil,
+							},
+							{
+								VulnerabilityID: "CVE-2011-3374",
+								Severity:        starboard.SeverityMedium,
+								PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2011-3374",
+								Score:           pointer.Float64Ptr(3.7),
+							},
+						},
+					},
+				},
+			},
+			expectedOutput: []templates.VulnerabilityWithCount{
+				{
+					Vulnerability: starboard.Vulnerability{
+						VulnerabilityID: "CVE-2019-1549",
+						Severity:        starboard.SeverityCritical,
+						PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2019-1549",
+						Score:           pointer.Float64Ptr(8.2),
+					},
+					AffectedWorkloads: 1,
+				},
+				{
+					Vulnerability: starboard.Vulnerability{
+						VulnerabilityID: "CVE-2011-3374",
+						Severity:        starboard.SeverityMedium,
+						PrimaryLink:     "https://avd.aquasec.com/nvd/cve-2011-3374",
+						Score:           pointer.Float64Ptr(3.7),
+					},
+					AffectedWorkloads: 2,
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		r := namespaceReporter{}
+		t.Run(tc.name, func(t *testing.T) {
+			vulWithCount := r.topNVulnerabilitiesByScore(tc.reports, 5)
+			assert.Equal(t, tc.expectedOutput, vulWithCount)
+		})
+	}
+}