SBOM Completeness with Direct & Transitive Dependencies
While modern SBOMs are all about making dependencies and tooling “visible”, many of them are still blind: they list only the libraries you specified in your pom.xml
and skip the dense forest of dependency trees that actually lands in production. That blind spot is where malware, outdated libs, and licensing issues like to hide. In this blog post, we will discuss why capturing both direct and transitive dependencies matters, why Maven projects are especially tricky, and how you can build a complete CycloneDX SBOM (with a visual graph!) out of SafeDep Vet in one command. Along the way we compare mainstream generators, map the regulatory pressure from laws like EU CRA, and come up with a plan-of-action for devs/CISOs for closing the gaps.
If you are interested in learning more about EU CRA, please refer to our blog SBOM and the EU Cyber Resilience Act (CRA) – What Software Vendors Need to Know.
Background
Since its earliest manuals, Maven has promised “automatic updating” and “dependency closures” (also known as transitive dependencies), meaning that a entry in pom.xml
silently expands into every library required by that depencency and its descendants. The official docs explains that there is “no limit to the number of levels that dependencies can be gathered from,” with resolution performed recursively at build time and the generated Project Dependencies pages regularly show dozens of jars that never appear in the root POM. However, unlike build systems and package managers like pnpm
, cargo
, pdm
etc., Maven and the Java ecosystem as a whole lacks a solid foundation for lockfiles.
While this abstraction speeds development, it also conceals risk: unwanted or outdated libraries can ride in unless a maintainer explicitly excludes them (eg, optional dependencies). Industry analysis of a million of Java packages found that almost one-third of exploitable vulnerabilities live only in transitive layers, invisible to tools that stop at first-level imports. The Log4Shell crisis illustrated the danger when organisations were compromised by an indirect copy of log4j-core
they never declared themselves, and attackers continue to probe that vector years later.
Problem: SBOMs That Stop at the First Branch
Maven hides the full dependency tree
Maven resolves transitive dependencies automatically; there is “no limit to the number of levels that dependencies can be gathered from”. The popular dependency:tree
goal shows that expansion in text, DOT or JSON formats, often revealing 10–50x more artefacts than appear in the root POM. Research on 3 million Maven packages confirms that almost one-third of projects are only vulnerable if those deeper layers are considered.
Risk concatanates in transitive layers
The Log4Shell exploit entered many Java applications that never declared Log4j directly; it arrived through indirect dependencies. Studies of real-world incidents show that updating a direct library often fails to patch flaws buried further down the graph, and tooling that stops at the first level cannot surface those blind spots.
Many SBOM generators still abbreviate
Issues filed against Syft report that versions prior to 1.30 listed only first-level packages for Java, omitting the dependency relationships required by auditors. However, NTIA’s minimum-elements document treats “X is included in Y” as the single mandatory relationship every SBOM must capture.
Regulatory momentum demands completeness
The EU Cyber Resilience Act will require vendors shipping to Europe to provide machine-readable SBOMs that cover complete dependency chains by December 2027, with fines up to €15 million for non-compliance. In the United States, Executive Order 14028 pushes federal agencies to buy only software that comes with an SBOM aligned to NIST guidance. Partial inventories will therefore fail procurement checks.
Operational impact for developers and CISOs
A flat SBOM hides which direct upgrade would excise a vulnerable transitive package, prolonging incident response times. In other words, a flat SBOM is ineffective in capturing the transitive dependencies. OpenSSF notes, it also obscures licence inheritance, exposing organisations to unexpected LGPL or GPL obligations.
Solution — Capture the Whole Graph
However, there are some ways to get around this situation, without having to switch to Gradle.
Extract the authoritative graph from Maven
Since maven already computes the full resolution, make use of it. The mvn dependency:tree -DoutputType=dot
command produces a canonical graph that downstream tools can ingest. For automation, the CycloneDX Maven plugin emits a CycloneDX v1.6 SBOM that aggregates all direct and transitive dependencies in a single step.
Prefer graph-aware SCA tools
SafeDep Vet resolves the Maven tree, and writes a CycloneDX SBOM plus an optional DOT graph in one CLI invocation, eliminating manual glue code. By contrast, Syft requires environment flags for Java and still lacks complete graph output in certain modes, as its own issue tracker notes.
Embed verification in CI/CD
Add a pipeline step that:
- Generates
bom.json
(CycloneDX) anddeps.dot
(graph) at build time. - Validates the SBOM.
- Fails the build if any critical CVE is detected anywhere in the graph.
This satisfies CRA’s upcoming requirement for continuous vulnerability monitoring across the full supply chain.
And voila! Because the SBOM now contains edge data, remediation tools can pinpoint the closest direct dependency upgrade that eliminates a vulnerable indirect library, shortening mean-time-to-patch and reducing the risk of breaking upstream APIs.
Tools Landscape Overview
The landscape analysis spans Maven-native plugins that output CycloneDX files with full transitive coverage, open-source scanners such as Syft that are still refining their Java dependency-graph support, SPDX-focused licence tools, and SafeDep’s Vet, which combines graph-complete SBOMs with malware and policy checks. The table below compares their default abilities, export formats, and known quirks:
Use case | Directs only | Transitives | Graph export | Notable quirks |
---|---|---|---|---|
Syft | ✅ | ⚠️ (Java needs SYFT_JAVA_RESOLVE_TRANSITIVE_DEPENDENCIES ) | ❌ (flat list) | Includes test scope by default |
CycloneDX Maven Plugin | ✅ | ✅ | via dependencyGraph | Runs in-build, no multi-ecosystem |
SPDX-tools | ✅ | ❌ | N/A | Focus on license data |
SafeDep Vet | ✅ | ✅ | --report-graph | Multi-lang with policy engine |
OSV Scanner | ❌ | ✅ | ❌ | Transitive Maven scanning by default; —no-resolve disables transitives; no native graph export |