Skip to Content
OpenVAPTAlpha
Pre-release. Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. Free for your own use, 100% at your own risk. This website collects no data. Read MoreRead Less
Pre-release. OpenVAPT has no Stable release yet. Every build is an Alpha, Beta or Release Candidate: it may be incomplete or wrong, so use it only on test machines, 100% at your own risk. See Release Channels
Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. These are AI systems, not people or registered engineers. Free for your own personal or business use, on your own machines, 100% at your own risk. Not for sale. Not for providing services. This website collects no data. Read the Acknowledgements
Pre-release. OpenVAPT has no Stable release yet. Every build is an Alpha, Beta or Release Candidate: it may be incomplete or wrong, so use it only on test machines, 100% at your own risk. See Release Channels
Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. These are AI systems, not people or registered engineers. Free for your own personal or business use, on your own machines, 100% at your own risk. Not for sale. Not for providing services. This website collects no data. Read the Acknowledgements
HighHOST-PKG-001Patch and Vulnerability ManagementServer

Security Updates Waiting to Be Installed

Checks the package manager for published security updates that are not installed yet.

Why It Matters
Most real-world compromises use vulnerabilities for which a fix was already available. Pending security updates are known, public weaknesses.
How It Detects
Runs the package manager in simulation mode only (apt-get -s upgrade, or dnf/yum updateinfo --security). Nothing is downloaded or installed. Lines that come from a security repository are counted.
How to Fix
Debian/Ubuntu: apt-get update && apt-get upgrade . RHEL family: dnf upgrade --security . Consider unattended-upgrades or dnf-automatic for security patches.
How to Verify
Re-run openvapt; this check should pass.
CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Control Category
Patch and Vulnerability Management: CIS Controls v8 7, 2; NIST CSF 2.0 ID.RA, PR.PS
Fix Effort
Medium
CIS Controls v8 Safeguard
7.3
NIST CSF 2.0 Subcategory
ID.RA-01, PR.PS-02
CWE
CWE-1104
OWASP Top 10
A06:2021
NIST SP 800-53 Rev. 5
SI-2, RA-5
Benchmark Topic
Patch management: install security updates (topic only, no benchmark text)
References
NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning
Debian, UnattendedUpgrades
Learn the theory behind this check: F3 Vulnerabilities, CVE, CWE and CVSS, P1 Linux Server Hardening.

Source Code

The complete file that defines this check. Run it with openvapt scan --only HOST-PKG-001, or read it offline with openvapt explain HOST-PKG-001.

