PURL, CPE, and the hardest problem in SCA: knowing what a component is
Ask three tools what is inside the same application and you will get three different answers. Not because the code differs, but because the tools disagree on names. Is it "apache log4j," "log4j-core," or "org.apache.logging.log4j:log4j-core"? Until everyone agrees on what a component is called, matching it against a vulnerability database is guesswork.
Two identifier systems dominate the SBOM world, and they were built for different eras.
CPE: the legacy system that won't leave
The Common Platform Enumeration was designed for the world the NVD grew up in: commercial products, installed on servers, identified by vendor and product name. A CPE string encodes vendor, product, and version, and NVD analysts assign them to CVEs by hand.
CPE has two structural problems for modern software. First, it was never designed for package ecosystems — there is no natural CPE for the 300 npm packages in your lockfile, and coverage of open-source libraries is inconsistent. Second, because CPEs are assigned manually downstream of disclosure, they arrive late and sometimes never.
PURL: an address, not a name
The Package URL takes the opposite approach: instead of a curated dictionary, a PURL is a deterministic address derived from where the package actually lives — its ecosystem, namespace, name, and version. Anyone can construct it from a lockfile without waiting for an authority to assign it. If you know the package, you know its PURL.
That determinism is why PURL has become the lingua franca of SBOM tooling, and why modern vulnerability sources keyed on ecosystems match against it directly.
Why your SBOM needs both
Here is the practical rub: package-ecosystem vulnerabilities match cleanly on PURL, but a large slice of the NVD — operating systems, firmware, commercial applications — is still keyed on CPE. An SBOM carrying only one identifier system is blind in one eye.
When we score SBOM quality, identifier coverage is one of the dimensions we weight, because it is the single best predictor of how well vulnerability matching will work downstream:
- Every component should carry a PURL if it came from a package ecosystem — this is nearly free at generation time and expensive to reconstruct later.
- CPEs matter for platform-level components — the OS packages and runtimes that ecosystem-native feeds don't cover.
- Hashes break ties — when names are ambiguous, a SHA-256 of the artifact is the identifier of last resort, and increasingly the one auditors trust most.
What to do about it
Generate SBOMs from the build, where the package manager knows exactly what it resolved — identifiers assembled after the fact, by scanning a filesystem, are where the "three tools, three answers" problem comes from. Then check what you produced: if your components are missing PURLs, your vulnerability report is not conservative, it is silently incomplete. That is a quality problem you can measure, and once you measure it, you can fix it at the source.