← Blog

Transitive Dependencies: The Hidden Supply-Chain Risk

Transitive dependencies are the code you never chose but still own. Learn why they are risky, how to find them, and how to fix and monitor them.

Bruno Baldo·Sep 28, 2026·Updated Sep 14, 2026·11 min read·Reviewed by Rainforest Technologies

Transitive dependencies are the indirect packages your software pulls in through its direct dependencies, and they are the quiet majority of every modern codebase. When you add a library to your project, you make a handful of deliberate choices. Those libraries then depend on other libraries, which depend on still more, and within a few levels the tree fans out into hundreds or even thousands of packages you never named, never evaluated, and in many cases have never heard of. This is the hidden supply-chain risk: most of what you ship is code you did not choose, and a flaw anywhere in that tree is a flaw you own. This deep dive is part of our broader software composition analysis guide, and it focuses specifically on the transitive layer, why it is so easy to overlook, and what to do about it.

The distinction is worth stating plainly. A direct dependency is one you deliberately added, the entry you can point to in your manifest. A transitive dependency, sometimes called an indirect or nested dependency, is one that got installed because something you added needed it. You picked a web framework; it picked a template engine; the template engine picked a string-parsing utility; and that utility picked something else again. Every one of those packages executes inside your process, with the same access to memory, environment, and secrets that your own code has. The runtime does not distinguish between the code you wrote, the code you chose, and the code that simply came along for the ride.

Why most of your tree is transitive

It surprises many teams the first time they actually count. Directionally, direct dependencies are a small fraction of the total; the overwhelming majority of the packages resolved into a typical application are transitive. A project with a couple of dozen direct dependencies can easily resolve into many hundreds or a few thousand total packages once the tree is fully expanded. This is not a sign of a bloated project; it is simply how reuse compounds. Each maintainer, reasonably, builds on the work of others, and that leverage is exactly why open source is so productive.

The consequence for security is that your attack surface is set far more by what you inherit than by what you install. You can be scrupulous about the handful of libraries you add, read their code, check their maintainers, and still end up running thousands of lines you have never seen. The hidden risk is not that transitive dependencies are inherently worse than direct ones. It is that they are invisible by default. They do not appear in your manifest, they rarely appear in code review, and without tooling they never appear in anyone's mental model of the application at all.

The vulnerability is still yours

Here is the uncomfortable part. When a CVE is published against a package five levels down in your tree, it is every bit as much your problem as a flaw in code you wrote yourself. If that package parses untrusted input, deserializes data, or handles a network request on a path your application exercises, an attacker can reach it. The exploit does not check who added the dependency.

What makes transitive vulnerabilities uniquely awkward is that you have no direct relationship with the affected package. You cannot simply bump its version in your manifest, because it is not in your manifest. The version you are running was chosen by one of your direct dependencies, or by one of theirs. You are, in effect, waiting on a chain of maintainers you have never met to each update in turn. This is why software supply chain failures now sit among the most pressing categories in application security, reflected in the OWASP Top 10 2025 under software supply chain failures (A03). The risk is not hypothetical or rare; it is structural, and it grows with every package you inherit.

How package managers resolve the tree

To manage transitive risk you have to understand, at least roughly, how it gets built. Every ecosystem has a resolver whose job is to take your declared dependencies, read what each of them requires, and produce a concrete, installable set of packages at specific versions. The details differ, but the shape is consistent across the major toolchains: the JavaScript world with npm, yarn, and pnpm; the JVM with Maven and Gradle; Python with pip and poetry; Go with its module system; and their equivalents elsewhere.

The resolver's decisions matter enormously for security. When two packages in your tree ask for overlapping but different versions of the same dependency, the resolver has to pick, and its choice determines whether you end up running a patched version or a vulnerable one. Some ecosystems flatten the tree and try to satisfy everyone with a single shared version; others allow multiple versions of the same package to coexist deeper in the tree. Either way, the version that actually lands is often not the version any single package explicitly requested. It is a negotiated outcome, and it can change the next time you install unless you have pinned it down.

Why lockfiles matter

