Safeguard
Supply Chain Security

Enriching SBOMs with Vulnerability and License Metadata

A base SBOM only lists what's in your build — OSV.dev, EPSS, and OpenSSF Scorecard turn that inventory into a prioritized risk decision.

Safeguard Research Team
Research
7 min read

A Software Bill of Materials, on its own, is a parts list. It names components, resolves versions, and — in CycloneDX 1.7 or SPDX 2.3 format — attaches a PURL or CPE identifier to each one. That is the entire job of a base SBOM, and it is also why an unenriched SBOM cannot answer the question every security team actually has: which of these 400 components should I fix first? OSV.dev, the Google-backed open vulnerability database, was purpose-built to answer that question more precisely than CPE matching alone, because its schema encodes exact affected version ranges per ecosystem rather than relying on the loosely structured product/vendor strings that have made NVD-only CPE matching notorious for false positives. FIRST.org's Exploit Prediction Scoring System (EPSS), maintained by its EPSS Special Interest Group, adds a second, orthogonal signal: a machine-learned probability that a given CVE will see exploitation in the wild in the next 30 days. Neither tool replaces the other, and neither replaces a license check or a maintenance-health signal. Enrichment is the practice of layering all of them onto a base inventory so a flat list of components becomes a ranked list of decisions. This piece walks through how that layering actually works.

What does "enrichment" add that a base SBOM doesn't have?

A base SBOM is a static snapshot: component name, resolved version, supplier, a PURL or CPE, sometimes a declared license and a cryptographic hash. None of those fields tell you whether a component is dangerous today. Enrichment attaches four additional layers on top of that inventory — known-vulnerability matches, exploit-likelihood scores, reconciled license data, and maintenance/health signals — usually stored as CycloneDX component properties or SPDX annotations so the enriched data travels with the SBOM rather than living in a separate spreadsheet. The practical effect is that the same SBOM you generated at build time keeps answering new questions as the threat landscape changes: when a new CVE drops next month, a team with an enriched, continuously re-matched inventory can query "are we affected, and where" in minutes, rather than re-scanning every repository from scratch. Enrichment turns the SBOM from a compliance artifact — something generated once to satisfy a procurement requirement — into an operational tool a security team actually queries during an incident.

How does vulnerability matching actually work against an SBOM?

Vulnerability matching takes each component's PURL or CPE and checks it against one or more vulnerability databases — the National Vulnerability Database (NVD), OSV.dev, and the GitHub Advisory Database are the three most widely used. NVD indexes CVEs against CPE strings, a format that predates modern package ecosystems and frequently mismatches vendor/product naming across npm, PyPI, Maven, and Go modules. OSV.dev, by contrast, aggregates ecosystem-native advisory sources — GitHub Security Advisories, the Python Packaging Authority's advisory database, RustSec, and others — into a schema keyed directly on package name plus precise semantic version ranges, which is why tools built on OSV data report meaningfully fewer false-positive matches than CPE-only scanners on ecosystems like npm and PyPI. Good enrichment pipelines don't pick one source; they query multiple databases and deduplicate, because NVD, OSV, and GitHub Advisories don't always disclose or backfill CVEs on the same schedule. The output attached to the SBOM is a per-component list: CVE ID, CVSS score, affected version range, and a fixed-version target — the raw material the next two enrichment layers prioritize.

How does EPSS change vulnerability prioritization?

CVSS scores severity — how bad a vulnerability would be if exploited — but it says nothing about the likelihood that anyone will actually exploit it. EPSS, maintained by FIRST.org's EPSS SIG, closes that gap with a machine-learning model that outputs a 0–1 probability that a given CVE will be exploited in the wild within the next 30 days, trained on signals including CVSS metrics, public exploit code availability, and observed scanning/mention volume across threat intelligence feeds. FIRST.org is explicit that EPSS is designed to complement CVSS, not replace it: a CVSS 9.8 vulnerability with a low EPSS score and no public exploit code is a different triage priority than a CVSS 7.5 vulnerability with a high EPSS score and active exploitation. When EPSS is attached as enrichment metadata alongside the CVE match, a team can set a policy like "block release on any finding with CVSS above 7 and EPSS above a chosen threshold" instead of triaging severity by hand for every one of hundreds of open findings — which is the same problem reachability analysis solves for code-level SAST/SCA findings, applied at the SBOM layer.

Why does license enrichment need more than the declared manifest field?

The license field a package manifest declares — the license key in package.json, the [project] license table in pyproject.toml, the <licenses> block in a Maven POM, or the license field in Cargo.toml — is self-reported by the package author and is not authoritative. Real license enrichment reconciles that declared value against the SPDX License List's canonical identifiers, scans the actual LICENSE and NOTICE files shipped in the package, and in ambiguous cases inspects source file headers directly. Discrepancies between these sources are a genuine and recurring signal, not an edge case: a manifest that says MIT while the shipped LICENSE file says AGPL-3.0 typically indicates a relicensing event the manifest metadata never caught up to, a packaging mistake, or a bundled sub-component under different terms — any of which changes the compliance obligations a downstream user actually has. Enrichment that only reads the manifest field, and never opens the license file itself, will silently propagate exactly this kind of error into every legal and compliance review built on top of the SBOM.

What do maintenance and health signals add beyond known CVEs?

A component with zero open CVEs today can still be a bad long-term bet, and CVE-matching alone has no way to say so. The OpenSSF Scorecard project runs a fixed set of automated checks against a package's source repository — branch protection, code review requirements, dependency pinning, presence of a security policy, how recently the project has shipped a release — and produces a score per check that can be attached as component properties on the SBOM itself, for example through the Hoppr Scorecard plugin's CycloneDX integration. These checks measure a different kind of risk than a CVE database: an abandoned or single-maintainer project with no branch protection and no security policy is a slow-burn liability even with a clean vulnerability history, since the next disclosed flaw in it may never get patched. Attaching Scorecard-derived health properties to the SBOM lets a team distinguish "actively maintained, currently vulnerable" from "unmaintained, currently clean" — two components that look identical in a CVE-only view but carry very different risk over a twelve-month horizon.

How Safeguard Helps

Safeguard's ESSCM module generates CycloneDX SBOMs automatically on every build and layers exactly this enrichment model on top of the base inventory rather than treating vulnerability, license, and health data as separate reports. License reconciliation runs across multiple sources — manifest declarations, SPDX compound-expression evaluation, and full-text scanning — and surfaces a distinct license-discrepancy finding whenever those sources disagree, instead of trusting the manifest field silently. Each component also carries a Risk Score that combines package health signals (maintenance activity, community trust, package age, typosquatting indicators) with SCAL attestation level, provenance verification, and behavioral analysis into a single weighted 0–10 score, shown alongside a separate Attestation Score that specifically measures supply-chain integrity — SLSA provenance, Sigstore signatures, and malicious-package detection. Because both scores live on the same SBOM record that Safeguard continuously re-scans, a team can see a component's vulnerability exposure, license posture, and maintenance health in one place, instead of reconciling three disconnected tools every time a release gate needs an answer.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.