Safeguard
Security Guides•

How do I read SPDX relationships like DEPENDS_ON and CONTAINS?

SPDX relationships connect packages in a graph. Learn what DESCRIBES, CONTAINS, DEPENDS_ON and the reverse forms mean, and how to avoid reading them backwards.

Safeguard Security Team
4 min read

An SPDX relationship is a three part statement: element A, a relationship type, element B. DESCRIBES links the document to the main package, CONTAINS says A holds B inside it, and DEPENDS_ON says A needs B to build and run. Read them left to right, and watch the reverse forms.

The basic shape

In tag-value form, a relationship looks like this:

Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-app
Relationship: SPDXRef-Package-app DEPENDS_ON SPDXRef-Package-lib

In JSON, the same data appears in a relationships list with spdxElementId, relationshipType, and relatedSpdxElement. The first and last fields are the two ends. The middle one is the verb.

The ones you will see most

The SPDX 2.3.1 specification defines the relationship types. These matter most when reading someone else's file:

TypeMeaning from the spec
DESCRIBESThe document describes the package. It is a logical relationship, and mandatory if more than one package or set of files is present
CONTAINSA contains B, for example an archive containing a source file
DEPENDS_ONA depends on the presence of B in order to build and run
DEPENDENCY_OFThe reverse: A is a dependency of B
BUILD_DEPENDENCY_OFA is needed to build B
DEV_DEPENDENCY_OFA is a development dependency of B
TEST_DEPENDENCY_OFA is needed for B's tests
RUNTIME_DEPENDENCY_OFA is a runtime dependency of B

Other types exist for optional and provided dependencies, and for links such as static linking.

The direction trap

Types come in pairs. DEPENDS_ON and DEPENDENCY_OF describe the same fact from opposite ends. If a file says lib DEPENDENCY_OF app, that means app depends on lib. Read it as "lib is a dependency of app."

A quick habit: say the sentence out loud, with the verb in the middle. If it reads naturally, you have the direction right.

How to find the top of the graph

Start at the DESCRIBES relationship. It points to the package the SBOM is about. From there, follow DEPENDS_ON edges to direct dependencies, then repeat. That is your dependency tree.

Be careful with CONTAINS. It describes physical content, such as files inside an archive. It is not the same as a dependency, though for a bundled library the two can overlap.

What the types tell you about risk

Build, dev, and test dependency types are useful for scoping. A test dependency flagged with a vulnerability is rarely in the shipped product. A RUNTIME_DEPENDENCY_OF edge is. But a generator may use the plain DEPENDS_ON for everything, so do not assume the narrow types are present. If every edge is DEPENDS_ON, you cannot separate test from runtime from the file alone.

Gaps to look for

  • Orphans. Packages that appear in the list but in no relationship. That could mean the generator found them but could not place them.
  • Flat graphs. Every package attached directly to the root, with no deeper edges. That suggests transitive data was not captured. The NTIA minimum elements report says the full graph should be stated or the gaps declared as known unknowns.
  • Missing DESCRIBES. Without it, the top of the graph is unclear.

A jq filter to list all dependency edges from an SPDX JSON file:

jq '.relationships[] | select(.relationshipType=="DEPENDS_ON")' sbom.json

Where Safeguard fits

Safeguard provides SBOM and dependency scanning, so you can compare a supplier's graph with one built from your own scan. A free scan is at gold.safeguard.sh/scan.

Quick check

Does CONTAINS mean the same as DEPENDS_ON?

No. CONTAINS is about what is physically inside something. DEPENDS_ON is about what must be present to build and run.

Why do I see a DESCRIBES line at the top?

It marks the package the document is about. It is the entry point for reading the graph.

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.