That is exactly what a lockfile does. A manifest declares what you want, often as a range: any compatible version at or above some baseline. A lockfile records what you actually got: the exact version of every package in the fully resolved tree, direct and transitive, usually alongside an integrity hash for each one. The manifest is the intention; the lockfile is the reality.

Without a lockfile, two installs of the same manifest on different days can produce different trees, because new versions of transitive packages are published constantly and floating ranges silently pull them in. That is bad for reproducibility and worse for security, because it means the code you tested is not necessarily the code you shipped. With a lockfile committed to version control, everyone, including your CI pipeline and your production build, resolves the same tree every time. The integrity hashes add a second guarantee: the bytes you install are the exact bytes that were recorded, so a package cannot be swapped out from under you even if a registry entry is tampered with. Lockfiles are the single most important habit for controlling transitive risk, and they cost nothing but the discipline to commit them.

Fixing a vulnerability in a transitive dependency

When a scanner flags a vulnerable transitive package, you have a ladder of options, and it pays to try them in order.

The cleanest fix is to upgrade the direct parent. Find which of your direct dependencies pulls in the vulnerable package, and see whether a newer version of that parent depends on a patched version. Most of the time this is the right answer: you update one line in your manifest, the resolver does the rest, and the vulnerable version disappears from your tree. It keeps you on supported, tested combinations of packages.

When the parent has not caught up yet, the next lever is a forced version. Every major ecosystem offers a way to say "wherever this package appears in my tree, use at least this version," whether it is called an override, a resolution, dependency management, or a replace directive. This surgically bumps the transitive package without waiting for the parent to update. It is powerful, so use it deliberately: a forced version can in principle break a parent that expected the older API, so pair it with tests, and treat it as a temporary bridge you remove once the parent ships its own fix.

If neither works, you escalate. You might replace the offending direct dependency with a better-maintained alternative that does not carry the problem, which is more work but often the healthier long-term move. As a genuine last resort, you can fork and patch the dependency yourself, accepting that you now own maintenance of that fork until upstream recovers. Forking is a real option when nothing else is available, but its cost is ongoing, so reserve it for cases where the risk truly justifies it.

The attacks that ride the transitive layer

Transitive dependencies are not only a passive source of inherited bugs; they are an active target, because the deeper a package sits, the less scrutiny it receives. Several supply-chain attack patterns exploit exactly this.

Dependency confusion tricks a resolver into pulling a malicious public package in place of a private internal one, by publishing a package with the same name and a higher version number to a public registry. Because resolvers can prefer the higher version, the attacker's code slips in as if it were yours. Typosquatting relies on a mistyped or look-alike name, so a single character off in a manifest, or in one of your dependencies' manifests, silently substitutes hostile code. Malicious and hijacked packages take over legitimate, widely used projects, either by a maintainer turning bad or by an attacker compromising a maintainer's account, and then ship a poisoned update that flows downstream into everyone's tree as a routine transitive bump. Protestware is the case where a maintainer deliberately sabotages or degrades their own package to make a point, and users inherit the damage.

What ties these together is that they arrive through the same trusted update channel you rely on for legitimate fixes, and they most often land in the transitive layer where no one is looking. Pinning versions and enforcing integrity hashes is your strongest defense: if you install exact, recorded versions with verified hashes, a surprise malicious release cannot enter your build silently, and you get a reviewable moment before any new code comes in. Floating ranges, by contrast, are an open invitation for the next published version, whatever it contains, to become part of your application automatically. For a fuller treatment of governing the whole open source estate, see our guide on open source vulnerability management.

Seeing the whole graph with SCA and an SBOM

You cannot manage what you cannot see, and the defining feature of transitive risk is that it is invisible without tooling. This is the job of software composition analysis. A capable SCA tool resolves your dependency tree the way your package manager does, walks it to full depth, and maps every package, direct and transitive, to known vulnerabilities, license obligations, and maintenance signals. Crucially, it shows you the path: not just that a vulnerable package is present, but which of your direct dependencies introduced it, which is precisely the information you need to choose a fix.

