Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/pages/explanations/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default {
'personal-access-token': { title: 'Personal Access Tokens' },
'sbom-problem-statement': { title: 'SBOM Problem Statement' },
'explaining-sboms': { title: 'Explaining SBOMs' },
'transitive-dependencies': { title: 'Transitive Dependencies' },
security: { title: 'Security' },
integrations: { title: 'Integrations' },
'advanced-topics': { title: 'Advanced Topics' },
Expand Down
138 changes: 138 additions & 0 deletions src/pages/explanations/transitive-dependencies.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: Transitive Dependency Risks and Vulnerabilities Explained
description: "What is a transitive dependency? Learn how a transitive dependency introduces hidden vulnerabilities and how DevGuard surfaces them per node."
seo:
robots: index,follow
og:
image: /og-image.png
type: article
schema:
type: TechArticle
keyword_primary: transitive dependency
lang: en-US
ignoreChecks: null
---

import { Callout } from '@document-writing-tools/kernux-theme'

# What Is a Transitive Dependency — and Why It Creates Hidden Vulnerabilities

Modern software rarely builds on just the packages you explicitly install. Behind every direct dependency lies a tree of further packages — and vulnerabilities anywhere in that tree can put your application at risk. Understanding transitive dependencies is the first step to controlling that risk.

---

## What is a transitive dependency?

When your application declares a dependency on a package, that package may itself depend on other packages. Those indirect packages are called **transitive dependencies** (also known as indirect dependencies).

**Example:**

```
Your App
└── openssl ← direct dependency
└── libcrypto ← transitive dependency (depth 2)
└── zlib ← transitive dependency (depth 3)
```

You declared a dependency on `openssl`. You did not explicitly choose `libcrypto` or `zlib` — but they are pulled into your build and run as part of your application.

The deeper the dependency tree, the less visibility teams typically have into what is actually running in their software.

---

## What is a transitive vulnerability?

A **transitive vulnerability** is a security flaw in a transitive dependency that can affect your application, even though the vulnerable package is not one you directly depend on.

**Concrete example:**

Your application uses `openssl`. `openssl` depends on `libcrypto`. A CVE is published against `libcrypto`. Even though you never listed `libcrypto` in your dependency file, your application is affected — because `openssl` ships `libcrypto` as part of its own dependency chain.

<Callout type="warning">
Transitive vulnerabilities are often overlooked in manual reviews because they do not appear in top-level dependency files like `package.json`, `go.mod`, or `requirements.txt`. A full software composition analysis (SCA) scan is required to surface them.
</Callout>

---

## Why transitive dependencies are a significant risk

### They are invisible by default

Most developers only see the packages they directly install. The full dependency tree — which can contain hundreds or thousands of packages in a typical application — is hidden unless you actively inspect it.

### They are harder to remediate

Fixing a direct dependency is straightforward: update the version in your manifest. Fixing a transitive vulnerability often requires the maintainer of your direct dependency to release an update first. Until then, you must find workarounds or accept the risk.

### They account for the majority of vulnerable packages

In practice, the vast majority of vulnerable packages found in a typical application are transitive — not direct — dependencies. Ignoring them leaves most of your attack surface unaddressed.

### Triage is harder for transitive vulnerabilities

When a vulnerability appears in one of your direct dependencies, you can read its code and understand exactly how your application calls it. With a transitive dependency, that visibility is gone: you only know that *some* intermediate package depends on it — you do not know which functions it calls, under what conditions, or whether the vulnerable code path is ever exercised at all.

This makes it genuinely difficult to answer the question every security team needs to answer: *Is my application actually affected?*

Research on npm packages confirms this. A [call-graph analysis study on transitive vulnerabilities](https://devguard.org/research-development/vexing-npm-Callgraph-Analyse%20f%C3%BCr%20die%20Reduzierung%20von%20False-positives-bei-der-schwachstellenidentifikation-in-javascript.pdf) found that a vulnerability propagates across a package boundary with roughly **26.3 % probability**. Across two hops the probability drops to about **6.9 %** — following a conditional probability model where each additional hop multiplies the chance of actual exposure:

```
P(A affected | C vulnerable) = P(A | B) · P(B | C) = 0.263 · 0.263 ≈ 0.069
```

In absolute numbers the study observed 2,360 directly affected packages — after one inheritance step 1,487 remained (63 %), after two steps only 512. The pattern is consistent exponential decay.

The practical implication: deeper transitive vulnerabilities are statistically *less likely* to affect your application, but they are also the hardest to verify manually. Tooling that surfaces the full dependency path — and lets you reason per node — is therefore more valuable the deeper the chain goes.

---

## Depth in the dependency graph

**Depth** describes how many hops a dependency is away from your application:

| Depth | Description | Example |
|-------|-------------|---------|
| 1 | Direct dependency — you declared it | `openssl` in your `go.mod` |
| 2 | Your direct dependency depends on it | `libcrypto` pulled in by `openssl` |
| 3+ | Further indirect dependencies | `zlib` pulled in by `libcrypto` |

Greater depth generally correlates with lower probability of actual impact — but does not eliminate risk. A critical vulnerability in a depth-5 package is just as exploitable as one in a direct dependency if your application exercises the code path that reaches it. Depth is a useful signal for prioritisation, not a reason to ignore a finding.

---

## How DevGuard handles transitive dependencies

When you connect a repository to DevGuard and run a scan, DevGuard performs a full **Software Composition Analysis (SCA)** that resolves the complete dependency graph — not just your top-level packages.

### The Dependency Risks table

After a scan, the **Dependency Risks** table in DevGuard shows every vulnerability found across your entire dependency tree. For each finding, DevGuard displays:

- **The vulnerable package** — the exact component that contains the CVE
- **The dependency path** — the full chain from your application down to the vulnerable package, node by node. DevGuard supports [path pattern matching](/explanations/vulnerability-management/mitigation-strategies#path-pattern-matching) so you can write VEX rules that target specific chains rather than blanket-dismissing a CVE.
- **The depth** — how many hops the vulnerable package is away from your code
- **Severity and exploitability data** — aggregated from NVD, EPSS, OSV, and other sources

This means you can see not only *that* a vulnerability exists, but *how* your application reaches it — which packages form the chain, and at which node the vulnerable code lives. For a deeper look at how DevGuard resolves these chains, see [Transitive Vulnerability Path Analysis](/explanations/supply-chain-security/transitive-vulnerability-path-analysis).

### Per-node remediation

DevGuard lets you act on each node in the dependency path individually. For every transitive vulnerability, you can:

- **Accept the risk** on a specific node — record that you have assessed the vulnerability in its context and consider it acceptable, with a reason and expiry date
- **Mark as false positive** — if the vulnerable code path is not reachable from your application. See [False Positive Detection](/explanations/vulnerability-management/false-positive-detection) for how DevGuard helps identify these cases.
- **Track remediation** — open an issue directly from the finding and follow the fix through to completion

This granularity matters because the right action often depends on *where* in the chain the vulnerability lives. A depth-3 vulnerability in a package used only in test code requires a different response than the same CVE in a package invoked on every request.

<Callout type="info">
DevGuard generates the dependency graph from the SBOM produced during the scan. The SBOM captures the full component inventory including transitive packages, their versions, and their relationships — giving DevGuard the data it needs to build the complete path from your application to each vulnerability.
</Callout>

---

## Related

- [Explaining SBOMs](/explanations/explaining-sboms) — what an SBOM is and why it is the foundation for transitive dependency analysis
- [Vulnerability Management](/explanations/vulnerability-management) — how to prioritize and remediate vulnerabilities found in your dependency tree
- [Run your first scan](/getting-started) — connect your repository and surface transitive vulnerabilities in minutes
Loading