SPDX 2.3 records dependency scope with dedicated relationship types: BUILD_DEPENDENCY_OF, DEV_DEPENDENCY_OF, TEST_DEPENDENCY_OF, RUNTIME_DEPENDENCY_OF, OPTIONAL_DEPENDENCY_OF and PROVIDED_DEPENDENCY_OF. Using them lets a reader separate what ships from what only exists during development, which changes how findings are triaged.
The scoped types in the spec
SPDX 2.3.1 defines each type with a package manager example. The left element is the dependency, and the right element is the package that has it.
| Relationship | Spec example |
|---|---|
| BUILD_DEPENDENCY_OF | A is in the compile scope of B in a Maven project |
| DEV_DEPENDENCY_OF | A is in the devDependencies scope of B |
| OPTIONAL_DEPENDENCY_OF | A is in the optionalDependencies scope of an npm project B; the build proceeds even if A cannot be found or only installs on some platforms |
| PROVIDED_DEPENDENCY_OF | A is in the provided scope of B in Maven, expected from a container or JDK |
| TEST_DEPENDENCY_OF | A is in the test scope of B in Maven |
| RUNTIME_DEPENDENCY_OF | A is needed for the execution of B, in the runtime scope in Maven |
The generic DEPENDENCY_OF type exists for cases where a package manager does not define scopes.
Why scope changes the answer
Consider a vulnerability in a test framework. If your SBOM only says depends on, it looks identical to a vulnerability in a package that handles customer traffic. With TEST_DEPENDENCY_OF, a triager can see the package never reaches production.
The same applies to licensing. A development tool with a restrictive license has different obligations from a library linked into a shipped binary, though a lawyer should make that call.
Scope is not a reachability result, though. A runtime dependency may still never be called. Scope narrows the list. Code analysis narrows it further.
Mapping from your manifest
Most generators read scope from the manifest. If yours emits only DEPENDS_ON for everything, check whether the tool supports scoped output before assuming the information is lost. If it does not, a post-processing step can map manifest sections to relationship types.
Relationship: SPDXRef-junit TEST_DEPENDENCY_OF SPDXRef-app
Relationship: SPDXRef-jackson RUNTIME_DEPENDENCY_OF SPDXRef-app
A common trap
Treating dev dependencies as irrelevant. They run on developer machines and in CI, where credentials live. Scope tells you where a package runs, not that it is safe, so keep scanning all scopes and prioritise by where they run.
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
What if my package manager has no scopes?
Use the generic DEPENDENCY_OF or DEPENDS_ON types, which the spec offers for cases without scope information.
Does TEST_DEPENDENCY_OF mean I can ignore the finding?
Not automatically. It tells you the package is not in the shipped runtime, but it may still run in CI or on developer machines.
Sources
Last reviewed: 19 October 2026.