internal/checks/host/os_patch.go · Lines 1-221View on GitHub at This Commit
1package host
2
3import (
4 "context"
5 "fmt"
6 "regexp"
7 "strconv"
8 "strings"
9 "time"
10
11 "github.com/openvapt/openvapt/internal/check"
12 "github.com/openvapt/openvapt/internal/checks/collect"
13 "github.com/openvapt/openvapt/internal/sys"
14)
15
16// eol maps "ID VERSION_ID" to the date standard (free) security support ends.
17// Sources: each vendor's published lifecycle page, cited in the check refs.
18// The table is dated; a release newer than this table may be missing, while a
19// release older than the oldest row of its distribution is always past end of
20// support and fails. Every row was re-verified on 2026-10-13 against the
21// vendors' published schedules (Canonical, Debian, Red Hat, CentOS, Rocky,
22// AlmaLinux, Oracle, Fedora, Amazon) as collected by endoflife.date, with the
23// Fedora 44 date from Fedora's own schedule (fedorapeople.org f-44 key
24// tasks). Fedora dates are provisional until the release two versions later
25// ships; re-check them when the table is next refreshed.
26var eol = map[string]string{
27 "ubuntu 16.04": "2021-04-02", "ubuntu 18.04": "2023-05-31", "ubuntu 20.04": "2025-05-31",
28 "ubuntu 20.10": "2021-07-22", "ubuntu 21.04": "2022-01-20", "ubuntu 21.10": "2022-07-14",
29 "ubuntu 22.04": "2027-06-01", "ubuntu 22.10": "2023-07-20", "ubuntu 23.04": "2024-01-20",
30 "ubuntu 23.10": "2024-07-12", "ubuntu 24.04": "2029-05-31", "ubuntu 24.10": "2025-07-10",
31 "ubuntu 25.04": "2026-01-17", "ubuntu 25.10": "2026-07-01", "ubuntu 26.04": "2031-05-31",
32 "debian 9": "2022-06-30", "debian 10": "2024-06-30", "debian 11": "2026-08-31", "debian 12": "2028-06-30", "debian 13": "2030-06-30",
33 // CentOS Linux 7 and 8; CentOS Stream 9 reports ID centos as well.
34 "centos 7": "2024-06-30", "centos 8": "2021-12-31", "centos 9": "2027-05-31", "centos 10": "2030-05-31",
35 "rhel 7": "2024-06-30", "rhel 8": "2029-05-31", "rhel 9": "2032-05-31", "rhel 10": "2035-05-31",
36 "rocky 8": "2029-05-31", "rocky 9": "2032-05-31", "rocky 10": "2035-05-31",
37 "almalinux 8": "2029-05-31", "almalinux 9": "2032-05-31", "almalinux 10": "2035-05-31",
38 "ol 7": "2024-12-31", "ol 8": "2029-07-31", "ol 9": "2032-06-30", "ol 10": "2035-06-30",
39 "fedora 39": "2024-11-26", "fedora 40": "2025-05-13", "fedora 41": "2025-12-15", "fedora 42": "2026-05-27",
40 "fedora 43": "2026-12-09", "fedora 44": "2027-06-02",
41 "amzn 2018.03": "2023-12-31", "amzn 2": "2026-06-30", "amzn 2023": "2029-06-30",
42}
43
44// EOLTableDate is printed in reports so readers know how fresh the table is.
45const EOLTableDate = "2026-10-13"
46
47// eolWarnDays is how close to end of support a release starts to fail, so
48// there is time to plan the upgrade.
49const eolWarnDays = 90
50
51// majorOnly lists distributions whose support depends on the major version.
52var majorOnly = map[string]bool{"centos": true, "rhel": true, "rocky": true, "almalinux": true, "ol": true, "debian": true}
53
54// osEOL looks up one release. It returns the end date, whether the release
55// is past (or within eolWarnDays of) end of support, and whether the result
56// is known. A release older than every row for its distribution is reported
57// as past end of support without a date.
58func osEOL(id, ver string, now time.Time) (end string, bad, known bool, daysLeft int) {
59 if majorOnly[id] {
60 ver = strings.SplitN(ver, ".", 2)[0]
61 }
62 if e, ok := eol[id+" "+ver]; ok {
63 d, err := time.Parse("2006-01-02", e)
64 if err != nil {
65 return "", false, false, 0
66 }
67 left := d.Sub(now)
68 return e, left < eolWarnDays*24*time.Hour, true, int(left.Hours() / 24)
69 }
70 // Amazon Linux 1 used year.month versions; 2018.03 was the last, so any
71 // earlier one (2017.09, 2016.09, ...) is past end of support. Comparing
72 // against the oldest row would fail here, because "2" sorts before them.
73 if id == "amzn" && amznYear.MatchString(ver) && versionLess(ver, "2018.03") {
74 return "", true, true, 0
75 }
76 oldest := ""
77 for k := range eol {
78 if rid, rv, _ := strings.Cut(k, " "); rid == id && (oldest == "" || versionLess(rv, oldest)) {
79 oldest = rv
80 }
81 }
82 if oldest != "" && ver != "" && versionLess(ver, oldest) {
83 return "", true, true, 0
84 }
85 return "", false, false, 0
86}
87
88var amznYear = regexp.MustCompile(`^20[01][0-9]\.[0-9]{2}$`)
89
90// versionLess compares dotted numeric versions ("16.04" < "18.04", "9" < "10").
91// A non-numeric component makes the comparison false.
92func versionLess(a, b string) bool {
93 as, bs := strings.Split(a, "."), strings.Split(b, ".")
94 for i := 0; i < len(as) || i < len(bs); i++ {
95 var x, y int
96 if i < len(as) {
97 n, err := strconv.Atoi(as[i])
98 if err != nil {
99 return false
100 }
101 x = n
102 }
103 if i < len(bs) {
104 n, err := strconv.Atoi(bs[i])
105 if err != nil {
106 return false
107 }
108 y = n
109 }
110 if x != y {
111 return x < y
112 }
113 }
114 return false
115}
116
117func init() {
118 check.Register(&check.Check{
119 ID: "HOST-OS-001",
120 Title: "Operating System Past End of Security Support",
121 Domain: check.DomainHost,
122 Severity: check.High,
123 CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
124 Summary: "Checks whether the Linux distribution release still receives free security updates.",
125 Rationale: "Once a release reaches end of life, newly discovered vulnerabilities are never fixed. Every later CVE in the kernel, OpenSSL or the web server stays open forever.",
126 Detection: "Reads ID and VERSION_ID from /etc/os-release and looks them up in a dated table of free vendor security-support dates, including Debian LTS (table date " + EOLTableDate + "). Releases older than the oldest table row for their distribution fail. Releases within 90 days of end of life also fail, so there is time to plan the upgrade.",
127 Fix: "Plan an upgrade to a supported release (for example do-release-upgrade on Ubuntu, or a fresh install and migration). Paid extended support (Ubuntu Pro, RHEL ELS) is an alternative while migrating.",
128 Verify: "cat /etc/os-release and compare with the vendor lifecycle page.",
129 Refs: []check.Ref{
130 {Title: "Ubuntu release cycle", URL: "https://ubuntu.com/about/release-cycle"},
131 {Title: "Debian releases and LTS", URL: "https://wiki.debian.org/LTS"},
132 {Title: "Red Hat Enterprise Linux life cycle", URL: "https://access.redhat.com/support/policy/updates/errata"},
133 {Title: "endoflife.date (community-maintained lifecycle data)", URL: "https://endoflife.date/"}},
134 Standards: check.Standards{CWE: []string{"CWE-1104"}, OWASP: []string{"A06:2021"}, NIST: []string{"SA-22", "SI-2"}, CISTopic: "Supported operating system versions"},
135 Run: func(_ context.Context, s sys.System) []check.Result {
136 osr := collect.OSRelease(s)
137 id, ver := osr["ID"], osr["VERSION_ID"]
138 if id == "" {
139 return []check.Result{check.SkipR("os-release", "Distribution could not be identified")}
140 }
141 name := osr["PRETTY_NAME"]
142 if name == "" {
143 name = id + " " + ver
144 }
145 end, bad, known, left := osEOL(id, ver, time.Now())
146 switch {
147 case !known:
148 return []check.Result{check.SkipR(name, "Release not in the end-of-life table (dated "+EOLTableDate+")")}
149 case bad && end == "":
150 return []check.Result{check.FailR(name, "Release is older than every supported release in the end-of-life table (dated "+EOLTableDate+"), so security support has ended")}
151 case bad && left < 0:
152 return []check.Result{check.FailR(name, "Security support ended on "+end)}
153 case bad:
154 return []check.Result{check.FailR(name, fmt.Sprintf("Security support ends on %s (in %d days, within the %d-day planning window)", end, left, eolWarnDays))}
155 }
156 return []check.Result{check.PassR(name, "Supported until "+end)}
157 },
158 })
159
160 check.Register(&check.Check{
161 ID: "HOST-PKG-001",
162 Title: "Security Updates Waiting to Be Installed",
163 Domain: check.DomainHost,
164 Severity: check.High,
165 CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
166 Summary: "Checks the package manager for published security updates that are not installed yet.",
167 Rationale: "Most real-world compromises use vulnerabilities for which a fix was already available. Pending security updates are known, public weaknesses.",
168 Detection: "Runs the package manager in simulation mode only (apt-get -s upgrade, or dnf/yum updateinfo --security). Nothing is downloaded or installed. Lines that come from a security repository are counted.",
169 Fix: "Debian/Ubuntu: apt-get update && apt-get upgrade . RHEL family: dnf upgrade --security . Consider unattended-upgrades or dnf-automatic for security patches.",
170 Verify: "Re-run openvapt; this check should pass.",
171 Refs: []check.Ref{
172 {Title: "NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning", URL: "https://csrc.nist.gov/pubs/sp/800/40/r4/final"},
173 {Title: "Debian, UnattendedUpgrades", URL: "https://wiki.debian.org/UnattendedUpgrades"}},
174 Standards: check.Standards{CWE: []string{"CWE-1104"}, OWASP: []string{"A06:2021"}, NIST: []string{"SI-2", "RA-5"}, CISTopic: "Patch management: install security updates"},
175 Run: func(ctx context.Context, s sys.System) []check.Result {
176 if out, err := s.Run(ctx, "apt-get", "-s", "upgrade"); err == nil {
177 var sec []string
178 for _, line := range strings.Split(out, "\n") {
179 if strings.HasPrefix(line, "Inst ") && strings.Contains(strings.ToLower(line), "security") {
180 f := strings.Fields(line)
181 if len(f) > 1 {
182 sec = append(sec, f[1])
183 }
184 }
185 }
186 if len(sec) > 0 {
187 return []check.Result{check.FailR("apt", fmt.Sprintf("%d security updates pending", len(sec)), firstN(sec, 30)...)}
188 }
189 return []check.Result{check.PassR("apt", "No pending security updates (based on the last apt-get update)")}
190 }
191 for _, cmd := range [][]string{{"dnf", "-q", "updateinfo", "list", "--security"}, {"yum", "-q", "updateinfo", "list", "security"}} {
192 if out, err := s.Run(ctx, cmd[0], cmd[1:]...); err == nil {
193 var sec []string
194 for _, line := range strings.Split(out, "\n") {
195 // Only advisory lines count ("ALSA-2024:1234 Important/Sec. pkg"),
196 // never the header or a footer dnf may print.
197 if f := strings.Fields(line); len(f) >= 3 && advisoryIDRe.MatchString(f[0]) {
198 sec = append(sec, f[0]+" "+f[2])
199 }
200 }
201 if len(sec) > 0 {
202 return []check.Result{check.FailR(cmd[0], fmt.Sprintf("%d security advisories pending", len(sec)), firstN(sec, 30)...)}
203 }
204 return []check.Result{check.PassR(cmd[0], "No pending security advisories")}
205 }
206 }
207 return []check.Result{check.SkipR("packages", "No supported package manager found (apt, dnf, yum)")}
208 },
209 })
210}
211
212// advisoryIDRe matches vendor advisory ids such as RHSA-2024:1234,
213// ALSA-2024:5678, RLSA-2024:1 or FEDORA-2024-abcdef.
214var advisoryIDRe = regexp.MustCompile(`^[A-Z]{2,10}-\d{4}[:-][0-9a-zA-Z]+$`)
215
216func firstN(in []string, n int) []string {
217 if len(in) <= n {
218 return in
219 }
220 return append(in[:n:n], fmt.Sprintf("... and %d more", len(in)-n))
221}

Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.

Cite This Page

If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.

APA 7
OpenVAPT. (2026). HOST-PKG-001 Security Updates Waiting to Be Installed (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/HOST-PKG-001.html

In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.

BibTeX
@misc{openvapt_host_pkg_001,
  author       = {{OpenVAPT}},
  title        = {{HOST-PKG-001 Security Updates Waiting to Be Installed}},
  year         = {2026},
  howpublished = {\url{https://openvapt.com/checks/HOST-PKG-001.html}},
  note         = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}

Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.

Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.