Skip to content

Commit 1cfd296

Browse files
committed
feat(bottlerocket): add vulnerability matching for Bottlerocket OS
Enable vulnerability detection by matching installed package versions from the Bottlerocket software inventory against security advisories stored in trivy-db, using RPM version comparison. - Add Release field in the inventory analyzer output - Implement the scanner using go-rpm-version for epoch:version-release comparison against advisory fixed versions - Add Bottlerocket to the OSTypes validation list - Update coverage documentation
1 parent b8a1ccd commit 1cfd296

15 files changed

Lines changed: 379 additions & 35 deletions

File tree

.github/workflows/semantic-pr.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
# OS
5353
activestate
5454
alpine
55+
bottlerocket
5556
wolfi
5657
chainguard
5758
redhat

docs/guide/coverage/os/bottlerocket.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Trivy supports the following scanners for OS packages.
44
| Scanner | Supported |
55
| :-----------: | :-------: |
66
| SBOM ||
7-
| Vulnerability | - |
7+
| Vulnerability | |
88
| License | - |
99

1010
Please see [here](index.md#supported-os) for supported versions.
@@ -18,4 +18,12 @@ The table below outlines the features offered by Trivy.
1818
## SBOM
1919
Trivy detects packages that are listed in the [software inventory].
2020

21+
## Vulnerability
22+
Trivy checks for vulnerabilities using the [Bottlerocket security advisories][advisories],
23+
comparing installed package versions from the software inventory against known fixed versions.
24+
25+
### Data Source
26+
[Bottlerocket Security Advisories][advisories]
27+
2128
[software inventory]: https://bottlerocket.dev/en/os/1.37.x/concepts/variants/#software-inventory
29+
[advisories]: https://advisories.bottlerocket.aws/

docs/guide/scanner/vulnerability.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ See [here](../coverage/os/index.md#supported-os) for the supported OSes.
2323
|---------------------------|--------------------------------------------------------------|
2424
| Arch Linux | [Vulnerable Issues][arch] |
2525
| Alpine Linux | [secdb][alpine] |
26+
| Bottlerocket | [Bottlerocket Security Advisories][bottlerocket] |
2627
| Wolfi Linux | [secdb][wolfi] |
2728
| Chainguard | [secdb][chainguard] |
2829
| MinimOS | [secdb][minimos] |
@@ -400,6 +401,7 @@ Example logic for the following vendor severity levels when scanning an Alpine i
400401
401402
[arch]: https://security.archlinux.org/
402403
[alpine]: https://secdb.alpinelinux.org/
404+
[bottlerocket]: https://advisories.bottlerocket.aws/
403405
[wolfi]: https://packages.wolfi.dev/os/security.json
404406
[chainguard]: https://packages.cgr.dev/chainguard/security.json
405407
[minimos]: https://packages.mini.dev/advisories/secdb/security.json

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,3 +509,5 @@ tool (
509509
golang.org/x/tools/cmd/goyacc
510510
sigs.k8s.io/kind
511511
)
512+
513+
replace github.com/aquasecurity/trivy-db => github.com/0intro/trivy-db v0.0.0-20260625131357-47d5de336db7

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
6464
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
6565
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
6666
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
67+
github.com/0intro/trivy-db v0.0.0-20260625131357-47d5de336db7 h1:ApIE77nVEfQGTwS62JQMIxmfIEIz5J4TxJQLJS8CSPU=
68+
github.com/0intro/trivy-db v0.0.0-20260625131357-47d5de336db7/go.mod h1:iIEV2oGuZScvfyX2SMIn78iVMNnepgo0QuJJh/srgVI=
6769
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
6870
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
6971
github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg=
@@ -180,8 +182,6 @@ github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gw
180182
github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY=
181183
github.com/aquasecurity/trivy-checks v1.12.2-0.20251219190323-79d27547baf5 h1:8HnXyjgCiJwVX1mTKeqdyizd7ZBmXMPL+BMQ5UZd0Nk=
182184
github.com/aquasecurity/trivy-checks v1.12.2-0.20251219190323-79d27547baf5/go.mod h1:hBSA3ziBFwGENK6/PYNIKm6N24SFg0wsv1VXeqPG/3M=
183-
github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d h1:mwCxwhDRnW5UkSQdZfekTCjaLyWp1rqfIa6KKRdMDAo=
184-
github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d/go.mod h1:B0cbg/BEHbJg2RcS7PLdlbGCzz2TkChcZAiI4oSs0VI=
185185
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI=
186186
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8=
187187
github.com/aquasecurity/trivy-kubernetes v0.9.1 h1:bSErQcavKXDh7XMwbGX7Vy//jR5+xhe/bOgfn9G+9lQ=

pkg/detector/ospkg/bottlerocket/bottlerocket.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,72 @@ package bottlerocket
33
import (
44
"context"
55

6-
osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version"
6+
version "github.com/knqyf263/go-rpm-version"
7+
"golang.org/x/xerrors"
8+
9+
"github.com/aquasecurity/trivy-db/pkg/db"
10+
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/bottlerocket"
711
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
812
"github.com/aquasecurity/trivy/pkg/log"
13+
"github.com/aquasecurity/trivy/pkg/scan/utils"
914
"github.com/aquasecurity/trivy/pkg/types"
1015
)
1116

1217
// Scanner implements the Bottlerocket scanner
1318
type Scanner struct {
19+
vs bottlerocket.VulnSrc
1420
}
1521

1622
// NewScanner is the factory method for Scanner
1723
func NewScanner() *Scanner {
18-
return &Scanner{}
24+
return &Scanner{
25+
vs: bottlerocket.NewVulnSrc(),
26+
}
1927
}
2028

21-
func (s *Scanner) Detect(ctx context.Context, _ string, _ *ftypes.Repository, _ []ftypes.Package) ([]types.DetectedVulnerability, error) {
22-
log.InfoContext(ctx, "Vulnerability detection of Bottlerocket packages is currently not supported.")
23-
return nil, nil
29+
func (s *Scanner) Detect(ctx context.Context, _ string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) {
30+
log.InfoContext(ctx, "Detecting vulnerabilities...", log.Int("pkg_num", len(pkgs)))
31+
32+
var vulns []types.DetectedVulnerability
33+
for _, pkg := range pkgs {
34+
advisories, err := s.vs.Get(db.GetParams{
35+
PkgName: pkg.Name,
36+
})
37+
if err != nil {
38+
return nil, xerrors.Errorf("failed to get Bottlerocket advisories: %w", err)
39+
}
40+
41+
installed := utils.FormatVersion(pkg)
42+
if installed == "" {
43+
continue
44+
}
45+
46+
installedVersion := version.NewVersion(installed)
47+
48+
for _, adv := range advisories {
49+
fixedVersion := version.NewVersion(adv.FixedVersion)
50+
if installedVersion.LessThan(fixedVersion) {
51+
vulns = append(vulns, types.DetectedVulnerability{
52+
VulnerabilityID: adv.VulnerabilityID,
53+
PkgID: pkg.ID,
54+
PkgName: pkg.Name,
55+
InstalledVersion: installed,
56+
FixedVersion: adv.FixedVersion,
57+
PkgIdentifier: pkg.Identifier,
58+
Layer: pkg.Layer,
59+
DataSource: adv.DataSource,
60+
Custom: adv.Custom,
61+
})
62+
}
63+
}
64+
}
65+
return vulns, nil
2466
}
2567

26-
func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool {
27-
return osver.Supported(ctx, nil, osFamily, osver.Minor(osVer))
68+
// IsSupportedVersion checks if the version is supported.
69+
func (s *Scanner) IsSupportedVersion(_ context.Context, _ ftypes.OSType, _ string) bool {
70+
// Bottlerocket doesn't publish per-version EOL dates, so we don't track them.
71+
// There is no case where a given input yields an unsupported Bottlerocket version.
72+
73+
return true
2874
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package bottlerocket_test
2+
3+
import (
4+
"sort"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/aquasecurity/trivy-db/pkg/db"
11+
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
12+
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
13+
"github.com/aquasecurity/trivy/internal/dbtest"
14+
"github.com/aquasecurity/trivy/pkg/detector/ospkg/bottlerocket"
15+
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
16+
"github.com/aquasecurity/trivy/pkg/types"
17+
)
18+
19+
func TestScanner_Detect(t *testing.T) {
20+
tests := []struct {
21+
name string
22+
fixtures []string
23+
pkgs []ftypes.Package
24+
want []types.DetectedVulnerability
25+
wantErr string
26+
}{
27+
{
28+
name: "vulnerable kernel",
29+
fixtures: []string{
30+
"testdata/fixtures/bottlerocket.yaml",
31+
"testdata/fixtures/data-source.yaml",
32+
},
33+
pkgs: []ftypes.Package{
34+
{
35+
Name: "kernel-6.1",
36+
Version: "6.1.124",
37+
Release: "1.1737763153.9fde645.br1",
38+
Epoch: 0,
39+
Layer: ftypes.Layer{
40+
DiffID: "sha256:aaa",
41+
},
42+
},
43+
},
44+
want: []types.DetectedVulnerability{
45+
{
46+
PkgName: "kernel-6.1",
47+
VulnerabilityID: "CVE-2024-43840",
48+
InstalledVersion: "6.1.124-1.1737763153.9fde645.br1",
49+
FixedVersion: "6.1.140-1.1749663821.e27d1b03.br1",
50+
Layer: ftypes.Layer{
51+
DiffID: "sha256:aaa",
52+
},
53+
DataSource: &dbTypes.DataSource{
54+
ID: vulnerability.Bottlerocket,
55+
Name: "Bottlerocket Security Advisories",
56+
URL: "https://advisories.bottlerocket.aws/updateinfo.xml.gz",
57+
},
58+
},
59+
},
60+
},
61+
{
62+
name: "not vulnerable - installed version is newer",
63+
fixtures: []string{
64+
"testdata/fixtures/bottlerocket.yaml",
65+
"testdata/fixtures/data-source.yaml",
66+
},
67+
pkgs: []ftypes.Package{
68+
{
69+
Name: "kernel-6.1",
70+
Version: "6.1.172",
71+
Release: "1.1779997967.64782dc8.br1",
72+
Epoch: 0,
73+
},
74+
},
75+
want: nil,
76+
},
77+
{
78+
name: "vulnerable package with epoch",
79+
fixtures: []string{
80+
"testdata/fixtures/bottlerocket.yaml",
81+
"testdata/fixtures/data-source.yaml",
82+
},
83+
pkgs: []ftypes.Package{
84+
{
85+
Name: "glibc",
86+
Version: "2.40",
87+
Release: "1.1740525475.e3a5862c.br1",
88+
Epoch: 1,
89+
},
90+
},
91+
want: []types.DetectedVulnerability{
92+
{
93+
PkgName: "glibc",
94+
VulnerabilityID: "CVE-2025-15281",
95+
InstalledVersion: "1:2.40-1.1740525475.e3a5862c.br1",
96+
FixedVersion: "1:2.43-1.1773716061.e2e9d7a9.br1",
97+
DataSource: &dbTypes.DataSource{
98+
ID: vulnerability.Bottlerocket,
99+
Name: "Bottlerocket Security Advisories",
100+
URL: "https://advisories.bottlerocket.aws/updateinfo.xml.gz",
101+
},
102+
},
103+
},
104+
},
105+
{
106+
name: "no packages",
107+
fixtures: []string{
108+
"testdata/fixtures/bottlerocket.yaml",
109+
"testdata/fixtures/data-source.yaml",
110+
},
111+
pkgs: nil,
112+
want: nil,
113+
},
114+
{
115+
name: "Get returns an error",
116+
fixtures: []string{
117+
"testdata/fixtures/invalid.yaml",
118+
"testdata/fixtures/data-source.yaml",
119+
},
120+
pkgs: []ftypes.Package{
121+
{
122+
Name: "kernel-6.1",
123+
Version: "6.1.124",
124+
Release: "1.1737763153.9fde645.br1",
125+
},
126+
},
127+
wantErr: "failed to get Bottlerocket advisories",
128+
},
129+
}
130+
131+
for _, tt := range tests {
132+
t.Run(tt.name, func(t *testing.T) {
133+
_ = dbtest.InitDB(t, tt.fixtures)
134+
defer db.Close()
135+
136+
s := bottlerocket.NewScanner()
137+
got, err := s.Detect(t.Context(), "", nil, tt.pkgs)
138+
if tt.wantErr != "" {
139+
require.ErrorContains(t, err, tt.wantErr)
140+
return
141+
}
142+
143+
sort.Slice(got, func(i, j int) bool {
144+
return got[i].VulnerabilityID < got[j].VulnerabilityID
145+
})
146+
require.NoError(t, err)
147+
assert.Equal(t, tt.want, got)
148+
})
149+
}
150+
}
151+
152+
func TestScanner_IsSupportedVersion(t *testing.T) {
153+
s := bottlerocket.NewScanner()
154+
assert.True(t, s.IsSupportedVersion(t.Context(), ftypes.Bottlerocket, "1.19.0"))
155+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
- bucket: bottlerocket
2+
pairs:
3+
- bucket: kernel-6.1
4+
pairs:
5+
- key: CVE-2024-43840
6+
value:
7+
FixedVersion: "6.1.140-1.1749663821.e27d1b03.br1"
8+
- bucket: glibc
9+
pairs:
10+
- key: CVE-2025-15281
11+
value:
12+
FixedVersion: "1:2.43-1.1773716061.e2e9d7a9.br1"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- bucket: data-source
2+
pairs:
3+
- key: bottlerocket
4+
value:
5+
ID: "bottlerocket"
6+
Name: "Bottlerocket Security Advisories"
7+
URL: "https://advisories.bottlerocket.aws/updateinfo.xml.gz"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- bucket: bottlerocket
2+
pairs:
3+
- bucket: kernel-6.1
4+
pairs:
5+
- key: CVE-2024-43840
6+
value: "invalid"

0 commit comments

Comments
 (0)