Skip to content

11.2 Dependency Depth and Transitive Risk

When developers add a package to their project, they're often adding far more than they realize. A single npm install or pip install command can pull in dozens or hundreds of additional packages—dependencies of dependencies extending multiple levels deep. Understanding this dependency structure is fundamental to measuring supply chain risk. The packages you directly choose represent only a fraction of your exposure; the transitive dependencies they bring constitute the majority of your attack surface.

This section provides practical techniques for measuring dependency depth, analyzing transitive risk, and communicating these risks to stakeholders.

Direct vs. Transitive Dependencies

Direct dependencies are packages you explicitly include in your project—those listed in your package.json, requirements.txt, or equivalent manifest file. You chose these packages, presumably evaluated them in some way, and integrated them into your application.

Transitive dependencies (also called indirect dependencies) are packages that your direct dependencies require. You didn't choose them; they came along because something you did choose needed them.

Example:

// package.json - Your direct dependencies
{
  "dependencies": {
    "express": "^4.18.2"  // 1 direct dependency
  }
}

Running npm install for this single direct dependency installs:

express@4.18.2
├── accepts@1.3.8
├── array-flatten@1.1.1
├── body-parser@1.20.1
│   ├── bytes@3.1.2
│   ├── content-type@1.0.5
│   ├── debug@2.6.9
│   │   └── ms@2.0.0
│   ├── depd@2.0.0
│   ├── destroy@1.2.0
│   ├── http-errors@2.0.0
│   │   ├── depd@2.0.0
│   │   ├── inherits@2.0.4
│   │   ├── setprototypeof@1.2.0
│   │   ├── statuses@2.0.1
│   │   └── toidentifier@1.0.1
...
└── [30+ additional packages]

One direct dependency becomes approximately 50 total packages across 4-5 depth levels.

The Transitive Majority:

Research consistently shows that transitive dependencies dominate:

  • Commercial codebases average 911 open source components, with 64% being transitive dependencies (~583 transitive components per application) according to the Black Duck OSSRA 2025 report
  • npm projects average 31 direct dependencies that expand to 53 transitive dependencies (4.32× amplification), with dependency trees averaging 4 levels deep but sometimes exceeding 20 levels in popular packages
  • NuGet (.NET) projects typically have 6-10 direct dependencies that expand to 20-70 transitive dependencies, with over 98% of Visual Studio solutions referencing NuGet components
  • PyPI (Python) projects average 4 direct dependencies expanding to only 6 transitive dependencies (1.50× amplification), benefiting from Python's comprehensive standard library
  • Maven (Java) exhibits the highest amplification at 24.70×—projects average 5 direct dependencies but pull in 75 transitive dependencies, with outliers like the AWS SDK exceeding 300 total dependencies

These statistics come from a 2024 empirical study of dependency amplification across 10 package ecosystems.

Risk Implications:

The transitive majority creates several risk dynamics:

  1. Visibility gap: Most organizations don't review transitive dependencies
  2. Control gap: You can't directly control what your dependencies depend on
  3. Update lag: Transitive vulnerabilities require upstream fixes to propagate
  4. Surprise exposure: Compromises in obscure packages affect you unexpectedly

Measuring Dependency Depth

Several tools help analyze dependency structure:

npm (JavaScript/Node.js):

# List all dependencies with tree structure
npm ls --all

# Count total packages
npm ls --all | wc -l

# Find max depth
npm ls --all --depth=Infinity | grep -E "^[│├└ ]+[a-z]" | \
  awk '{print gsub(/[│├└]/, "")}' | sort -rn | head -1

# Generate package-lock.json for exact versions
npm install --package-lock-only

Python (pip/pipenv):

# Install pipdeptree for visualization
pip install pipdeptree

# Show dependency tree
pipdeptree

# Show reverse dependencies (what depends on X)
pipdeptree --reverse --packages requests

# Count all dependencies
pipdeptree --warn silence | wc -l

Maven (Java):

# Show dependency tree
mvn dependency:tree

# Show with conflict resolution
mvn dependency:tree -Dverbose

# Output to file for analysis
mvn dependency:tree -DoutputFile=deps.txt

Gradle (Java/Kotlin):

# Show all dependencies
gradle dependencies

# Specific configuration
gradle dependencies --configuration runtimeClasspath

Go:

# Show module dependencies
go mod graph

# Show why a module is included
go mod why -m github.com/some/package

Specialized Analysis Tools:

