SPDX 3.0 splits its model into profiles, and several go beyond the package list. Build records how an artifact was produced. Security records vulnerabilities with VEX, CVSS, EPSS, KEV and SSVC assessments. SimpleLicensing and ExpandedLicensing hold licenses as strings or objects, Lite sets a license compliance minimum, and Extension carries custom data.
This post walks through each of those profiles in the SPDX 3.0.1 specification class by class and property by property. It compares the Build profile with SLSA provenance, maps the SPDX 2.3 license fields to their SPDX 3 replacements, lists the exact Lite minimums, and ends with a worked JSON-LD document that uses Build and Security together. We validated every JSON example in this post against the official SPDX 3.0.1 JSON schema and with the spdx3-validate tool, which also runs the SHACL shapes. If you want the short version of what each profile identifier means first, read our explainer on which SPDX 3 profiles to declare in profileConformance, and for the wider 2.3 to 3.0 change list see what changed in the SPDX 3.0 specification.
Key takeaways
- Build adds one class,
Build, with nine properties. OnlybuildTypeis required. Inputs, outputs and the invoking agent are expressed as relationships (hasInput,hasOutput,invokedBy), all scoped to thebuildlifecycle. - Build is descriptive, SLSA provenance is verifiable. The fields overlap closely, but SLSA provenance is designed to be signed and checked against expectations. An SPDX Build element only becomes evidence once you sign the document that contains it.
- Security goes well past VEX. It defines a
Vulnerabilityelement and ten concrete assessment relationship classes: CVSS v2, v3 and v4, EPSS, exploit catalog (KEV), SSVC and four VEX statuses. - Licensing in SPDX 3 is two profiles. SimpleLicensing keeps license expression strings; ExpandedLicensing turns them into objects. Concluded and declared licenses moved from package properties to relationships, and several 2.3 fields were renamed or removed.
- Lite is a license compliance minimum. A Lite package must carry a version, a supplier, copyright text, a download location or package URL, and exactly one concluded and one declared license relationship.
- Extension lets you attach data SPDX does not define. In 3.0.1 the only concrete extension class is
CdxPropertiesExtension, a list of name and value pairs. Anything else needs your own class and a consumer that understands it. - Declare only what you populate. Listing a profile in
profileConformanceis a claim that every element meets that profile's rules.
How the profiles relate, and a short timeline
SPDX 3.0 is a graph of elements. The Core profile defines Element, Relationship, Agent, CreationInfo and the rest of the spine. The Software profile adds packages, files, snippets and the Sbom collection. The profiles in this post add more classes and properties on top, and a few add rules that tighten existing classes. The Core vocabulary ProfileIdentifierType lists ten identifiers in 3.0.1: ai, build, core, dataset, expandedLicensing, extension, lite, security, simpleLicensing and software. Its description says that when a creator lists a profile in profileConformance, "they are claiming that all contained elements conform" to that profile's restrictions.
In JSON-LD, every class and property outside Core carries a profile prefix: the 3.0.1 context file maps names such as build_buildType and security_VexNotAffectedVulnAssessmentRelationship to full IRIs, while Core terms such as Relationship and profileConformance have none.
| Date | Milestone | Source |
|---|---|---|
| 2021 | SPDX 2.2.1 published as the international standard ISO/IEC 5962:2021 | spdx.dev specifications page |
| After ISO/IEC 5962:2021 (SPDX 2.2.2) | Formatting fixes, no new fields; annex lettering fixed so SPDX Lite is Annex G | SPDX "differences from previous editions" document |
| After 2.2.2 (SPDX 2.3) | Annex G updated so Lite includes the NTIA minimum elements as required fields; more security external references | Same document |
| 6 November 2023 | SPDX Security profile team publishes its design notes for vulnerability data in SPDX 3.0 | spdx.dev blog |
| 9 July 2024 | spdx.dev points readers to the Linux Foundation announcement of SPDX 3.0 | spdx.dev blog |
| After 3.0 (SPDX 3.0.1) | Current published model pages and JSON schema used in this post | spdx.github.io and spdx.org |
We could not find an exact release day for 3.0 or 3.0.1 on the primary pages we fetched, so the table gives the dated posts we could confirm instead.
The Build profile, field by field
The specification defines a software build as "the act of converting software inputs into software artifacts using software build tools". The profile adds one class, Build, a subclass of Element. Everything else about a build is expressed through relationships.
Build properties
| Property | Type | Cardinality | What it records |
|---|---|---|---|
buildType | IRI | 1..1 | The kind of build: the toolchain, platform or infrastructure it ran on |
buildId | string | 0..1 | An identifier for this build instance, such as a CI run number |
buildStartTime | DateTime | 0..1 | When the build started |
buildEndTime | DateTime | 0..1 | When it finished |
configSourceUri | IRI | 0..* | Where the build configuration came from |
configSourceEntrypoint | string | 0..* | The entry point inside that configuration, such as a workflow file |
configSourceDigest | Hash | 0..* | A digest of the configuration source |
parameter | DictionaryEntry | 0..* | Parameters passed to the builder |
environment | DictionaryEntry | 0..* | Environment variables set before the build was invoked |
A few details in the property pages matter in practice.
buildType is the only required property. The spec describes it as an IRI that tells a reader how to interpret the other build fields. Two Build elements with the same buildType are the same kind of build, possibly with different configuration. If you are not using a well known value, the spec says to namespace it under a domain you own, and it points to the SLSA definition of buildType for background. Examples it gives are a GitHub Actions workflow, one step in such a workflow, a compiler invocation, or a script that orchestrates other builds.
parameter and environment are deliberately separate. The environment page says it holds variables "set before a build is invoked rather than the variables provided to the builder". In other words, parameter is what you asked the builder to do and environment is the context it ran in.
The start and end times are optional on purpose. The Build class page says they "may be omitted to simplify creating reproducible builds". If you produce bit for bit reproducible artifacts and also generate the SPDX document inside the build, timestamps would make the SPDX file itself differ from run to run.
Build logs have a defined convention. The class page says an ExternalIdentifier of type urlScheme can identify a log, with the identifier's comment set to LogReference.
Build relationships
The profile names three relationship types as the minimum set: hasInput from the build to its inputs, hasOutput to its outputs and invokedBy to the agent that started it. It also lists optional ones: hasHost for the build stage or host, configures from a configuration to the build, ancestorOf and descendantOf for nested builds, and usesTool for build tools. All relationships in the Build profile "are scoped to the build LifecycleScopeType period", which in JSON-LD means using LifecycleScopedRelationship with "scope": "build". The spec also allows hasInput to point at a configuration file or build tool when you do not know its exact role at the time you write the document.
How the Build profile compares with SLSA provenance
SLSA provenance v1 is an in-toto attestation predicate with the type https://slsa.dev/provenance/v1. It has two halves: buildDefinition, which holds buildType, externalParameters, internalParameters and resolvedDependencies, and runDetails, which holds the builder (with id, version and builderDependencies), metadata (invocationId, startedOn, finishedOn) and byproducts. Our post on SLSA v1 software provenance attestation covers the levels and verification flow; here is how the fields line up with SPDX Build.
| Question | SPDX 3.0.1 Build | SLSA provenance v1 |
|---|---|---|
| What kind of build was it? | buildType (IRI, required) | buildDefinition.buildType (required) |
| Which run? | buildId | runDetails.metadata.invocationId |
| When? | buildStartTime, buildEndTime | metadata.startedOn, metadata.finishedOn |
| What did the caller ask for? | parameter, plus configSourceUri, configSourceEntrypoint, configSourceDigest | externalParameters |
| What did the platform set on its own? | environment (partly) | internalParameters |
| What went in? | hasInput relationships to elements with their own hashes | resolvedDependencies (resource descriptors with digests) |
| Who or what ran it? | invokedBy an Agent, hasHost | runDetails.builder.id |
| Which tools? | usesTool relationships | builder.builderDependencies |
| What came out? | hasOutput relationships | The attestation subject, plus byproducts |
| Nested or multi stage builds | ancestorOf, descendantOf between Build elements | One provenance per build; chaining is done across attestations |
| Integrity | None built in; sign the SPDX document separately | Designed to be wrapped in a signed in-toto envelope |
| Trust levels | Not defined | SLSA Build levels define what the builder must guarantee |
The practical difference is about trust, not vocabulary. SLSA provenance is written by the build platform, signed, and checked by a verifier against expected values such as the builder ID and source repository. The SLSA levels describe how hard it should be for a tenant of the build platform to forge it. SPDX Build has no levels and no signing model of its own. It is a description that sits in the same graph as your packages, licenses and vulnerabilities, which makes it easy to query alongside them.
So the two complement each other. A reasonable pattern is to keep SLSA provenance as the signed, verifiable record of how an artifact was made, and to use the SPDX Build element to connect that build to the SBOM. You can point at the attestation from the Build element with an external reference, or record the provenance digest, so a reader of the SBOM can find and verify the signed record. If the SPDX document is the only build record you produce, sign it, for example with Sigstore, otherwise anyone who can edit the file can rewrite its history.
The Security profile: more than VEX
The Security profile page itself is two lines long: it "captures security related information". The substance is in its classes. The spdx.dev design notes from the Security profile team list the frameworks it was built to carry: CVE, ISO/IEC 29147 and 30111, OASIS CSAF, FIRST CVSS and EPSS, CISA SSVC and KEV, and VEX.
The Vulnerability element
Vulnerability is a subclass of Artifact. It adds three optional timestamps: publishedTime, modifiedTime and withdrawnTime. Identifiers such as a CVE or advisory ID go in externalIdentifier with types like cve or securityOther, and links to advisories and fixes go in externalRef with types like securityAdvisory and securityFix. You connect a vulnerability to the affected component with an ordinary Core relationship of type hasAssociatedVulnerability.
Assessment relationships
Everything else in the profile is a relationship that says something about a vulnerability with respect to a product. They all inherit from the abstract VulnAssessmentRelationship, which itself is a Relationship. That parent adds assessedElement (the specific component where the vulnerability sits, when it differs from the product), suppliedBy (who made the assessment) and the same three timestamps. The from end is the vulnerability and the to end is the product.
| Class | relationshipType | Required properties | What it says |
|---|---|---|---|
CvssV2VulnAssessmentRelationship | hasAssessmentFor | score, vectorString | A CVSS v2.0 result |
CvssV3VulnAssessmentRelationship | hasAssessmentFor | score, severity, vectorString | A CVSS v3.0 or v3.1 result |
CvssV4VulnAssessmentRelationship | hasAssessmentFor | score, severity, vectorString | A CVSS v4.0 result |
EpssVulnAssessmentRelationship | hasAssessmentFor | probability, percentile (both 0 to 1); publishedTime becomes required | An EPSS score on a given day |
ExploitCatalogVulnAssessmentRelationship | hasAssessmentFor | catalogType (kev or other), exploited, locator | Whether the vulnerability is in an exploit catalog |
SsvcVulnAssessmentRelationship | hasAssessmentFor | decisionType (act, attend, track, trackStar) | A CISA SSVC decision |
VexAffectedVulnAssessmentRelationship | affects | actionStatement | VEX affected, with what to do |
VexNotAffectedVulnAssessmentRelationship | doesNotAffect | justificationType or impactStatement (one of them in practice) | VEX not affected, with a reason |
VexFixedVulnAssessmentRelationship | fixedIn | none beyond the parent | VEX fixed |
VexUnderInvestigationVulnAssessmentRelationship | underInvestigationFor | none beyond the parent | VEX under investigation |
The severity vocabulary for CVSS v3 and v4 is critical, high, medium, low and none, with the usual score bands (9.0 to 10.0 for critical, 0.0 for none). The SSVC value trackStar is the spec's spelling of "Track*" in CISA's guide.
VEX in SPDX 3
The four VEX classes share an abstract parent, VexVulnAssessmentRelationship, which adds statusNotes and vexVersion. Three rules on that parent page are worth knowing.
First, the from end must be a Vulnerability and the to end must be the product in the VEX sense. If the vulnerable code is in a subcomponent, you name it with assessedElement rather than pointing the relationship at the subcomponent.
Second, VEX statements inherit from the document. If a statement has no author, tools "must consider the entity listed in the CreationInfo section of the document as the VEX author", and if it has no date, the document's date applies. That is convenient, and it is also a trap: if you merge VEX statements from several suppliers into one document, each one must carry its own author and time, or they will all appear to come from whoever wrote the merged file.
Third, the not affected class makes both impactStatement and justificationType optional in the schema (0..1 each), but the page says that "to produce a valid VEX not_affected statement, one of them MUST be defined", citing the minimum elements for VEX. Schema validation will not catch a not affected statement with neither, so add your own check.
The justification vocabulary has five values: componentNotPresent, vulnerableCodeNotPresent, vulnerableCodeNotInExecutePath, vulnerableCodeCannotBeControlledByAdversary and inlineMitigationsAlreadyExist.
What Security adds beyond a VEX file
A standalone VEX document answers one question per product and vulnerability: affected or not, and why. The Security profile can hold that answer next to the evidence that drove it. In one graph you can record the CVSS vector your team calculated, the EPSS probability on the day you triaged, whether the vulnerability was in KEV at the time, the SSVC decision that set your response time, and the VEX status you published. Each of those is a separate dated relationship with its own suppliedBy, so you can show a regulator or customer how a decision was reached rather than only the final status. You can also keep several assessments of the same vulnerability from different parties, which the spdx.dev design notes call out as a goal.
The cost is that assessments go stale: EPSS changes daily and KEV gains entries. Treat them as dated snapshots and set publishedTime on every one.
Spec examples you should not copy as is
The example blocks on the 3.0.1 class pages are useful for reading but several would not validate. When we read them on 23 October 2026, the CVSS v4 example paired a score of 10.0 with severity medium and had a malformed vector and a trailing comma. The Vulnerability example listed one CVE number in identifier and a different one in its locators. The SSVC example used @type and @id keys where the others use type and spdxId, and several examples used a list for suppliedBy, which has a maximum of one. None of this changes the model; it just means you should start from the JSON schema and the property tables, not from the examples.
Licensing: SimpleLicensing, ExpandedLicensing and what changed from SPDX 2.3
SPDX 3 splits licensing into two profiles. SimpleLicensing "provides classes and properties to express licenses as a license expression string". It also defines the abstract base AnyLicenseInfo and SimpleLicensingText, a place to record license text that does not match anything on the SPDX License List. ExpandedLicensing represents "a fully expanded license expression in object form", with classes for listed and custom licenses, license sets and operators.
SimpleLicensing classes
| Class | Key properties | Use |
|---|---|---|
AnyLicenseInfo | abstract | Target type for license relationships |
LicenseExpression | licenseExpression (required), licenseListVersion (SemVer), customIdToUri | A license expression string such as Apache-2.0 OR MIT |
SimpleLicensingText | licenseText (required) | The text of a license or addition not on the SPDX License List |
The LicenseExpression page says a string "is not valid if it does not conform to the grammar" in the SPDX license expressions annex. customIdToUri maps LicenseRef- style identifiers in the expression to the elements or URIs that define them, which replaces the 2.3 habit of relying on an ExtractedLicensingInfo section with matching IDs.
ExpandedLicensing classes
ExpandedLicensing adds ListedLicense, CustomLicense, ListedLicenseException, CustomLicenseAddition, ConjunctiveLicenseSet (AND), DisjunctiveLicenseSet (OR), OrLaterOperator, WithAdditionOperator, and two individuals, NoAssertionLicense and NoneLicense, that stand in for the old NOASSERTION and NONE values. License objects carry properties such as isOsiApproved, isFsfLibre, standardLicenseHeader, standardLicenseTemplate, licenseXml, seeAlso, obsoletedBy and deprecation flags. You need this profile when a consumer wants to reason about license structure without parsing expression strings, or when you want to ship the full text and template of custom licenses inside the document.
For most SBOMs, SimpleLicensing is enough. Expression strings are compact, every SPDX aware tool already parses them, and you can always expand them later.
SPDX 2.3 license fields and their SPDX 3 equivalents
| SPDX 2.3 | SPDX 3.0.1 | Note |
|---|---|---|
licenseConcluded on a package or file | hasConcludedLicense relationship to an AnyLicenseInfo | Now a relationship, so it can carry its own creation info |
licenseDeclared | hasDeclaredLicense relationship | Same pattern |
licenseInfoFromFiles and licenseInfoInFile | Removed | The migration guide suggests keeping the old values as an annotation on the package |
licenseComments | comment on the relevant element or relationship | Element level comment is reused |
ExtractedLicensingInfo (licenseId, extractedText, name) | CustomLicense with spdxId, licenseText, name; or SimpleLicensingText | Rename reflects that custom licenses have more than text |
LicenseException | ListedLicenseException; custom ones as CustomLicenseAddition | Exceptions are now one kind of "addition" |
WithExceptionOperator (member, licenseException) | WithAdditionOperator (subjectLicense, subjectAddition) | More general naming |
licenseListVersion as 3.21 | SemVer, for example 3.21.0 | Add .0 when migrating |
NONE, NOASSERTION strings | NoneLicense, NoAssertionLicense individuals | Relationships also gained a completeness property |
In 2.3, the concluded and declared fields each accepted a valid expression, NONE or NOASSERTION, and a missing field meant the same as NOASSERTION. The 2.3 spec also asked for a written explanation in the license comments whenever concluded and declared differ. That advice still holds in SPDX 3, and our post on declared versus concluded licenses explains when the two should differ.
SPDX Lite: who it is for and what it requires
The 3.0.1 Lite annex says the profile "is designed to make it quick and easy to start" an SBOM "in situations where a company may have limited capacity for introducing new items into their processes". It "captures the minimum set of information required for license compliance in the software supply chain". The profile page frames it as "a simple view of SPDX data, from the point of view of use cases in some industries".
Lite is older than SPDX 3. It appears as Annex G in SPDX 2.2.2 and 2.3, where it was defined as the mandatory document creation fields plus a set of package fields: name, version, file name, supplier, download location, home page, concluded and declared license, license comments and copyright text. The 2.3 version required FilesAnalyzed to be false and was updated so Lite includes the NTIA minimum elements as required fields. The 2.3 annex says a Lite file can be written by hand "so that tools are not necessarily required". The intended reader is a supplier, often in manufacturing or embedded software, that needs to hand a customer a credible license record without running a full SBOM toolchain.
Lite minimums in SPDX 3.0.1
| Class | Mandatory | Recommended |
|---|---|---|
SpdxDocument | creationInfo, spdxId, at least one element (including an Sbom), at least one rootElement | comment, dataLicense, name, namespaceMap, verifiedUsing |
Sbom | creationInfo, spdxId, at least one element (including a Package), at least one rootElement | sbomType |
Package | name, packageVersion, copyrightText, suppliedBy, creationInfo, spdxId, and downloadLocation or packageUrl | attributionText, builtTime, comment, homepage, originatedBy, releaseTime, supportLevel, validUntilTime, verifiedUsing |
| Relationships | Exactly one hasConcludedLicense and exactly one hasDeclaredLicense per Package | |
Agent | name | |
Hash | algorithm, hashValue |
Two things stand out. Version, supplier and copyright text are optional in the general Software profile but required under Lite. And the license rule is "exactly one" relationship of each kind, which a JSON schema cannot check because it needs to count relationships across the graph. The schema validates shapes; the Lite count needs a graph check, which is what SHACL or a script is for.
Here is a minimal SPDX 3.0.1 Lite document. It validates against the SPDX 3.0.1 JSON schema and passes the spdx3-validate SHACL check. The package and organisation are fictional.
{
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
"@graph": [
{
"type": "CreationInfo",
"@id": "_:creationinfo",
"specVersion": "3.0.1",
"created": "2026-10-20T10:00:00Z",
"createdBy": [
"https://example.com/spdx/agent/acme-devices"
]
},
{
"type": "Organization",
"spdxId": "https://example.com/spdx/agent/acme-devices",
"name": "Acme Devices",
"creationInfo": "_:creationinfo"
},
{
"type": "SpdxDocument",
"spdxId": "https://example.com/spdx/sensor-lib-lite/document",
"creationInfo": "_:creationinfo",
"profileConformance": [
"core",
"software",
"simpleLicensing",
"lite"
],
"element": [
"https://example.com/spdx/sensor-lib-lite/sbom"
],
"rootElement": [
"https://example.com/spdx/sensor-lib-lite/sbom"
]
},
{
"type": "software_Sbom",
"spdxId": "https://example.com/spdx/sensor-lib-lite/sbom",
"creationInfo": "_:creationinfo",
"element": [
"https://example.com/spdx/sensor-lib-lite/pkg/acme-sensor-lib"
],
"rootElement": [
"https://example.com/spdx/sensor-lib-lite/pkg/acme-sensor-lib"
]
},
{
"type": "software_Package",
"spdxId": "https://example.com/spdx/sensor-lib-lite/pkg/acme-sensor-lib",
"creationInfo": "_:creationinfo",
"name": "acme-sensor-lib",
"software_packageVersion": "4.1.0",
"software_downloadLocation": "https://downloads.example.com/acme-sensor-lib-4.1.0.tar.gz",
"software_copyrightText": "Copyright 2024 Acme Devices",
"suppliedBy": "https://example.com/spdx/agent/acme-devices"
},
{
"type": "simplelicensing_LicenseExpression",
"spdxId": "https://example.com/spdx/sensor-lib-lite/license/mit",
"creationInfo": "_:creationinfo",
"simplelicensing_licenseExpression": "MIT"
},
{
"type": "Relationship",
"spdxId": "https://example.com/spdx/sensor-lib-lite/rel/declared",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/sensor-lib-lite/pkg/acme-sensor-lib",
"relationshipType": "hasDeclaredLicense",
"to": [
"https://example.com/spdx/sensor-lib-lite/license/mit"
]
},
{
"type": "Relationship",
"spdxId": "https://example.com/spdx/sensor-lib-lite/rel/concluded",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/sensor-lib-lite/pkg/acme-sensor-lib",
"relationshipType": "hasConcludedLicense",
"to": [
"https://example.com/spdx/sensor-lib-lite/license/mit"
]
}
]
}
Note that profileConformance lists core, software and simpleLicensing as well as lite, because the document uses classes from all four.
The Extension profile: custom data and its risks
The Extension profile defines one abstract class, Extension, and attaches it to any element through a single generic extension property. The class page lists three purposes: profile based extensions, community specific data "not appropriate for standardization across SPDX", and a structured way to express gaps in SPDX "that can be used as submission for revision to SPDX standard".
In 3.0.1 there is one concrete subclass, CdxPropertiesExtension. It holds one or more CdxPropertyEntry objects, each with a required cdxPropName and an optional cdxPropValue. The page says duplicate names are allowed and that the class "is intended to be compatible with the CycloneDX property" structure. That makes it the simplest way to carry name and value data across a CycloneDX to SPDX conversion.
Here is the libparse package object from the worked example below with an extension attached. The full document with this object in place validates against the schema and the SHACL shapes.
{
"type": "software_Package",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/pkg/libparse",
"creationInfo": "_:creationinfo",
"name": "libparse",
"software_packageVersion": "1.2.3",
"software_packageUrl": "pkg:npm/libparse@1.2.3",
"software_primaryPurpose": "library",
"software_copyrightText": "NOASSERTION",
"suppliedBy": "https://example.com/spdx/agent/example-corp",
"extension": [
{
"type": "extension_CdxPropertiesExtension",
"extension_cdxProperty": [
{
"type": "extension_CdxPropertyEntry",
"extension_cdxPropName": "example.com:review:owner",
"extension_cdxPropValue": "team-payments"
},
{
"type": "extension_CdxPropertyEntry",
"extension_cdxPropName": "example.com:review:ticket",
"extension_cdxPropValue": "SEC-4471"
}
]
}
]
}
If name and value pairs are not enough, you define your own subclass of Extension in a namespace you control, with its own properties. That is legal, but you are now outside what the published JSON schema and SHACL shapes describe.
Risks of extensions
- Consumers ignore what they do not understand. A tool that only knows the published model will skip your extension. Never put anything a consumer must act on, such as a license obligation or a not affected justification, only in an extension.
- Validation coverage drops. Standard validators can confirm that an extension is attached in the right place, but not that your custom fields are correct. Ship your own schema or SHACL shapes if others will rely on the data.
- Names collide. Name and value pairs have no namespace by default. Prefix property names with a domain you own, as in
example.com:review:owner. - Sensitive data leaks. Extensions are a common place for internal ticket numbers, hostnames and owner names. Review them before a document leaves your organisation.
- Declaring
extensioninprofileConformanceclaims conformance to the Extension profile, not to your private semantics. Document those separately.
Which profiles to declare for common use cases
core is always present. Add software whenever you describe packages or files. Then add only the profiles whose classes or rules you actually use.
| Use case | profileConformance | Why |
|---|---|---|
| Customer facing SBOM with licenses | core, software, simpleLicensing | Packages plus license expressions |
| Supplier license disclosure, minimum effort | core, software, simpleLicensing, lite | Adds Lite's required fields and the one concluded, one declared rule |
| Open source compliance with full license texts | core, software, simpleLicensing, expandedLicensing | Custom licenses and exceptions as objects |
| SBOM plus VEX in one file | core, software, security | Vulnerability elements and VEX relationships |
| Standalone VEX or vulnerability report | core, security (plus software if products are packages) | Products may be referenced from another document |
| Build record linked to the SBOM | core, software, build | Build element and lifecycle scoped relationships |
| Release evidence pack | core, software, simpleLicensing, build, security | The worked example below |
| Converted from CycloneDX with custom properties | add extension | Only if you keep CdxPropertiesExtension data |
| AI model or dataset inventory | add ai or dataset | Covered in separate posts in this series |
Worked example: Build and Security in one SPDX 3 JSON-LD document
This document describes release 2.4.0 of a fictional service, payments-api, built from a tagged commit by a CI service account. It depends on a fictional library, libparse 1.2.3, which has an illustrative vulnerability with the made up identifier EXAMPLE-2026-0001. The team scored it with CVSS 3.1 and then published a VEX not affected statement because the vulnerable code path is never called. The document validates against the SPDX 3.0.1 JSON schema and passes the spdx3-validate SHACL check.
{
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
"@graph": [
{
"type": "CreationInfo",
"@id": "_:creationinfo",
"specVersion": "3.0.1",
"created": "2026-10-20T09:30:00Z",
"createdBy": [
"https://example.com/spdx/agent/example-corp"
]
},
{
"type": "Organization",
"spdxId": "https://example.com/spdx/agent/example-corp",
"name": "Example Corp",
"creationInfo": "_:creationinfo"
},
{
"type": "Organization",
"spdxId": "https://example.com/spdx/agent/ci-runner",
"name": "Example Corp CI service account",
"creationInfo": "_:creationinfo"
},
{
"type": "SpdxDocument",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/document",
"creationInfo": "_:creationinfo",
"name": "payments-api 2.4.0 build and security record",
"profileConformance": [
"core",
"software",
"simpleLicensing",
"build",
"security"
],
"element": [
"https://example.com/spdx/payments-api-2.4.0/sbom",
"https://example.com/spdx/payments-api-2.4.0/pkg/payments-api",
"https://example.com/spdx/payments-api-2.4.0/pkg/libparse",
"https://example.com/spdx/payments-api-2.4.0/build/1",
"https://example.com/spdx/payments-api-2.4.0/vuln/example-2026-0001"
],
"rootElement": [
"https://example.com/spdx/payments-api-2.4.0/sbom"
]
},
{
"type": "software_Sbom",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/sbom",
"creationInfo": "_:creationinfo",
"software_sbomType": [
"build"
],
"element": [
"https://example.com/spdx/payments-api-2.4.0/pkg/payments-api",
"https://example.com/spdx/payments-api-2.4.0/pkg/libparse"
],
"rootElement": [
"https://example.com/spdx/payments-api-2.4.0/pkg/payments-api"
]
},
{
"type": "software_Package",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/pkg/payments-api",
"creationInfo": "_:creationinfo",
"name": "payments-api",
"software_packageVersion": "2.4.0",
"software_packageUrl": "pkg:npm/%40example/payments-api@2.4.0",
"software_primaryPurpose": "application",
"software_copyrightText": "Copyright 2026 Example Corp",
"suppliedBy": "https://example.com/spdx/agent/example-corp",
"verifiedUsing": [
{
"type": "Hash",
"algorithm": "sha256",
"hashValue": "3f1c2b8e5d7a9c0b4e6f8a1d2c3b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f3"
}
]
},
{
"type": "software_Package",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/pkg/libparse",
"creationInfo": "_:creationinfo",
"name": "libparse",
"software_packageVersion": "1.2.3",
"software_packageUrl": "pkg:npm/libparse@1.2.3",
"software_primaryPurpose": "library",
"suppliedBy": "https://example.com/spdx/agent/example-corp"
},
{
"type": "build_Build",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/build/1",
"creationInfo": "_:creationinfo",
"build_buildType": "https://example.com/buildtypes/ci-workflow/v1",
"build_buildId": "run-88412-attempt-1",
"build_buildStartTime": "2026-10-20T09:21:04Z",
"build_buildEndTime": "2026-10-20T09:28:47Z",
"build_configSourceUri": [
"git+https://git.example.com/payments/payments-api@refs/tags/v2.4.0"
],
"build_configSourceEntrypoint": [
".ci/release.yml"
],
"build_configSourceDigest": [
{
"type": "Hash",
"algorithm": "sha1",
"hashValue": "9d2c4e1f0a7b3c5d6e8f9a0b1c2d3e4f5a6b7c8d"
}
],
"build_parameter": [
{
"type": "DictionaryEntry",
"key": "target",
"value": "production"
}
],
"build_environment": [
{
"type": "DictionaryEntry",
"key": "NODE_ENV",
"value": "production"
}
]
},
{
"type": "LifecycleScopedRelationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/rel/build-input",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/build/1",
"relationshipType": "hasInput",
"to": [
"https://example.com/spdx/payments-api-2.4.0/pkg/libparse"
],
"scope": "build"
},
{
"type": "LifecycleScopedRelationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/rel/build-output",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/build/1",
"relationshipType": "hasOutput",
"to": [
"https://example.com/spdx/payments-api-2.4.0/pkg/payments-api"
],
"scope": "build"
},
{
"type": "LifecycleScopedRelationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/rel/build-invoked-by",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/build/1",
"relationshipType": "invokedBy",
"to": [
"https://example.com/spdx/agent/ci-runner"
],
"scope": "build"
},
{
"type": "Relationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/rel/depends",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/pkg/payments-api",
"relationshipType": "dependsOn",
"to": [
"https://example.com/spdx/payments-api-2.4.0/pkg/libparse"
]
},
{
"type": "simplelicensing_LicenseExpression",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/license/apache-2.0",
"creationInfo": "_:creationinfo",
"simplelicensing_licenseExpression": "Apache-2.0",
"simplelicensing_licenseListVersion": "3.27.0"
},
{
"type": "Relationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/rel/declared-license",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/pkg/payments-api",
"relationshipType": "hasDeclaredLicense",
"to": [
"https://example.com/spdx/payments-api-2.4.0/license/apache-2.0"
]
},
{
"type": "Relationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/rel/concluded-license",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/pkg/payments-api",
"relationshipType": "hasConcludedLicense",
"to": [
"https://example.com/spdx/payments-api-2.4.0/license/apache-2.0"
]
},
{
"type": "security_Vulnerability",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/vuln/example-2026-0001",
"creationInfo": "_:creationinfo",
"name": "EXAMPLE-2026-0001",
"summary": "Illustrative parser flaw in libparse 1.2.3",
"externalIdentifier": [
{
"type": "ExternalIdentifier",
"externalIdentifierType": "securityOther",
"identifier": "EXAMPLE-2026-0001"
}
],
"security_publishedTime": "2026-10-14T00:00:00Z"
},
{
"type": "Relationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/rel/has-vuln",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/pkg/libparse",
"relationshipType": "hasAssociatedVulnerability",
"to": [
"https://example.com/spdx/payments-api-2.4.0/vuln/example-2026-0001"
]
},
{
"type": "security_CvssV3VulnAssessmentRelationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/assessment/cvss",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/vuln/example-2026-0001",
"relationshipType": "hasAssessmentFor",
"to": [
"https://example.com/spdx/payments-api-2.4.0/pkg/payments-api"
],
"security_assessedElement": "https://example.com/spdx/payments-api-2.4.0/pkg/libparse",
"security_score": "9.8",
"security_severity": "critical",
"security_vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"suppliedBy": "https://example.com/spdx/agent/example-corp",
"security_publishedTime": "2026-10-20T09:30:00Z"
},
{
"type": "security_VexNotAffectedVulnAssessmentRelationship",
"spdxId": "https://example.com/spdx/payments-api-2.4.0/assessment/vex",
"creationInfo": "_:creationinfo",
"from": "https://example.com/spdx/payments-api-2.4.0/vuln/example-2026-0001",
"relationshipType": "doesNotAffect",
"to": [
"https://example.com/spdx/payments-api-2.4.0/pkg/payments-api"
],
"security_assessedElement": "https://example.com/spdx/payments-api-2.4.0/pkg/libparse",
"security_justificationType": "vulnerableCodeNotInExecutePath",
"security_impactStatement": "payments-api never calls the affected streaming parser entry point.",
"security_vexVersion": "1",
"security_publishedTime": "2026-10-20T09:30:00Z"
}
]
}
Reading it from top to bottom:
- One
CreationInfoblank node (_:creationinfo) is shared by every element. It setsspecVersionto3.0.1, the creation time and the creating agent. - Two agents. The organisation that authored the document, and the CI service account that ran the build. The second is the target of
invokedBy. - The
SpdxDocumentdeclares five profiles and points at anSbomas its root.profileConformanceincludesbuildandsecuritybecause the graph contains abuild_Buildand security classes. - The
software_SbomhassbomTypebuild, meaning it was produced during the build, and contains the two packages. - Two packages.
payments-apiis the product, with a package URL and a SHA-256 inverifiedUsing.libparseis its dependency. - The
build_Buildhas the requiredbuildType, a namespaced IRI under the author's own domain, plus a run ID, start and end times, the configuration source and its commit digest, one parameter and one environment variable. - Three lifecycle scoped relationships with
"scope": "build":hasInputto the dependency,hasOutputto the product andinvokedByto the CI agent. A real document would also list the source tree and toolchain as inputs. - Licenses. One
LicenseExpressionforApache-2.0, connected byhasDeclaredLicenseandhasConcludedLicense. - The vulnerability, its identifier and published time, and a
hasAssociatedVulnerabilityrelationship fromlibparse. - Two assessments. A CVSS v3.1 assessment with score, severity and vector, and a VEX not affected statement. Both point
fromthe vulnerabilitytothe product, and both useassessedElementto namelibparseas where the flaw sits.
Two things are missing on purpose. There is no signature, because SPDX documents are signed outside the document, and there is no EPSS or KEV data, because the identifier is invented. For a real CVE you would add an EpssVulnAssessmentRelationship with that day's values and an ExploitCatalogVulnAssessmentRelationship with catalogType kev.
Step by step: produce, check and query SPDX 3 profile data
These steps use only open tools and the files above. They assume Python 3.9 or later and jq.
1. Fetch the official schema and install validators in a virtual environment.
python3 -m venv .venv
. .venv/bin/activate
pip install jsonschema spdx3-validate
curl -sSLo spdx-json-schema.json https://spdx.org/schema/3.0.1/spdx-json-schema.json
2. Check the document against the JSON schema.
python - payments-api.spdx3.json spdx-json-schema.json <<'PY'
import json, sys, jsonschema
doc = json.load(open(sys.argv[1]))
schema = json.load(open(sys.argv[2]))
errors = list(jsonschema.Draft202012Validator(schema).iter_errors(doc))
for e in errors:
print(list(e.path), e.message[:200])
print(len(errors), "schema errors")
sys.exit(1 if errors else 0)
PY
When we removed build_buildType from the example, or set an invalid justification, this check failed as expected, so the schema does enforce required properties and vocabularies.
3. Run the SHACL check. spdx3-validate (version 0.0.7 in our test) loads the 3.0.1 model and checks both the schema and the SHACL shapes.
spdx3-validate --spdx-version 3.0.1 --json payments-api.spdx3.json
echo "exit code: $?"
4. Confirm what the document claims.
jq -c '.["@graph"][] | select(.type=="SpdxDocument") | .profileConformance' payments-api.spdx3.json
5. List every VEX statement with its product and justification.
jq -r '.["@graph"][]
| select(.type | startswith("security_Vex"))
| [.type, .relationshipType, (.to | join(",")), (.security_justificationType // "")]
| @tsv' payments-api.spdx3.json
6. Catch not affected statements with neither a justification nor an impact statement, which schema validation lets through.
jq -r '.["@graph"][]
| select(.type=="security_VexNotAffectedVulnAssessmentRelationship")
| select((.security_justificationType == null) and (.security_impactStatement == null))
| .spdxId' payments-api.spdx3.json
An empty result is what you want.
7. Check the Lite license rule: exactly one concluded and one declared license per package.
jq -r '.["@graph"] as $g
| $g[] | select(.type=="software_Package") | .spdxId as $p
| [$p,
([$g[] | select(.relationshipType=="hasConcludedLicense" and .from==$p)] | length),
([$g[] | select(.relationshipType=="hasDeclaredLicense" and .from==$p)] | length)]
| @tsv' sensor-lib-lite.spdx3.json
Every package row should show 1 in both count columns.
8. Gate it in CI. A minimal GitHub Actions job that fails the pull request if the SBOM does not validate:
name: spdx3-check
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install spdx3-validate
- run: spdx3-validate --spdx-version 3.0.1 --json sbom/payments-api.spdx3.json
Pin action versions to commit SHAs in your own repositories rather than tags.
Where Safeguard fits
Safeguard's SBOM generation and analysis is generally available and produces SPDX (JSON, XML and RDF) and CycloneDX (JSON and XML), with more than 30 export formats including CSV, Excel and PDF, continuous scanning on push, schedule or release, and a Deep Analysis option using Griffin. Our offering documents do not state which SPDX 3 profiles Safeguard emits, so ask for a sample export before you plan a pipeline that expects SPDX 3 Build or Security data from it.
Several generally available capabilities map onto the profiles in this post. License compliance parses SPDX license expressions, applies policy and generates NOTICE and attribution files, which is the work SimpleLicensing data feeds. Attestation and signing covers SLSA, Sigstore keyless signing through Fulcio and Rekor, in-toto and X.509, with Kubernetes admission control, which is how you add the integrity that SPDX Build lacks on its own. CSAF and VEX upload lets you bring supplier VEX into the platform, and the third party risk module lets you request SBOMs from vendors and score supplier risk. Policies and gates can then block a release on the findings. For AI assets, AI-BOM discovery is in early access and covers CycloneDX ML-BOM and the SPDX AI profile.
What Safeguard does not do: it does not author vulnerability disclosure reports, generate SWID or CoSWID tags, or convert between formats beyond the export formats listed above.
Common mistakes
- Dropping profile prefixes in JSON-LD.
buildTypeon its own is not a known term; it must bebuild_buildType. Same forsecurity_,simplelicensing_,expandedlicensing_andextension_. - Using a plain
Relationshipfor build inputs and outputs. The Build profile scopes its relationships to thebuildlifecycle. UseLifecycleScopedRelationshipwith"scope": "build". - Inventing a
buildTypeunder someone else's domain. If no well known value fits, namespace it under a domain you own. - Treating SPDX Build as provenance you can trust. Without a signature it is a claim anyone with write access can change. Pair it with signed SLSA provenance or sign the document.
- Pointing VEX at the vulnerable library instead of the product. The
toend is the product. Name the library withassessedElement. - Merging VEX from several suppliers without per statement authors. Missing authors and dates inherit from the document, so every statement looks like yours.
- Shipping a not affected statement with no justification and no impact statement. It passes the schema but is not a valid VEX statement.
- Recording EPSS without a date. The class makes
publishedTimerequired for a reason; an undated EPSS value is meaningless a week later. - Declaring
litewithout meeting the counting rule. Two declared license relationships on one package breaks Lite, and a schema validator will not tell you. - Copying the class page examples. Several contain errors. Start from the schema.
- Putting obligations in an extension. Many consumers will never read it.
FAQ
What is the SPDX 3.0 Build profile?
It is the part of SPDX 3 that describes a software build. It adds a Build element with a required buildType and optional run ID, times, configuration source, parameters and environment, and uses hasInput, hasOutput and invokedBy relationships scoped to the build lifecycle to connect the build to what went in, what came out and who ran it.
Is the SPDX Build profile the same as SLSA provenance?
No. The fields overlap, and the spec points to SLSA for the meaning of buildType, but SLSA provenance is a signed in-toto attestation checked against SLSA levels, while SPDX Build is an unsigned description inside an SBOM graph. Many teams use both and link them.
Does SPDX 3.0 support VEX?
Yes. The Security profile has four VEX relationship classes for affected, not affected, fixed and under investigation, a five value justification vocabulary, and rules for how statements inherit their author and date from the document.
Can SPDX 3 store CVSS and EPSS scores?
Yes. There are assessment classes for CVSS v2, v3 and v4, EPSS (probability and percentile, with a required published time), exploit catalogs such as CISA KEV, and CISA SSVC decisions. Each is a dated relationship from a vulnerability to a product.
What replaced licenseConcluded and licenseDeclared in SPDX 3?
Relationships. A package points to an AnyLicenseInfo element, usually a LicenseExpression, through a hasConcludedLicense or hasDeclaredLicense relationship.
What is the difference between SimpleLicensing and ExpandedLicensing?
SimpleLicensing stores license expressions as strings and unlisted license text as plain text. ExpandedLicensing breaks expressions into objects such as listed licenses, custom licenses, AND and OR sets and WITH operators. Most SBOMs only need SimpleLicensing.
What fields does SPDX Lite require?
In 3.0.1, each package needs a name, version, supplier, copyright text, a download location or package URL, and exactly one concluded and one declared license relationship. The document and its Sbom each need at least one element and one root element, and every Agent needs a name.
How do I add custom fields to an SPDX 3 document?
Use the Extension profile. The built in CdxPropertiesExtension holds name and value pairs compatible with CycloneDX properties. For anything richer, define your own Extension subclass in your own namespace and publish its definition to the people who consume your documents.
Fix it without waiting for upstream
An SPDX document with Build and Security data tells you which build shipped which vulnerable component and whether it matters, but it does not ship the fixed build. Someone still has to move the dependency, rebuild and publish a new SBOM, and that is the part where most teams lose time.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real terminal session with the Safeguard CLI. It reads a vulnerability finding and its advisory, asks before each command, upgrades lodash to 4.17.21 in package.json, runs npm install and npm test with your approval, prints a summary and pushes nothing. It is the step that comes after a VEX affected statement in an SPDX Security graph: the dependency named in assessedElement gets moved, and the next build produces a new SBOM.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with a device code and runs scans from the terminal. That suits a team that wants to scan a repository or check an SBOM on its own hardware before the SPDX document, with its build environment and internal identifiers, goes anywhere else.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. The demo covers the connection and tool choice. After that, Claude can use the tools you allowed to look up projects and findings in your account, which can be a quicker route to "which releases contain this component" than writing jq against a folder of SPDX files.
Click through it at your own pace, or open it full screen.
To try it yourself, download Safeguard or read the CLI documentation.
Quick check
In an SPDX 3 VEX not affected statement, which element goes in the to field: the product or the vulnerable library?
The product. The from end is the Vulnerability, the to end is the product in the VEX sense, and the vulnerable library is named with the assessedElement property.
Why can a JSON schema not fully check SPDX Lite conformance?
Lite requires exactly one hasConcludedLicense and exactly one hasDeclaredLicense relationship per package. Counting relationships across the graph needs SHACL or a script; a JSON schema only checks the shape of each object.
Sources
- SPDX 3.0.1, Build profile
- SPDX 3.0.1, Build class
- SPDX 3.0.1, buildType property
- SPDX 3.0.1, environment property
- SPDX 3.0.1, Security profile
- SPDX 3.0.1, Vulnerability class
- SPDX 3.0.1, VulnAssessmentRelationship
- SPDX 3.0.1, CvssV2VulnAssessmentRelationship
- SPDX 3.0.1, CvssV3VulnAssessmentRelationship
- SPDX 3.0.1, CvssV4VulnAssessmentRelationship
- SPDX 3.0.1, EpssVulnAssessmentRelationship
- SPDX 3.0.1, ExploitCatalogVulnAssessmentRelationship
- SPDX 3.0.1, SsvcVulnAssessmentRelationship
- SPDX 3.0.1, VexVulnAssessmentRelationship
- SPDX 3.0.1, VexAffectedVulnAssessmentRelationship
- SPDX 3.0.1, VexNotAffectedVulnAssessmentRelationship
- SPDX 3.0.1, VexFixedVulnAssessmentRelationship
- SPDX 3.0.1, VexUnderInvestigationVulnAssessmentRelationship
- SPDX 3.0.1, VexJustificationType vocabulary
- SPDX 3.0.1, SsvcDecisionType vocabulary
- SPDX 3.0.1, CvssSeverityType vocabulary
- SPDX 3.0.1, ExploitCatalogType vocabulary
- SPDX 3.0.1, SimpleLicensing profile
- SPDX 3.0.1, LicenseExpression class
- SPDX 3.0.1, SimpleLicensingText class
- SPDX 3.0.1, ExpandedLicensing profile
- SPDX 3.0.1, CustomLicense class
- SPDX 3.0.1, Lite profile
- SPDX 3.0.1, Annex D: SPDX Lite
- SPDX 3.0.1, Extension profile
- SPDX 3.0.1, Extension class
- SPDX 3.0.1, CdxPropertiesExtension class
- SPDX 3.0.1, ProfileIdentifierType vocabulary
- SPDX 3.0.1 JSON schema
- SPDX 3.0.1 JSON-LD context
- SPDX 2.3.1, Package information (license fields)
- SPDX 2.3.1, Annex G: SPDX Lite
- SPDX, Differences from previous editions
- SPDX specifications page
- spdx.dev, Capturing software vulnerability data in SPDX 3.0 (6 November 2023)
- spdx.dev, Linux Foundation announces SPDX 3.0 (9 July 2024)
- SLSA, Provenance v1.0
- spdx3-validate on PyPI
Last reviewed: 23 October 2026.