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
LowHOST-PKG-006Patch and Vulnerability ManagementServer

Package Lists Not Refreshed Automatically

Checks that the package manager downloads fresh repository metadata on a schedule, so pending updates can be seen at all.

Why It Matters
apt and dnf only know about updates listed in the metadata they last downloaded. Without a timer that refreshes it, HOST-PKG-001 and the administrator's own apt list --upgradable both report a clean system that is weeks behind. The refresh is read-only and separate from installing anything.
How It Detects
apt: reads /etc/apt/apt.conf.d/* for APT::Periodic::Update-Package-Lists "1" or asks the allow-listed systemctl is-enabled whether apt-daily.timer is enabled. dnf: asks whether dnf-makecache.timer is enabled. Skipped when neither package manager is present; Incomplete when apt configuration cannot be read.
How to Fix
Debian/Ubuntu: create /etc/apt/apt.conf.d/20auto-upgrades with APT::Periodic::Update-Package-Lists "1"; (dpkg-reconfigure unattended-upgrades writes it) and systemctl enable apt-daily.timer . RHEL family: systemctl enable --now dnf-makecache.timer .
How to Verify
systemctl is-enabled apt-daily.timer or systemctl is-enabled dnf-makecache.timer
CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
Control Category
Patch and Vulnerability Management: CIS Controls v8 7, 2; NIST CSF 2.0 ID.RA, PR.PS
Fix Effort
Low
CIS Controls v8 Safeguard
7.4
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
Automated patch management (CIS Controls v8 7.4) (topic only, no benchmark text)
References
Debian, UnattendedUpgrades (APT::Periodic settings)
NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning
Learn the theory behind this check: P1 Linux Server Hardening, P22 Authentication Policy and Package Hygiene.

Source Code

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

internal/checks/host/pkg_depth.go · Lines 1-381View on GitHub at This Commit
1package host
2
3import (
4 "context"
5 "fmt"
6 "net/url"
7 "sort"
8 "strings"
9
10 "github.com/openvapt/openvapt/internal/check"
11 "github.com/openvapt/openvapt/internal/checks/collect"
12 "github.com/openvapt/openvapt/internal/sys"
13)
14
15// HOST-PKG depth: how packages arrive and whether the running kernel is the
16// one installed. HOST-PKG-001 (pending security updates), HOST-UPD-001
17// (automatic security updates) and HOST-UPD-002 (Debian reboot marker) are
18// not repeated. Repository files are read; apt-cache and dnf are not run.
19
20var (
21 refAptSecure = check.Ref{Title: "apt-secure(8), Debian manual pages", URL: "https://manpages.debian.org/trixie/apt/apt-secure.8.en.html"}
22 refDnfConf = check.Ref{Title: "dnf.conf(5), Ubuntu noble manual pages (gpgcheck)", URL: "https://manpages.ubuntu.com/manpages/noble/en/man5/dnf.conf.5.html"}
23 refNIST80040 = check.Ref{Title: "NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning", URL: "https://csrc.nist.gov/pubs/sp/800/40/r4/final"}
24 refSourcesLst = check.Ref{Title: "sources.list(5), Debian manual pages", URL: "https://manpages.debian.org/trixie/apt/sources.list.5.en.html"}
25)
26
27// vendorRepoSuffixes are host name suffixes of distribution repositories.
28// Anything else is listed as third party. Matching is by suffix so regional
29// mirrors (my.archive.ubuntu.com) count as vendor hosts.
30var vendorRepoSuffixes = []string{
31 ".ubuntu.com", ".canonical.com", ".debian.org", ".debian.net",
32 ".fedoraproject.org", ".centos.org", ".redhat.com", ".rockylinux.org", ".almalinux.org",
33 ".oracle.com", ".amazonaws.com", ".opensuse.org", ".suse.com", ".raspbian.org", ".raspberrypi.org", ".raspberrypi.com",
34}
35
36func vendorRepoHost(h string) bool {
37 h = strings.ToLower(h)
38 for _, s := range vendorRepoSuffixes {
39 if strings.HasSuffix(h, s) || h == strings.TrimPrefix(s, ".") {
40 return true
41 }
42 }
43 return false
44}
45
46// repoHost extracts the host of a repository URI, or "" for local or
47// unparseable ones (file:, cdrom:, copy:).
48func repoHost(uri string) string {
49 u, err := url.Parse(strings.Trim(uri, `"'`))
50 if err != nil || u.Host == "" {
51 return ""
52 }
53 return strings.ToLower(u.Hostname())
54}
55
56// aptSourceURIs returns the URIs of one-line (deb ...) and Deb822 (URIs:)
57// apt source files, with the option block of one-line entries.
58type aptSource struct {
59 URI, Options string
60}
61
62func aptSources(text string) []aptSource {
63 var out []aptSource
64 for _, l := range activeLines(text) {
65 f := strings.Fields(l)
66 switch {
67 case len(f) >= 2 && (f[0] == "deb" || f[0] == "deb-src"):
68 opts := ""
69 rest := f[1:]
70 if strings.HasPrefix(rest[0], "[") {
71 // Collect "[ ... ]" which may span several fields.
72 var o []string
73 for len(rest) > 0 {
74 o = append(o, rest[0])
75 done := strings.HasSuffix(rest[0], "]")
76 rest = rest[1:]
77 if done {
78 break
79 }
80 }
81 opts = strings.Join(o, " ")
82 }
83 if len(rest) > 0 {
84 out = append(out, aptSource{URI: rest[0], Options: opts})
85 }
86 case strings.HasPrefix(l, "URIs:"):
87 for _, u := range strings.Fields(strings.TrimPrefix(l, "URIs:")) {
88 out = append(out, aptSource{URI: u})
89 }
90 }
91 }
92 return out
93}
94
95// repoFiles are the package repository definitions the checks read.
96var (
97 aptSourceFiles = []string{"/etc/apt/sources.list", "/etc/apt/sources.list.d/*.list", "/etc/apt/sources.list.d/*.sources"}
98 aptConfFiles = []string{"/etc/apt/apt.conf", "/etc/apt/apt.conf.d/*"}
99 yumRepoFiles = []string{"/etc/yum.repos.d/*.repo", "/etc/dnf/dnf.conf", "/etc/yum.conf"}
100)
101
102// aptPresent reports whether this machine uses apt.
103func aptPresent(s sys.System) bool {
104 if exists(s, "/etc/apt") {
105 return true
106 }
107 for _, pat := range []string{"/etc/apt/sources.list*", "/etc/apt/apt.conf.d/*", "/etc/apt/sources.list.d/*"} {
108 if m, _ := s.Glob(pat); len(m) > 0 {
109 return true
110 }
111 }
112 return false
113}
114
115// dnfPresent reports whether this machine uses dnf or yum.
116func dnfPresent(s sys.System) bool {
117 if exists(s, "/etc/yum.repos.d") || exists(s, "/etc/dnf") || exists(s, "/etc/yum.conf") {
118 return true
119 }
120 m, _ := s.Glob("/etc/yum.repos.d/*.repo")
121 return len(m) > 0
122}
123
124func init() {
125 check.Register(&check.Check{
126 ID: "HOST-PKG-002", Title: "Repository Signature Checks Disabled", Domain: check.DomainHost,
127 Severity: check.High, Category: check.CatPatch, Effort: check.EffortLow,
128 CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
129 Summary: "Checks that the package manager verifies repository signatures, so a tampered mirror or intercepted download cannot install code as root.",
130 Rationale: "Package installation runs as root. apt and dnf verify a signature over the repository index before trusting any package in it; an entry marked trusted=yes, AllowUnauthenticated or gpgcheck=0 switches that off, and a mirror operator or anyone on the network path can then deliver a modified package at the next update.",
131 Detection: "apt: reads /etc/apt/sources.list, /etc/apt/sources.list.d/*.list (entries with [trusted=yes]) and *.sources (Trusted: yes), plus /etc/apt/apt.conf and /etc/apt/apt.conf.d/* for AllowUnauthenticated or AllowInsecureRepositories set to true. dnf and yum: reads /etc/yum.repos.d/*.repo, /etc/dnf/dnf.conf and /etc/yum.conf for gpgcheck=0 in any section. Skipped when no repository files exist; Incomplete when some exist but cannot be read.",
132 Fix: "Remove trusted=yes (or Trusted: yes) from the listed source, install the repository's signing key under /etc/apt/keyrings and reference it with signed-by=; remove AllowUnauthenticated and AllowInsecureRepositories settings. On dnf and yum set gpgcheck=1 (and repo_gpgcheck=1 where supported) in each listed repository and import its key with rpm --import.",
133 Verify: "grep -r 'trusted=yes' /etc/apt/ ; grep -r 'gpgcheck=0' /etc/yum.repos.d/ /etc/dnf/dnf.conf 2>/dev/null",
134 Refs: []check.Ref{refAptSecure, refDnfConf, refNIST80040},
135 Standards: check.Standards{CWE: []string{"CWE-494", "CWE-347"}, OWASP: []string{"A08:2021"}, NIST: []string{"SI-7", "CM-14", "SR-3", "SR-11"}, CISTopic: "Package manager repositories and GPG keys are configured"},
136 Run: func(ctx context.Context, s sys.System) []check.Result {
137 var bad []string
138 seen := 0
139 var unreadable []string
140 src, u := readAll(ctx, s, aptSourceFiles...)
141 unreadable = append(unreadable, u...)
142 for _, f := range src {
143 seen++
144 for _, e := range aptSources(f.Text) {
145 if strings.Contains(strings.ToLower(e.Options), "trusted=yes") {
146 bad = append(bad, f.Path+": "+repoHostOrURI(e.URI)+" marked trusted=yes (signature check off)")
147 }
148 }
149 for _, l := range activeLines(f.Text) {
150 if strings.EqualFold(strings.Join(strings.Fields(l), " "), "Trusted: yes") {
151 bad = append(bad, f.Path+": Trusted: yes (signature check off)")
152 }
153 }
154 }
155 conf, u := readAll(ctx, s, aptConfFiles...)
156 unreadable = append(unreadable, u...)
157 for _, f := range conf {
158 for _, l := range activeLines(f.Text) {
159 if strings.HasPrefix(l, "//") {
160 continue
161 }
162 low := strings.ToLower(l)
163 if (strings.Contains(low, "allowunauthenticated") || strings.Contains(low, "allowinsecurerepositories")) && strings.Contains(low, "true") {
164 bad = append(bad, f.Path+": "+l)
165 }
166 }
167 }
168 repos, u := readAll(ctx, s, yumRepoFiles...)
169 unreadable = append(unreadable, u...)
170 for _, f := range repos {
171 seen++
172 var keys []string
173 for k := range collect.ParseKV(f.Text) {
174 if k == "gpgcheck" || strings.HasSuffix(k, ".gpgcheck") {
175 keys = append(keys, k)
176 }
177 }
178 sort.Strings(keys)
179 vals := collect.ParseKV(f.Text)
180 for _, k := range keys {
181 if k == "gpgcheck" && len(keys) > 1 {
182 continue // the unprefixed copy duplicates the section keys
183 }
184 for _, v := range vals[k] {
185 if strings.TrimSpace(v) == "0" {
186 bad = append(bad, f.Path+": "+k+"=0")
187 }
188 }
189 }
190 }
191 if len(bad) > 0 {
192 return []check.Result{check.FailR("package repositories", "Repository signature verification is disabled", bad...)}
193 }
194 if len(unreadable) > 0 {
195 return []check.Result{check.IncompleteR("package repositories", "Some repository files could not be read", unreadable...)}
196 }
197 if seen == 0 {
198 return []check.Result{check.SkipR("package repositories", "No apt or dnf repository files found")}
199 }
200 return []check.Result{check.PassR("package repositories", fmt.Sprintf("%d repository file(s) keep signature verification on", seen))}
201 },
202 })
203
204 check.Register(&check.Check{
205 ID: "HOST-PKG-003", Title: "Third-Party Package Repositories Present", Domain: check.DomainHost,
206 Severity: check.Info, Category: check.CatPatch, Effort: check.EffortMedium,
207 CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N",
208 Summary: "Lists package repositories that are not run by the distribution vendor, so each one can be justified.",
209 Rationale: "Every repository can install software as root at the next update, so each one is a supplier you trust completely. Personal package archives, vendor repositories and forgotten test repositories are a common way for unpatched or abandoned software to stay on a server. This is informational: a third-party repository is a decision to review, not a defect by itself.",
210 Detection: "Reads /etc/apt/sources.list, /etc/apt/sources.list.d/*.list and *.sources, and /etc/yum.repos.d/*.repo (baseurl, mirrorlist, metalink), and lists repository host names that do not end in a known distribution domain (ubuntu.com, canonical.com, debian.org, fedoraproject.org, centos.org, redhat.com, rockylinux.org, almalinux.org, oracle.com, amazonaws.com, opensuse.org, suse.com, raspberrypi.com). Only host names are reported.",
211 Fix: "For each listed repository, record who runs it and why it is needed; remove the ones nobody can justify (delete the file under /etc/apt/sources.list.d or /etc/yum.repos.d and apt-get update or dnf clean all). Keep the rest pinned to the packages they are for (apt preferences, dnf includepkgs).",
212 Verify: "ls /etc/apt/sources.list.d/ /etc/yum.repos.d/ 2>/dev/null",
213 Refs: []check.Ref{refSourcesLst, refNIST80040, {Title: "NIST SP 800-161 Rev. 1, Cybersecurity Supply Chain Risk Management Practices", URL: "https://csrc.nist.gov/pubs/sp/800/161/r1/upd1/final"}},
214 Standards: check.Standards{CWE: []string{"CWE-1357"}, OWASP: []string{"A08:2021"}, NIST: []string{"CM-8", "SR-3", "SR-4"}, CISTopic: "Software inventory: authorised repositories (CIS Controls v8 2.5)"},
215 Run: func(ctx context.Context, s sys.System) []check.Result {
216 hosts := map[string]string{}
217 var order []string
218 add := func(path, h string) {
219 if h == "" || vendorRepoHost(h) {
220 return
221 }
222 if _, ok := hosts[h]; !ok {
223 order = append(order, h)
224 hosts[h] = path
225 }
226 }
227 src, unreadable := readAll(ctx, s, aptSourceFiles...)
228 for _, f := range src {
229 for _, e := range aptSources(f.Text) {
230 add(f.Path, repoHost(e.URI))
231 }
232 }
233 repos, u := readAll(ctx, s, "/etc/yum.repos.d/*.repo")
234 unreadable = append(unreadable, u...)
235 for _, f := range repos {
236 vals := collect.ParseKV(f.Text)
237 for k, vs := range vals {
238 if strings.HasSuffix(k, ".baseurl") || strings.HasSuffix(k, ".mirrorlist") || strings.HasSuffix(k, ".metalink") {
239 for _, v := range vs {
240 for _, uri := range strings.Fields(v) {
241 add(f.Path, repoHost(uri))
242 }
243 }
244 }
245 }
246 }
247 if len(order) > 0 {
248 sort.Strings(order)
249 ev := make([]string, 0, len(order))
250 for _, h := range order {
251 ev = append(ev, h+" ("+hosts[h]+")")
252 }
253 return []check.Result{check.FailR("package repositories", fmt.Sprintf("%d third-party repository host(s) configured", len(order)), ev...)}
254 }
255 if len(unreadable) > 0 {
256 return []check.Result{check.IncompleteR("package repositories", "Some repository files could not be read", unreadable...)}
257 }
258 if len(src)+len(repos) == 0 {
259 return []check.Result{check.SkipR("package repositories", "No apt or dnf repository files found")}
260 }
261 return []check.Result{check.PassR("package repositories", "Only distribution vendor repositories are configured")}
262 },
263 })
264
265 check.Register(&check.Check{
266 ID: "HOST-PKG-004", Title: "Many Old Kernels Retained", Domain: check.DomainHost,
267 Severity: check.Low, Category: check.CatPatch, Effort: check.EffortLow,
268 CVSS: "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N",
269 Summary: "Checks that no more than three kernel versions are installed.",
270 Rationale: "Every installed kernel stays bootable from the boot menu, with every vulnerability it had. Old kernels also fill /boot until updates fail to install, which is a quiet way to stop receiving kernel patches. Keeping the running kernel plus one or two fallbacks is enough.",
271 Detection: "Counts kernel versions among installed packages: linux-image-<version> packages whose /var/lib/dpkg/status entry reads 'install ok installed' on Debian and Ubuntu (the /var/lib/dpkg/info/*.list files are used only when the status file cannot be read, because removed but not purged packages keep them), kernel and kernel-core from the allow-listed rpm -qa on the RHEL family. More than three fails. Skipped when no kernel package is installed (containers); Incomplete when no package database could be read.",
272 Fix: "Debian/Ubuntu: apt-get autoremove --purge removes kernels outside the keep list. RHEL family: set installonly_limit=3 in /etc/dnf/dnf.conf and run dnf remove --oldinstallonly . Never remove the running kernel (uname -r).",
273 Verify: "dpkg -l 'linux-image-*' | grep '^ii' or rpm -q kernel kernel-core",
274 Refs: []check.Ref{refNIST80040, {Title: "apt-get(8), Debian trixie manual pages (autoremove)", URL: "https://manpages.debian.org/trixie/apt/apt-get.8.en.html"}},
275 Standards: check.Standards{CWE: []string{"CWE-1104"}, OWASP: []string{"A06:2021"}, NIST: []string{"SI-2", "CM-7"}, CISTopic: "Remove unneeded software versions"},
276 Run: func(ctx context.Context, s sys.System) []check.Result {
277 pkgs, ok := installedPackages(ctx, s)
278 if !ok {
279 return []check.Result{check.IncompleteR("kernels", "No package database could be read (dpkg status, dpkg info or rpm)")}
280 }
281 vers := kernelVersions(pkgs)
282 switch {
283 case len(vers) == 0:
284 return []check.Result{check.SkipR("kernels", "No kernel packages are installed (container or unusual packaging)")}
285 case len(vers) > 3:
286 return []check.Result{check.FailR("kernels", fmt.Sprintf("%d kernel versions installed (recommended at most 3)", len(vers)), vers...)}
287 }
288 return []check.Result{check.PassR("kernels", fmt.Sprintf("%d kernel version(s) installed", len(vers)), vers...)}
289 },
290 })
291
292 check.Register(&check.Check{
293 ID: "HOST-PKG-005", Title: "Running Kernel Older Than Installed Kernel", Domain: check.DomainHost,
294 Severity: check.Medium, Category: check.CatPatch, Effort: check.EffortMedium,
295 CVSS: "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L",
296 Summary: "Checks that the kernel in memory is the newest kernel installed, so kernel security fixes are actually in effect.",
297 Rationale: "A kernel update protects nothing until the machine boots into it. The package manager reports the update as installed, so the gap is invisible unless the running version is compared with the installed one. HOST-UPD-002 reads the Debian reboot marker; this check works on every distribution by comparing versions.",
298 Detection: "Runs the allow-listed command uname -r and compares it with the installed kernel versions (linux-image-<version> packages, or kernel and kernel-core from rpm -qa). If the newest installed version is newer than the running one, the check fails. Incomplete when the running version or the package list cannot be read; skipped when no kernel package is installed.",
299 Fix: "Schedule a maintenance window and reboot into the newest kernel. Where downtime is a problem, use the distribution's live patching service and still plan regular reboots.",
300 Verify: "uname -r should match the newest version in dpkg -l 'linux-image-*' | grep ^ii or rpm -q kernel-core .",
301 Refs: []check.Ref{refNIST80040, refNIST80053},
302 Standards: check.Standards{CWE: []string{"CWE-1104"}, OWASP: []string{"A06:2021"}, NIST: []string{"SI-2", "RA-5"}, CISTopic: "Apply installed kernel updates (CIS Controls v8 7.4)"},
303 Run: func(ctx context.Context, s sys.System) []check.Result {
304 out, err := s.Run(ctx, "uname", "-r")
305 running := strings.TrimSpace(out)
306 if err != nil || running == "" {
307 return []check.Result{check.IncompleteR("kernel", "uname -r could not be run")}
308 }
309 pkgs, ok := installedPackages(ctx, s)
310 if !ok {
311 return []check.Result{check.IncompleteR("kernel", "No package database could be read (dpkg status, dpkg info or rpm)")}
312 }
313 vers := kernelVersions(pkgs)
314 if len(vers) == 0 {
315 return []check.Result{check.SkipR("kernel", "No kernel packages are installed (container or unusual packaging)")}
316 }
317 newest := vers[len(vers)-1]
318 // RPM kernels report an architecture suffix in uname that the
319 // package version lacks.
320 cmp := running
321 for _, arch := range []string{".x86_64", ".aarch64", ".ppc64le", ".s390x"} {
322 cmp = strings.TrimSuffix(cmp, arch)
323 }
324 if kernelLess(cmp, newest) {
325 return []check.Result{check.FailR("kernel", "Running kernel "+running+" is older than installed kernel "+newest+"; a reboot is needed")}
326 }
327 return []check.Result{check.PassR("kernel", "Running kernel "+running+" is the newest installed")}
328 },
329 })
330
331 check.Register(&check.Check{
332 ID: "HOST-PKG-006", Title: "Package Lists Not Refreshed Automatically", Domain: check.DomainHost,
333 Severity: check.Low, Category: check.CatPatch, Effort: check.EffortLow,
334 CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
335 Summary: "Checks that the package manager downloads fresh repository metadata on a schedule, so pending updates can be seen at all.",
336 Rationale: "apt and dnf only know about updates listed in the metadata they last downloaded. Without a timer that refreshes it, HOST-PKG-001 and the administrator's own apt list --upgradable both report a clean system that is weeks behind. The refresh is read-only and separate from installing anything.",
337 Detection: "apt: reads /etc/apt/apt.conf.d/* for APT::Periodic::Update-Package-Lists \"1\" or asks the allow-listed systemctl is-enabled whether apt-daily.timer is enabled. dnf: asks whether dnf-makecache.timer is enabled. Skipped when neither package manager is present; Incomplete when apt configuration cannot be read.",
338 Fix: "Debian/Ubuntu: create /etc/apt/apt.conf.d/20auto-upgrades with APT::Periodic::Update-Package-Lists \"1\"; (dpkg-reconfigure unattended-upgrades writes it) and systemctl enable apt-daily.timer . RHEL family: systemctl enable --now dnf-makecache.timer .",
339 Verify: "systemctl is-enabled apt-daily.timer or systemctl is-enabled dnf-makecache.timer",
340 Refs: []check.Ref{{Title: "Debian, UnattendedUpgrades (APT::Periodic settings)", URL: "https://wiki.debian.org/UnattendedUpgrades"}, refNIST80040},
341 Standards: check.Standards{CWE: []string{"CWE-1104"}, OWASP: []string{"A06:2021"}, NIST: []string{"SI-2", "RA-5"}, CISTopic: "Automated patch management (CIS Controls v8 7.4)"},
342 Run: func(ctx context.Context, s sys.System) []check.Result {
343 switch {
344 case aptPresent(s):
345 files, unreadable := readAll(ctx, s, aptConfFiles...)
346 for _, f := range files {
347 for _, l := range activeLines(f.Text) {
348 if strings.HasPrefix(l, "//") {
349 continue
350 }
351 if strings.Contains(l, "APT::Periodic::Update-Package-Lists") && strings.Contains(l, `"1"`) {
352 return []check.Result{check.PassR(f.Path, "APT::Periodic::Update-Package-Lists is 1")}
353 }
354 }
355 }
356 if len(enabledUnits(ctx, s, "apt-daily.timer")) > 0 {
357 return []check.Result{check.PassR("apt-daily.timer", "apt-daily.timer is enabled")}
358 }
359 if len(unreadable) > 0 {
360 return []check.Result{check.IncompleteR("apt", "Some apt configuration could not be read", unreadable...)}
361 }
362 return []check.Result{check.FailR("apt", "Package lists are not refreshed automatically (no APT::Periodic::Update-Package-Lists and apt-daily.timer not enabled)")}
363 case dnfPresent(s):
364 if len(enabledUnits(ctx, s, "dnf-makecache.timer")) > 0 {
365 return []check.Result{check.PassR("dnf-makecache.timer", "dnf-makecache.timer is enabled")}
366 }
367 return []check.Result{check.FailR("dnf", "dnf-makecache.timer is not enabled")}
368 }
369 return []check.Result{check.SkipR("packages", "No apt or dnf configuration found")}
370 },
371 })
372}
373
374// repoHostOrURI returns the host of a repository URI, or the URI itself for
375// local sources, for use in evidence.
376func repoHostOrURI(uri string) string {
377 if h := repoHost(uri); h != "" {
378 return h
379 }
380 return uri
381}

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-006 Package Lists Not Refreshed Automatically (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/HOST-PKG-006.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_006,
  author       = {{OpenVAPT}},
  title        = {{HOST-PKG-006 Package Lists Not Refreshed Automatically}},
  year         = {2026},
  howpublished = {\url{https://openvapt.com/checks/HOST-PKG-006.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.