Tool Ecosystem Features
npm-remote-ls npm Remote package analysis without install
depcruise JavaScript Visualization, rule validation
dephell Python Multi-format dependency analysis
jdeps Java JDK dependency analyzer
snyk Multi Security + dependency analysis
deps.dev Multi Google's open source dependency data

Risk Concentration

Risk concentration occurs when many packages in your dependency tree depend on the same underlying package. This creates a single point of failure—if that concentrated dependency is compromised, many paths through your dependency graph are affected.

The lodash Example:

lodash is one of the most depended-upon npm packages:1

  • Over 195,000 packages depend on lodash directly1
  • Millions depend on it transitively
  • A typical large JavaScript project may have 10-50 paths through lodash

If you have 20 direct dependencies and 15 of them transitively depend on lodash, a lodash vulnerability could affect 75% of your supply chain.

Measuring Concentration:

# npm: Count how many packages depend on a specific package
npm ls lodash | grep lodash | wc -l

# Python: Show what depends on requests
pipdeptree --reverse --packages requests

Fan-In Analysis:

Fan-in measures how many packages depend on a given package in your tree:

High fan-in = High risk concentration

For your project, identify packages with highest fan-in:

# Conceptual algorithm
for package in dependency_tree:
    fan_in[package] = count_dependents(package)

# Packages with highest fan_in represent concentration risk

Historical Concentration Incidents:

  • left-pad (2016): 11-line package removed from npm on March 22, 2016, broke thousands of builds for 2.5 hours because it was depended upon transitively by many packages
  • event-stream (2018): Popular package with high fan-in was compromised in November 2018, affecting packages throughout npm and targeting Bitcoin wallets
  • colors.js (2022): Sabotage on January 8, 2022 affected thousands of packages that depended on it

Mitigation Strategies:

For high-concentration dependencies:

  1. Monitor closely: Track security advisories for these packages
  2. Consider alternatives: Reduce concentration by using different packages for different purposes
  3. Inline critical functionality: For very small packages, consider copying code rather than adding dependencies
  4. Maintain update readiness: Be prepared to update quickly when vulnerabilities emerge

Dependency Freshness and Maintenance Activity

Dependency freshness refers to how recently dependencies have been updated, both in your project and in upstream packages. Stale dependencies correlate with unpatched vulnerabilities.

Freshness Metrics:

Metric Definition Risk Indication
Time since last release Days since upstream published Older = potentially unmaintained
Version lag Versions behind latest Higher = more unpatched vulns
Time since update Days since you updated Longer = more exposure time
Update frequency Releases per year Lower = less active maintenance

Measuring Freshness:

# npm: Check for outdated packages
npm outdated

# pip: Check for outdated packages  
pip list --outdated

# Detailed version analysis
npm view express time  # Shows publish dates for all versions

npm-check:

npm install -g npm-check
npm-check  # Interactive update interface with freshness info

Maintenance Indicators:

Beyond freshness, assess upstream maintenance health:

  • Issue response time: How quickly are issues addressed?
  • PR merge rate: Are contributions being integrated?
  • Commit frequency: Is there active development?
  • Maintainer count: Single maintainer = higher bus factor risk
  • Security policy: Does the project have a SECURITY.md?

OpenSSF Scorecard automates many of these assessments:

# Install scorecard
go install github.com/ossf/scorecard/v4/cmd/scorecard@latest

# Check a project
scorecard --repo=github.com/expressjs/express

Interpreting Freshness:

Not all stale dependencies are risky:

  • Stable, complete packages: Some packages are genuinely "done" and don't need updates
  • Major version stability: Older major versions may be intentionally maintained for compatibility
  • Low attack surface: Packages with limited functionality have less to go wrong

Context matters when evaluating freshness risk.

Diamond Dependencies and Version Conflicts

Diamond dependencies occur when two or more of your dependencies require different versions of the same package:

    Your Project
      /      \
   Pkg A    Pkg B
      \      /
       Pkg C
   (v1.0)  (v2.0)

Package A needs Pkg C v1.0; Package B needs Pkg C v2.0. This creates conflict.

Conflict Resolution Strategies by Ecosystem:

Ecosystem Strategy
npm Multiple versions installed (node_modules nesting)
pip Single version (last one wins, often breaks)
Maven Nearest definition wins (can be overridden)
Go Minimum version selection

