When a Node.js project pulls in express, it also pulls in roughly 60 additional packages it never explicitly requested — body-parser, qs, send, cookie, and dozens more, each with their own dependencies. These are transitive dependencies: packages your project doesn't import directly but that arrive anyway because something you did choose to depend on needs them. A 2020 study of npm found the average package pulls in 79 transitive dependencies, and a typical modern web app can easily carry 1,000+ total packages when transitive chains are fully expanded. Security teams that only scan the packages listed in package.json or requirements.txt are auditing a fraction of their real attack surface. The 2021 event-stream incident and the 2024 xz-utils backdoor (CVE-2024-3094) both spread through dependency chains most consuming applications never saw directly — which is exactly why transitive dependency visibility has become a core requirement for SBOM compliance under Executive Order 14028 and NIST SSDF.
What Is a Transitive Dependency?
A transitive dependency is a package your application depends on indirectly, through one of its direct dependencies, rather than one you added yourself. If your package.json lists express as a direct dependency, and express in turn requires path-to-regexp, merge-descriptors, and finalhandler, those three (and everything they in turn require) are transitive dependencies of your project. The distinction matters because most vulnerability disclosures — an estimated 84% according to a 2023 Endor Labs analysis of open source CVEs — land in transitive rather than direct dependencies. A team that reviews and pins its direct dependencies carefully can still ship a vulnerable build if a package four levels deep in the tree gets compromised, because nothing in their normal workflow ever surfaces that package by name.
Why Do Transitive Dependencies Create Security Risk?
Transitive dependencies create risk because they multiply attack surface without multiplying visibility — engineers approve a handful of direct packages but inherit hundreds of code paths they never reviewed. The npm ecosystem averages a dependency tree depth of 5-7 levels for a typical backend service, and each level is maintained by a different person or team with different security practices, release cadences, and account hygiene. A single compromised maintainer account four hops down — as happened with ua-parser-js in October 2021, when attackers published malicious versions that installed a cryptominer and password stealer on any machine that ran npm install — can compromise every application that depends on it, even though none of those applications' developers had ever heard of the package. The 2024 xz-utils backdoor followed the same pattern: a widely-used compression library maintained by one volunteer became a transitive dependency of openssh on many Linux distributions through liblzma, giving the attacker a path toward SSH authentication bypass on affected systems before the backdoor was caught by a Microsoft engineer investigating unrelated performance regressions.
How Deep Can a Dependency Tree Go?
A dependency tree can go 10 or more levels deep in large JavaScript and Java projects, and each additional level roughly compounds the number of packages a team is implicitly trusting. Sonatype's 2023 State of the Software Supply Chain report found that a single popular npm package can have over 1,700 transitive dependents relying on it, meaning a vulnerability in one library can ripple outward to thousands of downstream projects within days of disclosure. In practice, a moderately complex React application with a handful of direct dependencies for routing, state management, and UI components frequently resolves to 800-1,500 total packages once npm or yarn expands the full tree — and audits limited to the top level miss the vast majority of that surface. Java and Maven-based systems see similar patterns: a Spring Boot starter can drag in 40-plus transitive JARs, several of which historically carried CVEs (Log4Shell, CVE-2021-44228, being the starkest example — Log4j was rarely a direct dependency, yet it sat transitively inside thousands of enterprise Java applications through logging frameworks and application servers).
Does a Vulnerable Transitive Dependency Always Put an Application at Risk?
No — a vulnerable transitive dependency only puts an application at real risk if the vulnerable code path is actually reachable from code the application executes. Log4Shell is the textbook example: Log4j 2.x shipped the vulnerable JNDI lookup functionality in log4j-core, but applications that never called the affected logging pattern with attacker-controlled input, or that used only log4j-api without the vulnerable core module, were not exploitable even though a CVE match would flag them. This is why traditional SCA tools that flag every package containing a CVE regardless of usage generate enormous false-positive rates — one 2022 industry estimate put unreachable-vulnerability noise at over 70% of total alerts in typical scan results. Reachability analysis, which traces whether application code actually calls into the vulnerable function or class, is what separates a theoretical risk from an exploitable one, and it's the difference between a security team drowning in tickets and one that can triage in minutes.
How Do Teams Track Transitive Dependencies at Scale?
Teams track transitive dependencies at scale by generating a Software Bill of Materials (SBOM) that enumerates every direct and transitive package, then continuously matching that inventory against vulnerability feeds as new CVEs publish. Formats like CycloneDX and SPDX, both now referenced in CISA's SBOM guidance, are built specifically to represent full dependency graphs rather than just top-level manifests, because a manifest-only view — as established above — misses the 84% of vulnerabilities that live in transitive packages. Manual tracking breaks down fast: a mid-size engineering org with 200 services and an average of 1,000 resolved packages per service is managing roughly 200,000 package instances, a scale at which spreadsheet-based or point-in-time tracking cannot keep pace with daily upstream releases and same-day CVE disclosures. Continuous SBOM generation tied to CI/CD, rather than periodic manual audits, is the only approach that catches a newly disclosed CVE in a transitive package within hours instead of the weeks a quarterly review cycle allows.
How Safeguard Helps
Safeguard generates and ingests SBOMs automatically across your build pipelines, mapping the complete dependency graph — direct and transitive — for every service, rather than relying on top-level manifest scanning. Griffin AI then applies reachability analysis to that graph, tracing whether vulnerable code in a transitive package is actually callable from your application logic, so teams triage the small set of exploitable findings instead of the hundreds of theoretical CVE matches a Log4Shell-style alert flood produces. When a fix is available, Safeguard opens an auto-fix pull request that bumps the affected transitive package (or its nearest resolvable parent) to a patched version, with the reachability evidence attached so reviewers can approve with context instead of guesswork. This turns transitive dependency risk from an invisible, sprawling liability into a scoped, continuously monitored, and actionable queue.