CISA KEV: which of your running Kubernetes images are on the list
What the CISA Known Exploited Vulnerabilities catalog is, why it should be the first filter on any vulnerability list, and a free kubectl + curl script that checks every running image in your cluster against it.
StackRadar Team
· 8 min read
Every vulnerability list in a Kubernetes cluster is too long. A cluster of moderate size carries a few thousand findings, and a team of three can fix a handful a week. Any prioritisation scheme has to start by shrinking the list to something finite, and the single best first cut is not severity — it is evidence of exploitation. The CISA Known Exploited Vulnerabilities catalog is that evidence, in a JSON file, for free. This post explains what the list is and is not, and gives you a script that answers the only question that matters about it: which of the images running in my cluster right now contain a KEV CVE?
What KEV is
CISA added the catalog in November 2021 alongside Binding Operational Directive 22-01, which requires US federal civilian agencies to remediate every listed CVE by its due date. The bar for inclusion is deliberately narrow: the CVE must have an assigned ID, there must be reliable evidence of active exploitation, and there must be a clear remediation action (a patch, a mitigation, or "stop using it"). Each record looks like this:
{
"cveID": "CVE-2021-44228",
"vendorProject": "Apache",
"product": "Log4j2",
"vulnerabilityName": "Apache Log4j2 Remote Code Execution Vulnerability",
"dateAdded": "2021-12-10",
"shortDescription": "Apache Log4j2 contains a vulnerability where JNDI features ...",
"requiredAction": "For all affected software assets ... apply updates per vendor instructions ...",
"dueDate": "2021-12-24",
"knownRansomwareCampaignUse": "Known",
"cwes": ["CWE-20", "CWE-400", "CWE-502"]
}Two properties make it useful. It is small — around 1,400 entries against roughly 40,000 CVEs published a year — so a KEV hit is a finite, actionable set. And it is an observation, not a prediction: unlike a CVSS score or an EPSS probability, a KEV entry means someone has already watched this CVE being used against a real target. That is why StackRadar's Radar Score treats KEV membership as certainty rather than blending it with a probability, and why the KEV badge appears on a finding regardless of where it sorts.
What KEV is not
- Not complete. Exploitation that CISA has not seen reliable evidence of is not in the list. A CVE being absent from KEV means "not confirmed", never "safe". That is the gap EPSS fills.
- Not a container list. Entries are keyed on vendor and product, not on package names or PURLs. "Linux kernel" in KEV does not tell you which Debian
linux-imageversion is affected; the CVE ID does, once you have an SBOM to match it against. - Not prioritised within itself. A KEV entry from 2017 and one added yesterday sit side by side. The
dateAddedandknownRansomwareCampaignUsefields are the only ordering hints.
The script: running images × KEV
The idea is three steps. Ask the kubelets, not the manifests, which image digests are running; scan each one for CVE IDs; intersect with the KEV list. The first step is the part most people get wrong, so here it is on its own:
# Every distinct image digest the kubelets are running — app, sidecar and init
# containers alike — as reported by the container runtime, not by the manifest.
kubectl get pods -A -o jsonpath='{range .items[*]}{range .status.containerStatuses[*]}{.imageID}{"\n"}{end}{range .status.initContainerStatuses[*]}{.imageID}{"\n"}{end}{end}' \
| sed 's#^docker-pullable://##' | grep . | sort -u > running-images.txt
wc -l running-images.txtstatus.containerStatuses[].imageID is what the container runtime actually pulled, digest included. It differs from spec.containers[].image whenever a tag has moved, and it covers init containers and sidecars that a pipeline scanner never saw. Some runtimes prefix it with docker-pullable://, hence the sed.
The whole check, using Grype as the scanner:
#!/usr/bin/env bash
# kev-check.sh — which running images in this cluster contain a CISA KEV CVE?
# Needs: kubectl, curl, jq, and grype (https://github.com/anchore/grype).
set -euo pipefail
KEV_URL="https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
# 1. The KEV catalogue, as a sorted list of CVE IDs.
curl -fsSL "$KEV_URL" | jq -r '.vulnerabilities[].cveID' | sort -u > kev-ids.txt
echo "KEV catalogue: $(wc -l < kev-ids.txt) CVEs" >&2
# 2. Every distinct image digest the cluster is running.
kubectl get pods -A -o jsonpath='{range .items[*]}{range .status.containerStatuses[*]}{.imageID}{"\n"}{end}{range .status.initContainerStatuses[*]}{.imageID}{"\n"}{end}{end}' \
| sed 's#^docker-pullable://##' | grep . | sort -u > running-images.txt
echo "Running images: $(wc -l < running-images.txt)" >&2
# 3. Scan each image, keep only CVE IDs, intersect with KEV.
# Grype reports GHSA IDs for some ecosystems; relatedVulnerabilities carries the CVE alias.
while read -r image; do
ids=$(grype -q -o json "$image" 2>/dev/null \
| jq -r '.matches[] | .vulnerability.id, (.relatedVulnerabilities[]?.id)' \
| grep -E '^CVE-' | sort -u)
hits=$(comm -12 <(echo "$ids") kev-ids.txt || true)
if [ -n "$hits" ]; then
echo "$image"
echo "$hits" | sed 's/^/ /'
fi
done < running-images.txtOr with Trivy, swapping the scan line:
# Same loop with Trivy instead of Grype:
ids=$(trivy image -q -f json "$image" 2>/dev/null \
| jq -r '.Results[]?.Vulnerabilities[]?.VulnerabilityID' \
| grep -E '^CVE-' | sort -u)grep -E '^CVE-' matters. Both scanners report GitHub (GHSA-) and distro-specific IDs for many findings; the KEV list is CVE IDs only, so the intersection must be too. Grype's relatedVulnerabilities carries the CVE alias for a GHSA match; Trivy reports the CVE directly where one exists.On a laptop with registry credentials for the cluster's images, this runs in a few minutes for a cluster of fifty distinct images, and the output is short — typically zero to a handful of images, each with one to three CVEs. That is the list to fix this week.
The one-command version
The free, Apache-2.0 StackRadar CLI does the same three steps with pods attributed, deduplicated by digest, and an exit code you can gate on:
# The same answer in one command, with pods attributed and exit code 2 if anything hits:
stackradar scan --fail-on kev --only-fixedIt sends nothing to StackRadar — package names go to api.osv.dev and one GET fetches the KEV JSON from cisa.gov. Both the script and the CLI are point-in-time, though: the KEV list grows by a few entries a week, and the answer that was "none" on Monday can be "two" on Thursday without anything in the cluster changing.
Making it continuous
The reason KEV checks get skipped is that nobody wants to rescan every image every day. You do not have to. An SBOM is a fact about an image that does not change; if you keep it, the check is a join between two lists — the stored components and today's KEV feed — with no image pulled. StackRadar does exactly that: the scanner uploads a CycloneDX SBOM per running image once, the KEV catalogue is re-synced daily, and a finding's contribution to the Radar Score is a generated column over the KEV field, so an entry added today lifts the affected workloads to the top of every cluster's list the same day. The mechanics are on the continuous scanning page.
If you build it yourself instead, the recipe is: store the scanner's JSON output per digest, poll the KEV feed daily, diff the CVE set, and page on new intersections. The script above is the inner loop; the storage and the diff are the part worth owning or buying.
What to do with a hit
- Read the
requiredAction. For most library CVEs it is "apply updates"; for a handful it is "discontinue use". - Find the fixed version for your package, not the upstream one — distro backports mean the Debian fix is often a
+deb12u3suffix, not a new upstream release. CVE patching in Kubernetes covers why the version numbers lie. - Fix it where it is deployed: bump the chart, rebuild on a newer base image, or move to a newer tag. Then confirm the running digest changed — step one of the script is also the verification.
Frequently asked questions
What is the CISA KEV catalog?
The Known Exploited Vulnerabilities catalog is a list, maintained by the US Cybersecurity and Infrastructure Security Agency since November 2021, of CVEs for which CISA has reliable evidence of exploitation in the wild. Each entry records the vendor and product, the date it was added, a required action, a due date by which US federal civilian agencies must remediate it under Binding Operational Directive 22-01, and whether it is known to be used in ransomware campaigns.
How many vulnerabilities are in the KEV list?
Around 1,400 as of 2026, growing by a few entries a week. That is a tiny fraction of the roughly 40,000 CVEs published each year, which is exactly what makes the list useful as a first filter: a KEV hit in a running image is a small, finite, actionable set.
Where do I download the KEV list?
CISA publishes it as JSON and CSV at cisa.gov/known-exploited-vulnerabilities-catalog. The JSON feed URL is stable and unauthenticated, updates the same day entries are added, and is the file the script in this post fetches. There is no API key and no rate limit that matters for a daily poll.
Is KEV better than CVSS for prioritisation?
They answer different questions. CVSS says how bad exploitation would be; KEV says it is already happening. For deciding what to patch this week, KEV first is the consensus advice from CISA, FIRST and most practitioners, with EPSS to rank what remains. CVSS alone ranks a never-exploited 9.8 above the 7.5 attackers are using today.
Does KEV cover container and Kubernetes vulnerabilities?
Yes, to the extent they are exploited. Entries include the Linux kernel, OpenSSL, glibc, curl, ingress-nginx, various container runtimes and many application libraries. What KEV does not do is tell you whether the affected package is in your images — that is the job of the SBOM and the script below.