Safeguard
Security Guides•

How does SPDX record build, dev, test and runtime dependencies?

SPDX has scoped relationships for build, dev, test and runtime dependencies. See when each type fits and why scope changes how you triage findings.

Safeguard Security Team
3 min read

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.

RelationshipSpec example
BUILD_DEPENDENCY_OFA is in the compile scope of B in a Maven project
DEV_DEPENDENCY_OFA is in the devDependencies scope of B
OPTIONAL_DEPENDENCY_OFA 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_OFA is in the provided scope of B in Maven, expected from a container or JDK
TEST_DEPENDENCY_OFA is in the test scope of B in Maven
RUNTIME_DEPENDENCY_OFA 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.

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.