Skip to content

Commit 6fafbeb

Browse files
authored
fix(rootio): fix severity selection (#9181)
1 parent aa944cc commit 6fafbeb

File tree

10 files changed

+193
-28
lines changed

10 files changed

+193
-28
lines changed

docs/docs/coverage/others/rootio.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Root.io patches are detected when Trivy finds packages with specific version suf
1313
When Root.io patches are detected, Trivy automatically switches to Root.io scanning mode for vulnerability detection.
1414
Even when the original OS distributor (Debian, Ubuntu, Alpine) has not provided a patch for a vulnerability, Trivy will display Root.io patches if they are available.
1515

16+
!!! note
17+
For vulnerabilities, Trivy uses the severity level from the original OS vendor (if the vendor has specified a severity).
18+
1619
For detailed information about supported scanners, features, and functionality, please refer to the documentation for the underlying OS:
1720

1821
- [Debian](../os/debian.md)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ require (
2424
github.com/aquasecurity/testdocker v0.0.0-20250616060700-ba6845ac6d17
2525
github.com/aquasecurity/tml v0.6.1
2626
github.com/aquasecurity/trivy-checks v1.11.3-0.20250604022615-9a7efa7c9169
27-
github.com/aquasecurity/trivy-db v0.0.0-20250627124416-ca81c496a932
27+
github.com/aquasecurity/trivy-db v0.0.0-20250716122853-45f09ec4df9c
2828
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48
2929
github.com/aquasecurity/trivy-kubernetes v0.9.0
3030
github.com/aws/aws-sdk-go-v2 v1.36.5

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -829,8 +829,8 @@ github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gw
829829
github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY=
830830
github.com/aquasecurity/trivy-checks v1.11.3-0.20250604022615-9a7efa7c9169 h1:TckzIxUX7lZaU9f2lNxCN0noYYP8fzmSQf6a4JdV83w=
831831
github.com/aquasecurity/trivy-checks v1.11.3-0.20250604022615-9a7efa7c9169/go.mod h1:nT69xgRcBD4NlHwTBpWMYirpK5/Zpl8M+XDOgmjMn2k=
832-
github.com/aquasecurity/trivy-db v0.0.0-20250627124416-ca81c496a932 h1:5GKQ53uGGHEEtZ/FX94jcIdfEGeyiHZ7tmJ5nCtDz5c=
833-
github.com/aquasecurity/trivy-db v0.0.0-20250627124416-ca81c496a932/go.mod h1:Ubl2YWA6Zg7eaojg4MDmeDdYU4+PiGPsnwo6B5UIwqw=
832+
github.com/aquasecurity/trivy-db v0.0.0-20250716122853-45f09ec4df9c h1:nWJKidnaCx50H0JvqzCQNr0Ew9sUO0QcnGSeDJajmvc=
833+
github.com/aquasecurity/trivy-db v0.0.0-20250716122853-45f09ec4df9c/go.mod h1:upAJqDQkN5FdIJbtJMpokncGNhYAPGkpoCbaGciWPt4=
834834
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI=
835835
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8=
836836
github.com/aquasecurity/trivy-kubernetes v0.9.0 h1:rp8RuXwKfFWUPR/ULksA2WpD0z6rslVkzLmPGQr61Wc=

pkg/detector/ospkg/rootio/rootio.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package rootio
22

33
import (
4+
"cmp"
45
"context"
56
"strings"
67

@@ -81,7 +82,7 @@ func (s *Scanner) Detect(ctx context.Context, osVer string, _ *ftypes.Repository
8182
if !s.isVulnerable(ctx, utils.FormatSrcVersion(pkg), adv) {
8283
continue
8384
}
84-
vulns = append(vulns, types.DetectedVulnerability{
85+
vuln := types.DetectedVulnerability{
8586
VulnerabilityID: adv.VulnerabilityID,
8687
PkgID: pkg.ID,
8788
PkgName: pkg.Name,
@@ -91,7 +92,20 @@ func (s *Scanner) Detect(ctx context.Context, osVer string, _ *ftypes.Repository
9192
PkgIdentifier: pkg.Identifier,
9293
Custom: adv.Custom,
9394
DataSource: adv.DataSource,
94-
})
95+
}
96+
97+
// We add the severity from the base OS, so we need to keep the severity level from the base OS (as SeveritySource).
98+
if adv.Severity != dbTypes.SeverityUnknown {
99+
vuln.Vulnerability = dbTypes.Vulnerability{
100+
Severity: adv.Severity.String(),
101+
}
102+
103+
// Datasource contains BaseID + ID for root.io advisories,
104+
// But baseOS (e.g. Debian) advisories use ID only.
105+
vuln.SeveritySource = cmp.Or(adv.DataSource.BaseID, adv.DataSource.ID)
106+
}
107+
108+
vulns = append(vulns, vuln)
95109
}
96110
}
97111
return vulns, nil

pkg/detector/ospkg/rootio/rootio_test.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,33 @@ func TestScanner_Detect(t *testing.T) {
4848
want: []types.DetectedVulnerability{
4949
{
5050
PkgName: "openssl",
51-
VulnerabilityID: "CVE-2024-13176",
51+
VulnerabilityID: "CVE-2024-13176", // Debian and Root.io contain this CVE
5252
InstalledVersion: "3.0.15-1~deb12u1.root.io.0",
5353
FixedVersion: "3.0.15-1~deb12u1.root.io.1, 3.0.16-1~deb12u1",
54+
SeveritySource: vulnerability.Debian,
5455
DataSource: &dbTypes.DataSource{
55-
ID: vulnerability.RootIO,
56-
Name: "Root.io Security Patches",
57-
URL: "https://api.root.io/external/patch_feed",
56+
ID: vulnerability.RootIO,
57+
BaseID: vulnerability.Debian,
58+
Name: "Root.io Security Patches (debian)",
59+
URL: "https://api.root.io/external/patch_feed",
60+
},
61+
Vulnerability: dbTypes.Vulnerability{
62+
Severity: dbTypes.SeverityMedium.String(),
63+
},
64+
},
65+
{
66+
PkgName: "openssl",
67+
VulnerabilityID: "CVE-2025-27587", // Debian only contains this CVE
68+
InstalledVersion: "3.0.15-1~deb12u1.root.io.0",
69+
FixedVersion: "3.0.16-1~deb12u1",
70+
SeveritySource: vulnerability.Debian,
71+
DataSource: &dbTypes.DataSource{
72+
ID: vulnerability.Debian,
73+
Name: "Debian Security Tracker",
74+
URL: "https://salsa.debian.org/security-tracker-team/security-tracker",
75+
},
76+
Vulnerability: dbTypes.Vulnerability{
77+
Severity: dbTypes.SeverityLow.String(),
5878
},
5979
},
6080
},
@@ -84,9 +104,10 @@ func TestScanner_Detect(t *testing.T) {
84104
InstalledVersion: "1.22.1-9+deb12u2.root.io.0",
85105
FixedVersion: "1.22.1-9+deb12u2.root.io.1",
86106
DataSource: &dbTypes.DataSource{
87-
ID: vulnerability.RootIO,
88-
Name: "Root.io Security Patches",
89-
URL: "https://api.root.io/external/patch_feed",
107+
ID: vulnerability.RootIO,
108+
BaseID: vulnerability.Ubuntu,
109+
Name: "Root.io Security Patches (ubuntu)",
110+
URL: "https://api.root.io/external/patch_feed",
90111
},
91112
},
92113
},
@@ -116,9 +137,10 @@ func TestScanner_Detect(t *testing.T) {
116137
InstalledVersion: "643-r00072",
117138
FixedVersion: "643-r10072",
118139
DataSource: &dbTypes.DataSource{
119-
ID: vulnerability.RootIO,
120-
Name: "Root.io Security Patches",
121-
URL: "https://api.root.io/external/patch_feed",
140+
ID: vulnerability.RootIO,
141+
BaseID: vulnerability.Alpine,
142+
Name: "Root.io Security Patches (alpine)",
143+
URL: "https://api.root.io/external/patch_feed",
122144
},
123145
},
124146
},
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
- bucket: data-source
22
pairs:
3+
- key: debian 12
4+
value:
5+
ID: "debian"
6+
Name: "Debian Security Tracker"
7+
URL: "https://salsa.debian.org/security-tracker-team/security-tracker"
38
- key: root.io debian 12
49
value:
510
ID: "rootio"
6-
Name: "Root.io Security Patches"
11+
BaseID: "debian"
12+
Name: "Root.io Security Patches (debian)"
713
URL: "https://api.root.io/external/patch_feed"
814
- key: root.io ubuntu 20.04
915
value:
1016
ID: "rootio"
11-
Name: "Root.io Security Patches"
17+
BaseID: "ubuntu"
18+
Name: "Root.io Security Patches (ubuntu)"
1219
URL: "https://api.root.io/external/patch_feed"
1320
- key: root.io alpine 3.19
1421
value:
1522
ID: "rootio"
16-
Name: "Root.io Security Patches"
23+
BaseID: "alpine"
24+
Name: "Root.io Security Patches (alpine)"
1725
URL: "https://api.root.io/external/patch_feed"

pkg/detector/ospkg/rootio/testdata/fixtures/rootio.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
- bucket: debian 12
2+
pairs:
3+
- bucket: openssl
4+
pairs:
5+
- key: CVE-2024-13176
6+
value:
7+
VulnerableVersions:
8+
- "<3.0.16-1~deb12u1"
9+
PatchedVersions:
10+
- "3.0.16-1~deb12u1"
11+
Severity: 2
12+
- key: CVE-2025-27587
13+
value:
14+
VulnerableVersions:
15+
- "<3.0.16-1~deb12u1"
16+
PatchedVersions:
17+
- "3.0.16-1~deb12u1"
18+
Severity: 1
119
- bucket: root.io debian 12
220
pairs:
321
- bucket: openssl

pkg/vulnerability/testdata/fixtures/vulnerability.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@
6464
ghsa: 3
6565
References:
6666
- http://example.com
67+
- key: CVE-2023-0001
68+
value:
69+
Title: dos
70+
Description: dos vulnerability
71+
VendorSeverity:
72+
nvd: 1
73+
alpine: 2
74+
ubuntu: 3
75+
References:
76+
- http://example.com
6777
- key: GHSA-0000-aaaa-1111
6878
value:
6979
Title: dos
@@ -72,4 +82,4 @@
7282
nvd: 1
7383
ghsa: 3
7484
References:
75-
- http://example.com
85+
- http://example.com

pkg/vulnerability/vulnerability.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package vulnerability
22

33
import (
4+
"cmp"
45
"slices"
56
"strings"
67
"sync"
@@ -91,8 +92,12 @@ func (c Client) FillInfo(vulns []types.DetectedVulnerability, severitySources []
9192
// Detect the data source
9293
dataSource := lo.FromPtr(vulns[i].DataSource)
9394

95+
// To determine severity, use BaseID if available, otherwise use ID.
96+
// e.g. `debian` severity for root.io advisories.
97+
dataSourceID := cmp.Or(dataSource.BaseID, dataSource.ID)
98+
9499
// Select the severity according to the detected sourceID.
95-
severity, severitySource := c.getSeverity(vulnID, &vuln, dataSource, severitySources)
100+
severity, severitySource := c.getSeverity(vulnID, &vuln, dataSourceID, severitySources)
96101
if severity == dbTypes.SeverityUnknown.String() {
97102
noSeverityIDs = append(noSeverityIDs, vulnID)
98103
}
@@ -126,10 +131,10 @@ func (c Client) FillInfo(vulns []types.DetectedVulnerability, severitySources []
126131
}
127132
}
128133

129-
func (c Client) getSeverity(vulnID string, vuln *dbTypes.Vulnerability, dataSource dbTypes.DataSource, severitySources []dbTypes.SourceID) (string, dbTypes.SourceID) {
134+
func (c Client) getSeverity(vulnID string, vuln *dbTypes.Vulnerability, dataSourceID dbTypes.SourceID, severitySources []dbTypes.SourceID) (string, dbTypes.SourceID) {
130135
for _, source := range severitySources {
131136
if source == "auto" {
132-
return c.autoDetectSeverity(vulnID, vuln, dataSource)
137+
return c.autoDetectSeverity(vulnID, vuln, dataSourceID)
133138
}
134139

135140
if severity, ok := vuln.VendorSeverity[source]; ok {
@@ -149,19 +154,19 @@ func (c Client) getSeverity(vulnID string, vuln *dbTypes.Vulnerability, dataSour
149154
// 3. Use the severity from NVD as a fallback.
150155
// 4. Try severities from other data sources (e.g., Debian severity for Red Hat distributions).
151156
// 5. If no severity is found from any data source, return "UNKNOWN".
152-
func (c Client) autoDetectSeverity(vulnID string, vuln *dbTypes.Vulnerability, dataSource dbTypes.DataSource) (string, dbTypes.SourceID) {
153-
autoSeveritySrcs := []dbTypes.SourceID{dataSource.ID, vulnerability.NVD}
154-
if vs, ok := vuln.VendorSeverity[dataSource.ID]; ok {
155-
return vs.String(), dataSource.ID
157+
func (c Client) autoDetectSeverity(vulnID string, vuln *dbTypes.Vulnerability, dataSourceID dbTypes.SourceID) (string, dbTypes.SourceID) {
158+
autoSeveritySrcs := []dbTypes.SourceID{dataSourceID, vulnerability.NVD}
159+
if vs, ok := vuln.VendorSeverity[dataSourceID]; ok {
160+
return vs.String(), dataSourceID
156161
}
157162

158163
// use severity from GitHub for all GHSA-xxx vulnerabilities
159164
if strings.HasPrefix(vulnID, "GHSA-") {
160165
// use severity from GitHub for all GHSA-IDs
161-
autoSeveritySrcs = []dbTypes.SourceID{dataSource.ID, vulnerability.GHSA, vulnerability.NVD}
166+
autoSeveritySrcs = []dbTypes.SourceID{dataSourceID, vulnerability.GHSA, vulnerability.NVD}
162167
}
163168

164-
if severity, severitySource := c.getSeverity(vulnID, vuln, dataSource, autoSeveritySrcs); severity != dbTypes.SeverityUnknown.String() {
169+
if severity, severitySource := c.getSeverity(vulnID, vuln, dataSourceID, autoSeveritySrcs); severity != dbTypes.SeverityUnknown.String() {
165170
return severity, severitySource
166171
}
167172

pkg/vulnerability/vulnerability_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,91 @@ func TestClient_FillInfo(t *testing.T) {
371371
},
372372
},
373373
},
374+
{
375+
name: "happy path. Use alpine severity for root.io vulnerability",
376+
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
377+
vulns: []types.DetectedVulnerability{
378+
{
379+
VulnerabilityID: "CVE-2023-0001",
380+
DataSource: &dbTypes.DataSource{
381+
ID: vulnerability.RootIO,
382+
BaseID: vulnerability.Alpine,
383+
Name: "Root.io Security Patches (alpine)",
384+
URL: "https://api.root.io/external/patch_feed",
385+
},
386+
},
387+
},
388+
expectedVulnerabilities: []types.DetectedVulnerability{
389+
{
390+
VulnerabilityID: "CVE-2023-0001",
391+
Status: dbTypes.StatusAffected,
392+
SeveritySource: vulnerability.Alpine,
393+
DataSource: &dbTypes.DataSource{
394+
ID: vulnerability.RootIO,
395+
BaseID: vulnerability.Alpine,
396+
Name: "Root.io Security Patches (alpine)",
397+
URL: "https://api.root.io/external/patch_feed",
398+
},
399+
Vulnerability: dbTypes.Vulnerability{
400+
Title: "dos",
401+
Description: "dos vulnerability",
402+
Severity: dbTypes.SeverityMedium.String(),
403+
References: []string{"http://example.com"},
404+
VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{
405+
"nvd": dbTypes.SeverityLow,
406+
"alpine": dbTypes.SeverityMedium,
407+
"ubuntu": dbTypes.SeverityHigh,
408+
},
409+
},
410+
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2023-0001",
411+
},
412+
},
413+
},
414+
{
415+
name: "happy path. Use debian severity for root.io vulnerability",
416+
fixtures: []string{"testdata/fixtures/vulnerability.yaml"},
417+
vulns: []types.DetectedVulnerability{
418+
{
419+
VulnerabilityID: "CVE-2023-0001",
420+
SeveritySource: vulnerability.Debian,
421+
DataSource: &dbTypes.DataSource{
422+
ID: vulnerability.RootIO,
423+
BaseID: vulnerability.Debian,
424+
Name: "Root.io Security Patches (debian)",
425+
URL: "https://api.root.io/external/patch_feed",
426+
},
427+
Vulnerability: dbTypes.Vulnerability{
428+
Severity: dbTypes.SeverityCritical.String(),
429+
},
430+
},
431+
},
432+
expectedVulnerabilities: []types.DetectedVulnerability{
433+
{
434+
VulnerabilityID: "CVE-2023-0001",
435+
Status: dbTypes.StatusAffected,
436+
SeveritySource: vulnerability.Debian,
437+
DataSource: &dbTypes.DataSource{
438+
ID: vulnerability.RootIO,
439+
BaseID: vulnerability.Debian,
440+
Name: "Root.io Security Patches (debian)",
441+
URL: "https://api.root.io/external/patch_feed",
442+
},
443+
Vulnerability: dbTypes.Vulnerability{
444+
Title: "dos",
445+
Description: "dos vulnerability",
446+
Severity: dbTypes.SeverityCritical.String(),
447+
References: []string{"http://example.com"},
448+
VendorSeverity: map[dbTypes.SourceID]dbTypes.Severity{
449+
"nvd": dbTypes.SeverityLow,
450+
"alpine": dbTypes.SeverityMedium,
451+
"ubuntu": dbTypes.SeverityHigh,
452+
"debian": dbTypes.SeverityCritical,
453+
},
454+
},
455+
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2023-0001",
456+
},
457+
},
458+
},
374459
{
375460
name: "GetVulnerability returns an error",
376461
fixtures: []string{"testdata/fixtures/sad.yaml"},

0 commit comments

Comments
 (0)