• 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

install and scan
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 scan

On 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.

more ways to scan
# 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 --serve

The 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:

ci
# 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-fixed

Exit 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.
DECLARED · MANIFESTSRUNNING · KUBELETapp:v2tag re-pushedThe digest app:v2 points at nowin no manifestadmission webhookInjected mesh sidecarin no manifestby handA colleague’s kubectl runin no manifestleft behindOne-off Job, never cleaned up

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:

  1. List pods through your existing kubeconfig.
  2. Collect the digests the kubelets report and deduplicate them. A hundred replicas of one digest is one scan.
  3. Pull each image from your registry and match its OS and language packages against OSV with the embedded osv-scanner library.
  4. 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.
YOUR CLUSTERSTACKRADAR SCANRunning podslisted throughyour kubeconfigDigestsas the kubeletreports themPull & matchyour registry,then OSVFindingsattributed to pods,KEV first100 replicas = 1 scanembedded osv-scanner

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 login to 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 --image mode 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

YOUR CLUSTERYOUR MACHINEWHAT IT TALKS TOYour clusterlist pods only;Secrets on requeststackradar scanone static binaryYour registriesimage pullsapi.osv.devpackage names and versionscisa.govone GET for the KEV catalogueStackRadarnothing is sent
Image pulls go to your registries, package names and versions go to api.osv.dev, and one GET goes to cisa.gov for the KEV catalogue (cached for 24 hours). That is the full list. No account, no sign-up, no telemetry. Cluster secrets are never read unless you pass --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.

All posts