CVE Triage for a Homelab: Which Ones Actually Matter
How to read a wall of vulnerabilities without losing your mind

Contents
The morning my vulnerability scanner reported 1,404 CVEs across a dozen containers, I nearly turned it off and went back to blissful ignorance. The number was accurate and completely useless at the same time, because a raw count tells you nothing about which of those 1,404 could actually be used against anything I run. Most were in libraries my services never call, sitting in a base image, reachable by no attacker who wasn’t already root on the host. A few were the sort of thing that ends up in an incident report.
Vulnerability triage is the skill of telling those two groups apart quickly, before the sheer volume trains you to ignore the scanner entirely. That last failure mode is the real risk in a homelab: alert fatigue is how a genuinely exploitable flaw ends up buried on page nine of a report nobody reads any more.
Why the severity score misleads you
Every CVE you see comes stamped with a CVSS score, and the instinct is to sort by it descending and start at the top. That instinct is wrong often enough to be dangerous. The CVSS base score describes the severity of a flaw in the abstract, in a hypothetical worst case, with no idea whether the vulnerable code path is ever executed in your deployment. A 9.8 “critical” in an XML-parsing routine is a 9.8 whether or not your application has ever parsed a byte of XML.
CVSS does define temporal and environmental metrics that would fix this, but almost nobody publishes them, and scanners rarely compute them for you. So the score you actually see is the least useful of the three. Treating it as a work queue means you spend your Saturday chasing a theoretical remote-code-execution bug in a font-rendering library that only runs when someone uploads a TrueType file to a service that has no upload feature, while a boring “medium” in your reverse proxy — the one thing genuinely facing the internet — waits its turn.
The question that matters is whether this can be used against my setup, given how my setup is actually wired — how bad it might theoretically be for someone else, somewhere, barely enters into it. Answering that needs context the score does not carry.
The questions that decide whether a CVE matters
Before touching a single package, I run each candidate through a short set of filters. They are ordered so the cheapest, most decisive checks come first.
- Is the affected thing even running? A CVE in
imagemagickis irrelevant if that binary is present in the image but never invoked. Installed and reachable are different states. - Is it exposed? A flaw reachable only from
localhost, or only from inside a trusted network segment, has a completely different risk profile from one on a service published to the internet. My internet-facing surface is tiny and I know exactly what is on it, so this filter clears most of the list immediately. - Is it being exploited in the wild right now? This is the single most valuable signal, and I lean on other people’s research for it (more below).
- Is there a fix available? An unfixable CVE and a fixable one demand different responses. You cannot
apt upgradeyour way out of the former, so it goes into a separate bucket for compensating controls rather than the patch queue. - What is the blast radius if it does go wrong? A compromise of the container that holds nothing but a static marketing page is an annoyance. A compromise of the box that can reach my backups is an emergency.
Notice that CVSS severity is not on that list until quite late. It becomes a useful tiebreaker once exposure and exploitability have already thinned the field, and it is a poor place to start.
Let CISA and FIRST do the hard research
Two public data sources turn triage from guesswork into something closer to arithmetic.
The CISA KEV catalogue — Known Exploited Vulnerabilities — is a list of CVEs with confirmed, observed exploitation in the real world. It is deliberately small (a few thousand entries against the hundreds of thousands of CVEs in existence) and that smallness is the point. If a CVE is in KEV, someone is definitely using it against real targets, and it jumps the queue regardless of its CVSS number. If it is not in KEV, exploitation is at least not yet widespread. The catalogue is free JSON you can pull and cache.
EPSS — the Exploit Prediction Scoring System, run by FIRST — gives every CVE a probability between 0 and 1 that it will be exploited in the next 30 days, based on a model trained on real exploitation data. Where CVSS asks “how bad if”, EPSS asks “how likely”. A CVE with a CVSS of 9.8 but an EPSS of 0.0004 is a bad bug that essentially nobody is bothering to weaponise. A CVSS 6.5 with an EPSS of 0.7 deserves your Saturday.
Used together the logic is simple. Anything in KEV is urgent. Anything with a high EPSS is a strong candidate. Everything else gets patched on the normal cadence and does not wake you up. This is roughly the prioritisation model that mature security teams have converged on, and it scales down to a homelab beautifully because it replaces “read 1,400 descriptions” with “check two lists”.
A triage pipeline you can actually run
Here is the shape of what I run on a schedule. I scan images with Trivy, keep only the fixable findings, then decorate each one with its KEV and EPSS status so I can sort by something meaningful. The scanner and the enrichment are separate steps on purpose, so I can re-enrich yesterday’s scan when the KEV catalogue updates without rescanning everything.
| |
The output is a short, ranked table. Anything with KEV=true I treat as this-week work. Anything with an EPSS above roughly 0.1 I look at next. The long tail of KEV=false EPSS=0.0007 findings I patch on the monthly rebuild and otherwise forget about. That single --ignore-unfixed flag alone typically removes more than half the raw count, because base-image CVEs with no upstream fix are noise you can do nothing about today anyway.
The reason to script it rather than eyeball the Trivy output is consistency. When triage is a manual read, your threshold drifts with your mood and how tired you are. When it is a pipeline, the box that decides “urgent” is the same every time, and you can tune the one EPSS number instead of re-litigating every finding.
The CVEs you cannot fix, and what to do about them
A large share of homelab CVEs live in the base image — glibc, openssl, the shell, coreutils — and have no fixed version available when you scan. Waking up to forty “critical” glibc findings that Debian has not yet patched is demoralising and teaches you to distrust the whole exercise. Two things help.
First, shrink the surface so there is less to be vulnerable in. A minimal or distroless base image carries a fraction of the packages of a full Debian image, which means a fraction of the CVEs, most of which you would never have been able to reach anyway. I go into the mechanics of this in distroless and multi-stage builds — it is the single most effective thing I have done to quieten a scanner, because a package that is not in the image cannot be reported.
Second, when you have genuinely assessed a finding and decided it does not apply, record that decision so the scanner stops shouting about it. Trivy reads a .trivyignore file, and the discipline is to always annotate why.
| |
An ignore file with reasons is an audit trail of your own judgement. An ignore file full of bare CVE IDs is a landmine your future self will step on, because in six months you will have no idea whether you suppressed something after careful thought or in a fit of Friday-afternoon impatience.
Troubleshooting the triage itself
The scanner reports different counts on the same image. Vulnerability databases update constantly, so a rescan an hour later legitimately shifts. What should not shift is your enriched, filtered list from the same scan artefact — which is exactly why the pipeline above saves scan.json and enriches a copy rather than piping everything through in one breath.
EPSS lookups are slow or rate-limited. The FIRST API is generous but querying it one CVE at a time over a long list is rude and sluggish. Batch the request — the endpoint accepts comma-separated CVEs — or download the full daily EPSS CSV once and join against it locally. For a homelab the daily bulk file is the calmer option.
A “critical” refuses to disappear after you patch. Usually the fixed package is installed but an older, vulnerable copy is still baked into an earlier layer, or a dependency pinned it back. Rebuild from scratch rather than rebuilding on cache, and rescan. If it still shows, confirm the scanner’s database actually knows about the fixed version yet — occasionally the CVE feed lags the distro’s advisory by a day.
You patched the base image and the app broke. This is the argument for testing image rebuilds in CI before they reach anything you care about, so a security bump gets the same smoke test as a feature change. Wiring your builds through Gitea Actions means the rebuild-and-rescan loop happens automatically on a push rather than by hand at 11pm.
Is this worth doing at home?
For a homelab that hosts nothing but yourself and never faces the internet, honestly, casual patching plus a minimal base image gets you most of the way, and formal triage is overkill. The moment you publish anything — a reverse proxy, a Git server, a photo gallery your family uses — the calculus changes, because now there is an attacker population that runs the exact KEV catalogue you should be reading, pointed at the exact services you exposed.
The genuine payoff is not that you patch more. It is that you patch less, with more confidence, because you have a defensible reason to leave nine hundred findings alone this week. Triage is what lets you keep the scanner switched on without it running your life, and a scanner you actually read beats a comprehensive one you have learned to ignore. If you set up nothing else, wire in KEV lookups and a --ignore-unfixed filter — that combination turns an unreadable wall into a list short enough to act on before you lose interest.