That complete inventory is also what a software bill of materials captures. An SBOM is a formal, machine-readable list of everything in your application, transitives included, and it is fast becoming a baseline expectation from customers, auditors, and regulators. When a major vulnerability breaks, the difference between the teams who answer "are we affected?" in minutes and the teams who spend days grepping through builds is almost always whether they had an accurate SBOM and the graph behind it.

Why continuous monitoring is non-negotiable

A dependency scan is a snapshot, and the tree does not hold still. New vulnerabilities are disclosed every day against packages you already ship and have not touched. Code that was clean when you released it can become vulnerable overnight without a single line changing on your side, simply because a researcher published a finding against something deep in your tree.

Continuous monitoring is what turns that from a crisis into a routine. By keeping a live view of your resolved dependency graph and matching it against advisories as they appear, you learn about a newly disclosed transitive vulnerability when it is disclosed, not when it is exploited. This is the natural extension of building security into the whole lifecycle, the same philosophy we describe in secure software development: shift the discovery of dependency risk left into development and CI, and keep watching in production so nothing that emerges later goes unnoticed.

How Rainforest helps

Rainforest provides software composition analysis that treats the transitive layer as a first-class concern rather than an afterthought. It resolves your full dependency graph, direct and indirect, ties every flagged package back to the direct parent that introduced it so remediation is obvious, and generates an SBOM you can share with confidence. Because it runs in CI and monitors your resolved tree continuously, newly disclosed vulnerabilities surface even in code you have not changed, and issues are caught on pull requests before they reach production. And because it is part of one platform rather than a standalone silo, your open source risk sits alongside the rest of your application security picture instead of in a separate report nobody reads. You can explore the details on our software composition analysis page.

The hidden risk of transitive dependencies is not that they are dangerous by nature; it is that they are easy to ignore right up until one of them is on the front page. Pin your versions, commit your lockfiles, map the whole graph, and watch it continuously, and the tree you never chose becomes something you can actually see and defend. If you want to see your own dependency graph, transitives and all, mapped and monitored, book a demo.

Frequently asked questions

What is a transitive dependency?

A transitive dependency, also called an indirect or nested dependency, is a package that gets installed not because you added it, but because one of your direct dependencies needs it. When you add a library, it brings its own dependencies, which bring theirs, and so on. The result is a tree that fans out several levels deep, and the packages below the top level are all transitive. They run with the same privileges as your own code even though you never chose them.

Why are transitive dependencies a security risk?

Because most of the code in a typical application arrives transitively, most of your risk lives in code you never selected or reviewed. A vulnerability deep in the tree is still yours, and an attacker can reach it if your application exercises the affected path. The difficulty is that you have no direct relationship with the package, so you cannot simply update it in your manifest, and without tooling these dependencies are invisible in code review and in your mental model of the app.

How do I fix a vulnerability in a transitive dependency?

Start by upgrading the direct parent that pulled in the vulnerable package, since a newer parent often depends on a patched version and the resolver does the rest. If the parent has not updated yet, force a safe version using your ecosystem's override, resolution, dependency-management, or replace mechanism, backed by tests. If that is not viable, replace the direct dependency with a better-maintained alternative, and as a last resort fork and patch the package yourself, accepting the ongoing maintenance cost.

What is dependency confusion?

Dependency confusion is a supply-chain attack that tricks a package resolver into installing a malicious public package instead of a legitimate private, internal one. The attacker publishes a package to a public registry using the same name as your internal package but with a higher version number. Because resolvers can prefer the higher version, the hostile package is pulled into your build as if it were the real one. Scoping internal packages and controlling registry resolution are key defenses.

Why do lockfiles matter for security?

A manifest declares the versions you want, often as a range, while a lockfile records the exact versions you actually resolved across the entire tree, direct and transitive, usually with an integrity hash for each. Without a lockfile, floating ranges can pull in different, newer transitive packages on each install, so the code you tested may not be the code you shipped. A committed lockfile makes builds reproducible, and the hashes ensure the bytes you install match what was recorded, blocking silent substitution.

Bruno Baldo

Written by

Bruno Baldo

CMO

Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Keep reading