- KEV
- prioritisation
- free tools
CISA KEV: which of your running Kubernetes images are on the list
What the CISA KEV catalogue is, why it should be your first filter, and a free kubectl + curl script that checks every image running in your cluster against it.
StackRadar Team
9 min read
A moderately sized cluster carries a few thousand vulnerability findings. A team of three fixes a handful a week. So the first cut has to shrink the list to something finite, and the best first cut is not severity. It is evidence of exploitation.
The CISA Known Exploited Vulnerabilities catalog (KEV) is that evidence, as a free JSON file. To find out which images running in your cluster right now contain a KEV CVE, you need four steps: list the image digests the kubelets report, scan each one, keep the CVE IDs, and intersect them with the KEV feed. The script below does exactly that with kubectl, curl, jq and Grype or Trivy. First, what the list is and what it is not.
What KEV is: CVEs someone has already exploited
CISA started the catalogue in November 2021. A CVE gets in only if it meets three criteria: it has a CVE ID, there is reliable evidence of active exploitation in the wild, and there is a clear remediation action, such as a vendor update. The JSON feed needs no account or key. 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 do not protect ...",
"requiredAction": "For all affected software assets for which updates exist, the only acceptable remediation actions are: 1) Apply updates; OR 2) remove affected assets ...",
"dueDate": "2021-12-24",
"knownRansomwareCampaignUse": "Known",
"forensicTriage": "No",
"notes": "https://nvd.nist.gov/vuln/detail/CVE-2021-44228",
"cwes": ["CWE-20", "CWE-400", "CWE-502"]
}Two properties make it useful:
- It is small. The 25 September 2026 release lists 1,726 CVEs in total, against more than 40,000 new CVEs a year. About 300 entries were added in the last twelve months, a few at a time, on two or three days most weeks. A KEV hit in your cluster is a short, finite list.
- It is an observation, not a prediction. A CVSS score estimates impact and an EPSS probability forecasts exploitation. A KEV entry means someone has already seen the CVE used against a real target.
That second property is why StackRadar's Radar Score treats KEV membership as certainty instead of blending it with a probability. A CVSS 7.5 in KEV contributes 75 points; a never-exploited 9.8 at the median EPSS score contributes about 18. The KEV badge also shows on a finding wherever it sorts.
The due dates now come from BOD 26-04
KEV was launched with Binding Operational Directive 22-01, which gave US federal civilian agencies two weeks to fix each listed CVE (six months for CVEs from before 2021). On 10 June 2026 CISA revoked it and replaced it with BOD 26-04. The catalogue stays, but deadlines now depend on four questions: is the asset publicly exposed, is the CVE in KEV, can an attacker automate the exploit, and does it give partial or total control. For a KEV CVE the answer is 3 or 14 calendar days, and the worst cases (total control of an exposed or automatable target) add forensic triage. Of the 109 entries added between 10 June and 25 September 2026, 86 got a 3-day due date, and 67 of those were flagged for forensic triage.
The directive binds federal agencies only. If you do not have a patch SLA for exploited CVEs yet, the dueDate field is a reasonable default.
What KEV is not
- Not complete. If CISA has not seen reliable evidence of exploitation, the CVE is not listed. IngressNightmare (CVE-2025-1974, in the ingress-nginx controller that was retired in March 2026) is not in the catalogue, even though proof-of-concept exploits were published. Absent from KEV means "not confirmed", never "safe". EPSS fills that gap.
- Not a container list. Entries are keyed on vendor and product, not on package names or PURLs. The libwebp heap overflow (CVE-2023-4863) is filed as "Google Chromium WebP", and the 31 "Linux Kernel" entries say nothing about which Debian package version is affected. Match on the CVE ID against an SBOM, never on the product name. Kernel CVEs belong to your nodes, not your images.
- Not ranked internally. A 2017 entry and yesterday's sit side by side. The only ordering hints are
dueDate,forensicTriage,dateAddedandknownRansomwareCampaignUse("Known" for 361 of the 1,726 entries).
The script: running images × KEV
Ask the kubelets which image digests are running, not the manifests. Scan each one for CVE IDs, then intersect with KEV. Most people get the first step 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 the image the container runtime actually pulled, digest included. It differs from spec.containers[].image whenever a tag has moved. It also covers init containers and sidecars that a pipeline scanner never saw. Nodes that run Docker prefix it with docker-pullable://, hence the sed.
The whole check, with Grype as the scanner:
#!/usr/bin/env bash
# kev-check.sh — which running images in this cluster contain a CISA KEV CVE?
# Needs: kubectl (read access to pods), curl, jq, grype (https://github.com/anchore/grype),
# and pull access to the cluster's registries (docker login).
set -euo pipefail
export LC_ALL=C # comm needs both lists sorted the same way
PLATFORM="${PLATFORM:-linux/amd64}" # your nodes' architecture, not your laptop's
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 | tr -d ' ') 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 | tr -d ' ')" >&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
if ! report=$(grype -q -o json --platform "$PLATFORM" "$image" < /dev/null 2>/dev/null); then
echo "SCAN FAILED: $image (check registry credentials)" >&2
continue
fi
hits=$(jq -r '.matches[] | .vulnerability.id, (.relatedVulnerabilities[]?.id)' <<< "$report" \
| { grep -E '^CVE-' || true; } | sort -u | comm -12 - kev-ids.txt)
if [ -n "$hits" ]; then
echo "$image"
echo "$hits" | sed 's/^/ /'
fi
done < running-images.txtThree details in it are easy to get wrong:
--platform. For a multi-arch image the kubelet usually reports the manifest-list digest, and a scanner on an Apple Silicon laptop would otherwise pull the arm64 variant of an image your amd64 nodes run. SetPLATFORM=linux/arm64for arm64 nodes, and run the script once per architecture on a mixed cluster.- Failed scans are printed, not swallowed. An image the scanner cannot pull, usually for lack of registry credentials, shows up as
SCAN FAILEDon stderr. A silent skip would look exactly like "no KEV hits". grep -E '^CVE-'. 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'srelatedVulnerabilitiescarries the CVE alias of a GHSA match; Trivy reports the CVE directly where one exists.
For Trivy, swap the scan and the parse:
# Same loop with Trivy instead of Grype — replace the scan and the parse in step 3:
if ! report=$(trivy image -q -f json --platform "$PLATFORM" "$image" < /dev/null 2>/dev/null); then
echo "SCAN FAILED: $image (check registry credentials)" >&2
continue
fi
hits=$(jq -r '.Results[]?.Vulnerabilities[]?.VulnerabilityID' <<< "$report" \
| { grep -E '^CVE-' || true; } | sort -u | comm -12 - kev-ids.txt)The output lists only the images with hits, plus any image it could not scan. This run used a test list with an old debian:12.1 image, which still has the glibc "Looney Tunables" privilege escalation, and one deliberately unpullable image:
$ ./kev-check.sh
KEV catalogue: 1726 CVEs
Running images: 3
docker.io/library/debian@sha256:eaace54a93d7b69c7c52bb8ddf9b3fcba0c106a497bc1fdbb89a6299cf945c63
CVE-2023-4911
SCAN FAILED: docker.io/library/nope-does-not-exist-xyz@sha256:0000…0000 (check registry credentials)The one-command version
The free, Apache-2.0 StackRadar CLI runs the same three steps. It attributes each hit to its pods, deduplicates by digest, picks each node's architecture itself, and returns 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 kevIt sends nothing to StackRadar. Image pulls go to your registries, package names and versions go to api.osv.dev, and one GET fetches the KEV JSON from cisa.gov. Introducing the StackRadar CLI covers the rest of its flags.
Both the script and the CLI give you a point-in-time answer. KEV grows most weeks, so an answer that was "none" on Monday can be "two" on Thursday without anything in the cluster changing.
Making it continuous without rescanning
KEV checks get skipped because nobody wants to rescan every image every day. You do not have to. An image's SBOM does not change. If you keep it, the daily check is a join between two lists, the stored components and today's KEV feed, and no image is pulled.
To build it yourself: store the scanner's JSON output per digest, poll the KEV feed daily (its catalogVersion field changes on each release), re-run the intersection, and alert on new hits. The script above is the inner loop; the storage and the diff are the part worth owning or buying.
What StackRadar does: the in-cluster scanner uploads a CycloneDX SBOM once per running image, and the KEV catalogue is re-synced daily. Each finding's contribution to the Radar Score is a database column generated from its KEV field, so a CVE added today moves the affected images to the top of the list the same day. A KEV alert tells you when CISA adds a CVE you already run. The mechanics are on the continuous scanning page.
What to do with a hit
- Read the
requiredActionand thedueDate. For most library CVEs the action is "apply updates"; for some it is "discontinue use". The due date is a sane SLA even if you are not a federal agency. - 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. If the project is retired and no fixed release will come, the fix is to replace it. 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 of CVEs that the US Cybersecurity and Infrastructure Security Agency has reliable evidence are being exploited in the wild. CISA has maintained it since November 2021. Each entry records the vendor and product, the date it was added, a required action, a due date for US federal civilian agencies, whether the CVE is known to be used in ransomware campaigns, and whether forensic triage is required.
How many vulnerabilities are in the KEV list?
The 25 September 2026 release lists 1,726 CVEs, about 300 of them added in the previous twelve months. Compare that with the 40,000-plus CVEs published every year: KEV is a tiny fraction of the total, which is what makes it useful as a first filter.
Does BOD 22-01 still apply?
No. CISA revoked BOD 22-01 on 10 June 2026 and replaced it with BOD 26-04, "Prioritizing Security Updates Based on Risk". The KEV catalogue carries on under the new directive, but due dates are now 3 or 14 calendar days depending on exposure, automatability and technical impact, instead of the old default of two weeks. Both directives bind US federal civilian agencies only.
Where do I download the KEV list?
CISA publishes it at cisa.gov/known-exploited-vulnerabilities-catalog as a JSON feed, a CSV file and a JSON schema. The feed needs no account or API key, and its catalogVersion and dateReleased fields tell you when it last changed. The script in this post reads the JSON feed.
Is KEV better than CVSS for prioritisation?
They answer different questions. CVSS says how bad exploitation would be; KEV says exploitation is already happening. CVSS alone ranks a never-exploited 9.8 above a 7.5 that attackers are using today. A practical order is KEV first, then EPSS to rank the CVEs nobody has confirmed as exploited yet, with CVSS as the measure of impact.
Does KEV cover container and Kubernetes vulnerabilities?
Only the ones that are exploited, and only under vendor and product names. Entries that turn up in container images include glibc (CVE-2023-4911), OpenSSL (Heartbleed), Bash (Shellshock), sudo, polkit, Log4j, Spring Framework, Apache Tomcat and libwebp. Some well-known Kubernetes CVEs, such as IngressNightmare (CVE-2025-1974), are not in it. KEV does not tell you whether a package is in your images; you need an SBOM or a scan for that.
More from the blog
All postsA new CVE should not need a new scan: how we keep 900,000 advisories current
Most scanners match an image once, when it is scanned. We mirror OSV.dev every hour, EPSS and KEV every day, and re-match every stored SBOM when the mirror changes. How, and what it does not solve.
10 min read
Most of your container vulnerabilities are not yours to fix
A container scan mixes three kinds of finding: your dependencies, the base image, and images someone else builds. How to separate them without hiding an exploited CVE.
7 min read
EPSS score explained for Kubernetes operators
EPSS score explained: what the 30-day exploitation probability means, how to read its percentile, which threshold to act on, and how it reorders a CVSS-sorted list.
8 min read
StackRadar is open source: a self-hosted Kubernetes vulnerability dashboard under AGPL-3.0
The server, the dashboard and the Helm chart are on GitHub under AGPL-3.0, the scanner and CLI under Apache-2.0. One Helm command installs the lot in your cluster, with no telemetry and nothing leaving it. What is open, why these licences, and where the cloud fits.
8 min read