- announcements
- open source
- CLI
Introducing the StackRadar CLI: scan what your cluster is actually running
stackradar scan is a free, Apache-2.0 CLI that finds known and exploited CVEs in every image your Kubernetes cluster is running. No agent, no account, no telemetry.
StackRadar Team
5 min read
A CVE hits the news and someone asks "are we affected?". The honest answer usually takes an hour: find every cluster, work out which images are deployed, scan them one at a time, and hope nothing was redeployed since the manifest you are reading was written. stackradar scan answers it in about a minute, from your laptop, using the images your cluster is running right now. Nothing gets installed in the cluster, and you don't need an account.
Try it in 30 seconds
brew install lockdep/tap/stackradar
# or, on any Linux or macOS machine
curl -fsSL https://raw.githubusercontent.com/lockdep/stackradar-cli/main/install.sh | sh
stackradar scanOn a terminal, stackradar scan asks which kubeconfig context to use. In CI it takes the current context. It needs one permission, list pods, the same access as kubectl get pods. If it can also list nodes, it pulls each image for the architecture its node runs. If it can't, it assumes linux/amd64 and tells you so. Binaries for Linux, macOS and Windows (amd64 and arm64) are on the releases page.
# a specific context, two namespaces
stackradar scan --context prod -n payments -n checkout
# everything except the system namespaces
stackradar scan -N kube-system -N monitoring
# one image, no cluster needed
stackradar scan --image nginx:1.27
# JSON for scripts, a self-contained HTML report, or a live local dashboard
stackradar scan -o json > report.json
stackradar scan -o html --file report.html
stackradar scan --serveThe default output is a table sorted by severity. -o html writes the same interactive report that --serve opens locally, as one self-contained file you can attach to a ticket and open offline. In CI it works as a gate:
# exit 2 if anything running is in CISA's Known Exploited Vulnerabilities catalogue
stackradar scan --fail-on kev
# exit 2 on high or critical findings, and only count the ones with a fix
stackradar scan --fail-on high --only-fixedExit codes follow Grype's convention (0 clean, 1 error, 2 threshold met), so it drops into pipeline logic written for other scanners. Image pulls use the registry credentials you already have: ~/.docker/config.json, credential helpers, cloud keychains. With --use-pull-secrets it uses the cluster's own imagePullSecrets instead, and the report says which secrets it used.
Why it reads the kubelet, not your manifests
One design decision shapes everything else: where the list of images comes from. Most ways of answering "what are we running?" start from what was declared: Deployment specs, Helm values, the manifests in your GitOps repo. That list is wrong in quiet, systematic ways:
- Tags move. A mutable tag (
:latest,:v2, even a re-pushed:1.4.2) can point at a different image than it did when the pod started. The spec doesn't record which one. The node knows exactly which digest it pulled. - Webhooks inject containers. Service-mesh sidecars, secrets agents and tracing proxies run in your cluster with your network access, and an inventory built from specs has never heard of them.
- Clusters collect strays. A colleague's
kubectl run, an operator's workloads, a one-off Job nobody cleaned up. No repo declares them.
The kubelet reports what is actually there. Each running pod's status.containerStatuses[].imageID holds the exact digest the container runtime is executing, for app, init and injected containers alike. So the CLI starts there:
- List pods through your existing kubeconfig.
- Collect the digests the kubelets report and deduplicate them. A hundred replicas of one digest is one scan.
- Pull each image from your registry and match its OS and language packages against OSV with the embedded osv-scanner library.
- Attribute findings back to the namespaces and pods that run them, and flag anything in CISA's Known Exploited Vulnerabilities catalogue so the CVEs being exploited in the wild sort first.
Starting from pods also keeps the permissions small. A tool that walks the workload hierarchy from the spec side has to list Deployments, StatefulSets, CronJobs and a dozen other kinds across the cluster. This one needs to see pods. If you can run kubectl get pods, you can run it.
What it deliberately does not do
A launch post that only lists strengths is an advert, so here are the limits, stated the way we'd want another vendor to state them:
- It is a snapshot. A scan is true for the moment it ran. Tomorrow's advisory can make today's clean image vulnerable without a single deploy, and the CLI does not watch for that. Keeping the answer current is the job of the in-cluster scanner, which is the part of StackRadar we charge for.
- It only knows what OSV knows. Debian, Ubuntu, Alpine and the mainstream language ecosystems are well covered. At the time of writing, OSV has no feed for Amazon Linux, Oracle Linux, Fedora or Photon OS. Trivy and Grype cover Amazon Linux and Oracle Linux, so if your base images use those, they will find OS-package CVEs that this CLI can't.
- Counts will not match Trivy or Grype exactly. No two scanners agree on the same image. They find different packages, and they score severity differently. We use the highest CVSS base score an OSV advisory carries, while Trivy prefers the distro's own rating, so our severities on distro packages can read higher. Advisories with no CVSS score are shown as Unscored, not quietly filed under low. The tools roundup explains where the disagreements come from.
- It can only scan what your machine can pull. An image behind credentials you do not hold is reported as not reachable from here, with the reason and the
docker loginto run. It is never silently skipped and never guessed at. - It does not replace scanning in CI. Before deploy, the artefact you are about to ship is exactly what you want to scan. Trivy and Grype are good at that job, and the CLI's
--imagemode does it too. "What is running right now?" is a different question, and it is the one that gets asked during an incident.
Nothing is sent to StackRadar
--use-pull-secrets, and then the credentials stay in memory for the pull.The CLI is Apache-2.0 and ships as a single static Go binary. Every release's checksums are signed with cosign from the repository's GitHub Actions identity, and the verification commands are on the CLI page.
Where it fits
stackradar scan is the free, standalone answer to "are we affected, right now?". Get it from GitHub or the CLI page, run it against your cluster, and tell us what it gets wrong. Issues and scan discrepancies (a redacted -o json is ideal) are exactly the feedback we want.
When the question becomes "will we know when we become affected?", that is the continuous half of StackRadar: an in-cluster agent that keeps a CycloneDX SBOM of every running image and re-matches it as advisories land, with every cluster in one dashboard.
More from the blog
All postsStackRadar 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
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
Open-source vulnerability scanners for Kubernetes: what each one does and does not do
Trivy Operator, Kubescape, Grype and Syft, OSV-Scanner, the StackRadar CLI, and what became of KubeClarity: what each does, and what you still build yourself.
8 min read
A 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