Risks from Version Conflicts:

  1. Security gaps: Older resolved version may have vulnerabilities
  2. Inconsistent behavior: Different parts of application use different versions
  3. Build failures: Incompatible versions cause installation failures
  4. Runtime errors: Version mismatches cause unexpected behavior

Detecting Conflicts:

# npm: Shows deduplication issues
npm dedupe --dry-run

# Maven: Shows version conflicts
mvn dependency:tree -Dverbose=true | grep "omitted for conflict"

# pip: pip-compile shows resolution
pip-compile --verbose requirements.in

Resolution Approaches:

  1. Update to compatible versions: Find versions that satisfy all constraints
  2. Override versions: Force a specific version (accepting compatibility risk)
  3. Replace conflicting packages: Find alternatives without conflicts
  4. Accept duplication: For npm, multiple versions may coexist

Visualizing Dependency Risk

Effective visualization helps communicate dependency risk to stakeholders who won't read terminal output.

Visualization Tools:

deps.dev: - Web-based dependency visualization from Google - Shows relationships across ecosystems - Highlights security issues

Graphviz with custom scripts:

# Generate DOT format
pipdeptree --graph-output dot > deps.dot

# Render to PNG
dot -Tpng deps.dot -o deps.png

Dependency Cruiser (JavaScript):

npx dependency-cruiser --output-type dot src | dot -Tsvg > deps.svg

Commercial Tools: - Snyk: Dependency trees with vulnerability overlay - Sonatype Nexus Lifecycle: Interactive dependency analysis - JFrog Xray: Universal dependency scanning with visualization

Effective Visualizations:

For technical audiences: - Full dependency trees with version information - Highlight paths to vulnerable packages - Show fan-in/concentration

For executives: - Summary statistics (total count, depth, outdated) - Trend over time (is dependency count growing?) - Risk scoring with color coding - Comparison to industry benchmarks

Sample Executive Dashboard Metrics:

Metric Your Project Industry Avg
Total Dependencies 450 683
Max Depth 6 7
Outdated (>1 year) 23 (5%) 15%
Known Vulnerabilities 3 critical -
Single-Maintainer Deps 12 -

Communicating to Stakeholders

Different stakeholders need different information:

For Developers: - Specific vulnerable packages and remediation steps - Breaking changes in update paths - Alternative packages when appropriate

For Engineering Managers: - Technical debt from outdated dependencies - Effort estimates for remediation - Risk-based prioritization

For Executives: - Business impact of dependency risk - Trend direction (improving or degrading) - Comparison to peers/industry - Investment required for improvement

Communication Strategies:

  1. Lead with impact: "This vulnerability could allow data theft" not "CVSS 9.1"
  2. Provide context: "This is more critical than typical because it's in our payment flow"
  3. Show the path: Visualize how attack could propagate through dependencies
  4. Offer options: Present remediation choices with tradeoffs
  5. Track progress: Show improvement over time

Recommendations

For Developers:

  1. Know your tree. Run npm ls --all or equivalent regularly. Understand what you're actually shipping.

  2. Minimize direct dependencies. Every direct dependency brings transitive baggage. Question whether each dependency is necessary.

  3. Watch high-fan-in packages. Monitor security advisories for your most concentrated dependencies.

  4. Keep dependencies fresh. Regular updates prevent vulnerability accumulation. Automate where possible.

  5. Resolve conflicts explicitly. Don't let package managers make version decisions invisibly. Understand and document resolutions.

For Security Practitioners:

  1. Analyze concentration risk. Identify packages that would cause maximum impact if compromised.

  2. Track freshness metrics. Incorporate dependency age into risk scoring.

  3. Monitor maintenance health. Flag dependencies with declining maintenance activity.

  4. Automate analysis. Integrate dependency analysis into CI/CD for continuous visibility.

For Engineering Managers:

  1. Budget for dependency maintenance. Keeping dependencies current is ongoing work that requires allocation.

  2. Set freshness targets. Establish policies for maximum dependency age.

  3. Use visualizations for planning. Help teams understand the scope of dependency management work.

  4. Track metrics over time. Monitor whether dependency hygiene is improving or degrading.

Understanding dependency depth and transitive risk transforms supply chain security from abstract concern to measurable problem. The techniques in this section enable organizations to see their actual exposure, prioritize remediation efforts, and communicate risk effectively. In the next sections, we'll examine how to evaluate the criticality of specific dependencies and score them for risk.


  1. npm registry statistics and Snyk Open Source Advisor, 2024. Dependency counts reflect npm registry data as of late 2024.