Safeguard
Security Guides•

How do you add a purl or CPE to an SPDX package with ExternalRef?

In SPDX 2.3 an ExternalRef line holds category, type and locator. Use PACKAGE-MANAGER for a purl and SECURITY for a CPE. Syntax, examples and mistakes.

Safeguard Security Team
4 min read

In SPDX 2.3, identifiers like a purl or CPE go in an ExternalRef line with three parts: a category, a type and a locator. A purl goes under PACKAGE-MANAGER with type purl, and a CPE goes under SECURITY with a cpe type. The field is optional, and a package can have many.

The three parts

The 2.3.1 spec defines the format as category, then type, then locator separated by spaces. Category is one of SECURITY, PACKAGE-MANAGER, PERSISTENT-ID or OTHER. The type comes from a list in an annex of the spec. The locator is a string with no spaces that identifies the package in the target system. Cardinality is zero or more, so you can attach several.

The spec gives these examples.

ExternalRef: SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*
ExternalRef: PERSISTENT-ID swh swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2
ExternalRef: OTHER LocationRef-acmeforge acmecorp/acmenator/4.1.3-alpha

For a purl, the common form uses the package-manager category and the purl type.

ExternalRef: PACKAGE-MANAGER purl pkg:npm/lodash@4.17.21

Which category for which identifier

IdentifierCategoryWhat it is for
purlPACKAGE-MANAGERNaming a package in an ecosystem registry
CPESECURITYLooking up known vulnerabilities by product name
Software Heritage IDPERSISTENT-IDA stable identifier for archived content
Anything elseOTHERCustom locators with a namespaced type

NTIA's minimum elements call these other unique identifiers and say they exist to map data across ecosystems. The report lists CPE, SWID and purl as common examples and says to use them if they exist.

Why more than one identifier helps

No single naming scheme covers every ecosystem, and NTIA says so. A purl matches package registry data well. A CPE matches the way some vulnerability databases name products. Carrying both increases the chance that a downstream tool finds the right records. It also means a mismatch between them is a useful signal that something was resolved wrongly.

Mistakes to avoid

Spaces in the locator. The spec defines the locator as a string with no spaces, so do not paste a display name.

A guessed CPE. CPEs are easy to fabricate and hard to verify. Only add one you can trace to a source, or leave it out.

Using OTHER for a standard type. If a listed type exists, use it so consumers can recognize it.

Forgetting the comment. There is a separate ExternalRefComment field for human context, such as how the CPE was derived.

Quick test

After generating an SPDX file, count packages with a purl reference. In a typical package manager project, nearly all should have one. The ones without are the places your inventory is weakest.

Safeguard can generate SBOMs and run dependency scanning across your repositories, so the fields discussed here are checked on real projects instead of by hand. You can try the free scan at gold.safeguard.sh/scan.

Try it yourself

Open the demo, click through the component list, and look at how each package is identified and what is missing.

Click through it at your own pace, or open it full screen.

Quick check

Can I add several ExternalRef lines to one package?

Yes. The cardinality is zero or more, so a package can carry a purl, a CPE and other references.

Is ExternalRef required?

No. It is optional in SPDX 2.3, though NTIA recommends using other unique identifiers where they exist.

Sources

Last reviewed: 19 October 2026.

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.