Open-source Kubernetes SBOM scanner. Star us on GitHub
Documentation menu

Vulnerability matching

How StackRadar matches SBOM components against 300K+ OSV.dev advisories: Package URL (PURL) matching, semver range evaluation, name-version fallback, and match confidence.

The data source: a local OSV.dev mirror

StackRadar matches against OSV.dev, Google's open, machine-readable vulnerability database that aggregates advisories from GitHub Security Advisories, PyPA, RustSec, Go vulndb, and many other ecosystem sources — 300K+ records. Rather than calling the OSV API per lookup, StackRadar maintains a full local mirror that is updated continuously from OSV's change feed. That means:

  • Matching runs against fresh data — a newly published advisory is applied to already-uploaded SBOMs, so you learn an existing image is affected without rescanning or redeploying it.
  • Lookups are local database queries, so full-cluster rescans are fast.
  • Your dependency inventory is never sent to a third-party API for matching.

Step 1 — Package URL (PURL) matching

Every component in a CycloneDX SBOM carries a Package URL — an ecosystem-qualified identifier like:

purl
pkg:npm/lodash@4.17.20
pkg:golang/github.com/gin-gonic/gin@1.9.0
pkg:deb/debian/openssl@3.0.11-1~deb12u2?arch=amd64

StackRadar strips the version and qualifiers to get the base PURL (pkg:npm/lodash) and looks up every advisory whose affected-package list matches it exactly. Matching on PURL rather than bare package names avoids the classic false-positive trap where openssl the Debian package, the Alpine package, and an npm package with a similar name are conflated.

Step 2 — version range evaluation

A PURL match only says "this package has had advisories". The component's version is then evaluated against each advisory's affected ranges, which OSV expresses as events:

EventMeaning
introducedStart of the affected range (inclusive)
fixedFirst safe version (exclusive end of range)
last_affectedLast known affected version (inclusive end)
limitHard upper bound on the range (exclusive)

Semver range evaluation applies to ecosystems with reliable semver ordering — npm, crates.io, Go, NuGet, Hex, and Swift — plus explicit affected-version lists for the rest. When an advisory carries a fix, the fixed version is surfaced with the finding so remediation is a version bump, not research.

Step 3 — name + version fallback

Components without a usable PURL fall back to name-and-version matching. Every finding records how it was matched:

Match metadataValues
Matched bypurl or name_version
Confidenceexact (version confirmed in an affected range) or partial

This is deliberately transparent: when you triage a finding, you can see whether it came from a precise PURL + version-range match or a fuzzier fallback, instead of every result pretending to be equally certain.

Severity and CVSS scoring

Severity is derived from the advisory in priority order:

  1. A CVSS v3.1 vector, parsed and scored numerically.
  2. A CVSS v4 vector, when that's all the advisory provides.
  3. A database-specific severity label from the source advisory.

Scores map to labels the standard way:

CVSS scoreSeverity
9.0 – 10.0Critical
7.0 – 8.9High
4.0 – 6.9Medium
0.1 – 3.9Low
Known-malicious packages (OSV MAL- advisories, e.g. typosquats and hijacked releases) are treated as Critical regardless of CVSS, since a malicious dependency is a compromise, not a weakness.

When matching runs

  • On SBOM ingestion — every new upload is matched immediately.
  • On database updates — as the OSV mirror ingests new and modified advisories, existing components are re-evaluated, which is how a CVE published today shows up on an image you deployed last month.

For what's inside the SBOMs being matched, see the CycloneDX format reference.