Insights Business| SaaS| Technology Understanding Persistent Risk in Dependency Management and Why 95 Percent of Vulnerable Downloads Had Fixes Available
Business
|
SaaS
|
Technology
Feb 15, 2026

Understanding Persistent Risk in Dependency Management and Why 95 Percent of Vulnerable Downloads Had Fixes Available

AUTHOR

James A. Wondrasek James A. Wondrasek
Graphic representation of the topic Software Supply Chain Security: After SolarWinds and XZ Utils

Here’s something that should worry you: 95% of vulnerable component downloads already had fixes available when developers downloaded them. That’s right. Developers weren’t downloading vulnerable packages because patches didn’t exist. They were downloading them despite patches existing.

The 95% statistic isn’t about patch availability. It’s about process failure.

After SolarWinds and XZ Utils, your dependencies are the primary attack surface. As we explore in our comprehensive security approach, the question isn’t whether you’ve got vulnerable dependencies. It’s how long you’re leaving them unfixed and why.

This is where persistent risk comes in. It’s a framework from Sonatype combining unfixed risk (those unpatched vulnerabilities) with corrosive risk (the time-to-discover and time-to-remediate vulnerabilities hiding in old versions). Together, they tell you exactly how bad your dependency management process is.

In this article we’re going to walk through what persistent risk means, why that 95% statistic matters, how Log4Shell is still haunting 13% of downloads three years later, and how active dependency management with tools like Dependabot and Renovate reduces your risk by 7x.

What Is Persistent Risk in Dependency Management?

Persistent risk is unfixed risk plus corrosive risk.

Unfixed risk refers to vulnerabilities within software components that have been identified but have yet to be addressed and in many cases will never be addressed. It’s straightforward. It’s known vulnerabilities sitting in your dependencies without patches applied. Every CVE scanner measures this. It’s those red alerts you’re ignoring.

Corrosive risk is the sneaky one. Corrosive risk impacts current and historical releases by incorporating the time needed to resolve vulnerabilities plus the delay in discovering vulnerabilities in old versions. It’s the time-to-discover vulnerabilities in old versions plus the time-to-remediate when you finally get around to updating. The older your dependencies get, the more likely they contain undiscovered vulnerabilities. And the harder they are to update because of breaking changes piling up.

Like metal corroding over time, old dependencies accumulate hidden weaknesses.

Traditional risk models only track unfixed risk. They tell you which CVEs exist right now. But they don’t tell you how exposed you are to future CVEs based on how old your dependencies are. They don’t quantify how hard it’ll be to fix those future vulnerabilities when version gaps are measured in years.

That’s why persistent risk matters. It captures both your current exposure (unfixed risk) and your future exposure (corrosive risk).

Unfixed risk gets measured by vulnerable components, severity scores, and exposure time. Every extra day a known CVE sits in production increases your risk.

Corrosive risk gets measured by dependency age. This is where the libyears metric comes in. Older dependencies correlate with more vulnerabilities because security researchers keep finding new CVEs in old code. And older dependencies are harder to update because version gaps keep getting wider.

The research is clear: persistent risk is driven by consumption practices, not open source quality. The best projects find and fix vulnerabilities quickly. Most downloads just aren’t of the fixed version.

That’s on you.

Why Did 95 Percent of Vulnerable Downloads Have Fixes Available?

Sonatype found that 95% of vulnerable downloads already had fixes available at download time. This isn’t developers waiting for patches. This is developers choosing vulnerable versions despite available fixes.

Three root causes.

First up, complacent dependency management. The “set and forget” anti-pattern. Dependencies get pinned and never updated. No process for monitoring security advisories. Update cadence is quarterly or annual, if it exists at all.

Second, update friction without automation. The manual process doesn’t scale. Check the registry, test compatibility, merge the PR, deploy. Do that for 200 dependencies per project. High effort, high perceived risk. “If it works, don’t touch it” takes over.

Third, risk aversion creating update debt. Teams fear breaking changes from non-security updates. So they adopt “security updates only” policies. This creates large version gaps. The larger the gap, the harder updates become. Debt accumulates.

These three causes create worst-case exposure. Complacent management means high unfixed risk. Update debt means high corrosive risk.

Complacent dependency management results in risk that is seven times higher at 3.6% compared to 0.5% for active management. That 0.5% is the no path forward rate – components with no fixed version available.

The other 3.1% is complacency. Vulnerable components with available fixes that weren’t applied.

That gap from 0.5% to 3.6% is process failure. Your failure.

What Is the Libyears Metric and What Does It Tell You?

Libyears measures dependency age. For each dependency, you calculate the time between your current version and the latest version. Express it in years. Sum it across all dependencies.

Say your project uses React 16.8 (released February 2019). Latest is React 18.2 (June 2022). That’s 3.36 libyears. If you’ve got 50 dependencies with similar gaps, you could have 50+ libyears total.

Higher libyears correlate with more vulnerabilities. You are four times as likely to have security issues when you use outdated dependencies.

Libyears correlates with vulnerability density (older versions more likely to have CVEs), remediation difficulty (larger gaps mean more breaking changes), and maintenance signal (high libyears suggests complacent management).

Use libyears operationally. Calculate current libyears as a baseline. Set reduction goals. Track it as a process health metric. Prioritise updates for dependencies contributing the most libyears.

Libyears has limitations though. Age doesn’t equal vulnerable. Some old versions are still maintained. Use it alongside CVE scanning, not as a replacement.

How Does Log4Shell Demonstrate the Persistent Risk Problem?

Log4Shell (CVE-2021-44228) was disclosed December 9, 2021. CVSS 10.0. Remote code execution. Apache released a fix on December 10 – less than 24 hours later.

Thirteen percent of all Log4j downloads are still vulnerable versions nearly three years later.

Read that again. Three years. Non-breaking patches available within 24 hours. Mainstream media coverage. Government advisories. And 13% of downloads are still vulnerable.

The patch was a minor version bump from 2.14.1 to 2.15.0. No API breaking changes. Drop-in replacement. Clear upgrade guidance. This was as easy as security patches get.

The timeline tells the story:

December 10, 2021: Patch available.

January 2022: 80%+ of downloads still vulnerable.

June 2022: 50% still vulnerable.

December 2023: 25% still vulnerable.

December 2024: 13% still vulnerable.

This proves three things. High unfixed risk (13% didn’t patch despite availability), high corrosive risk (organisations using old Log4j likely have high libyears across all dependencies), and process failure (severity and publicity didn’t guarantee patching without proactive automation).

Despite Log4Shell being one of the most well-known vulnerabilities encountered in the last ten years, teams continue choosing known vulnerabilities.

If a CVSS 10.0 with non-breaking patches and global coverage still affects 13% of downloads three years later, what’s happening with medium-severity CVEs in your less-critical dependencies?

What Is the Difference Between Active and Passive Dependency Management?

Active management reduces persistent risk by 7x compared to passive approaches.

Passive management is reactive. Updates get triggered by incidents or quarterly reviews. Process is manual. Cadence is infrequent and batched. Result: high unfixed risk plus high corrosive risk.

Here’s the passive pattern: scheduled “dependency update sprint” addressing months of debt. Teams find 50+ outdated dependencies. Half have breaking changes. Testing takes weeks. Something breaks. They learn: “dependency updates are risky and painful.” So they do it less frequently. The cycle reinforces itself.

Active management is proactive. Updates get triggered by automated monitoring and continuous PRs from bots like Dependabot or Renovate. Process is automated: bot creates PR, CI runs tests, human reviews if needed, merge. Cadence is continuous and incremental. Result: low unfixed risk plus low corrosive risk.

Here’s the active pattern: daily or weekly automated PRs with small version bumps. Tests run automatically. Most patch versions automerge if tests pass. Minor versions get reviewed weekly. Major versions get planned quarterly. Effort spreads out: 10 minutes per week instead of 40 hours per quarter.

Active management prevents update debt. Small gaps are easier to close than large gaps. Updating from 1.2.3 to 1.2.4 is low risk. Updating from 1.2.3 to 2.5.0 after two years is high risk.

Continuous testing catches breaking changes early. Effort gets spread out. Teams maintain familiarity with dependency changes.

You need a few things: automated testing (CI/CD with good coverage), merge policies (criteria for automerging patches, review cadence for minors, planning for majors), the tools (Dependabot or Renovate), and a cultural shift (trust automation, embrace continuous small changes).

The data backs this up. Mean-time-to-remediation: 2-7 days for active versus 60-180 days for passive. Lower update failure rate. Reduced corrosive risk.

How Do Dependabot and Renovate Enable Active Management?

Both tools automate the same workflow. Monitor package registries. Detect new versions and security advisories. Create pull requests. Run CI tests. Wait for review or automerge based on policies.

Repositories with automated dependency updates experience 40% fewer security vulnerabilities than those without automation.

Dependabot is GitHub’s built-in tool. Free. GitHub only. Configuration in .github/dependabot.yml. Security updates, version updates, grouped updates, scheduling (daily, weekly, monthly). Works with 14 package managers. Minimal configuration. Limitations: GitHub-only, less flexibility, basic scheduling.

Renovate is from Mend. Free for open source, freemium for private. Multi-platform: GitHub, GitLab, Bitbucket, Azure DevOps, Gitea. Configuration in renovate.json. All Dependabot features plus advanced scheduling (timezone-aware, business hours), sophisticated automerge policies, dependency dashboard, regex support. Works with over 30 package managers. Steeper learning curve, more complex configuration, higher flexibility.

Use Dependabot if you’re GitHub-only, want zero setup complexity, and basic scheduling works for you.

Use Renovate if you’re multi-platform, need advanced scheduling, want sophisticated automerge policies, or you’ll invest setup time for flexibility.

For most small to medium organisations on GitHub, Dependabot is the right choice. For complex requirements (monorepos, multi-platform), Renovate is worth it.

Either tool reduces persistent risk. The choice is about organisational fit, not security outcomes. For a complete dependency management tool comparison covering Snyk, open source alternatives, and decision frameworks, see our tool selection guide.

What Is the Right Dependency Pinning and Update Strategy?

Applications (web apps, APIs, services) should pin exact versions. Always commit lockfiles (package-lock.json, Gemfile.lock, poetry.lock). Update via automation.

Why? Reproducible builds. Without exact versions and lockfiles, npm install on different days installs different versions. That’s untested code in production.

Libraries (published packages) should use semantic version ranges (^1.2.3 or ~1.2.3). Still commit lockfiles for development. Test against minimum and maximum supported versions.

Why? Downstream flexibility. If your library pins React to exactly 18.2.0, consumers can’t use it with React 18.3.0 without conflicts.

Patch versions (1.2.3 to 1.2.4): bug fixes and security patches. No breaking changes. Risk: very low. Strategy: automerge if tests pass. Cadence: continuous.

Minor versions (1.2.3 to 1.3.0): new features, backward compatible in theory. Risk: low (but breaking changes happen). Strategy: automated PR, human review, weekly merge.

Major versions (1.2.3 to 2.0.0): breaking changes. Risk: medium to high. Strategy: manual planning, changelog review, deliberate update. Cadence: quarterly or as needed.

Security updates always bypass the normal schedule.

End-of-life (EOL) components no longer receive security patches. EOL components indicate lack of dependency management. They signal systemic complacency.

EOL components require replacement, not updating. Check if the maintainer recommends a successor. Evaluate alternatives: community forks, foundation-backed projects, different approaches. Budget migration time like a major upgrade.

Dependabot and Renovate flag EOL versions automatically. Don’t ignore the warnings. EOL dependencies accumulate unfixed vulnerabilities indefinitely. No patches are coming.

How Do You Implement Active Dependency Management in Your Organisation?

Start with a pilot project. Choose one with 10-20 dependencies and good test coverage. Enable Dependabot or Renovate. Observe before rolling out broadly.

This transition isn’t just technical. It’s cultural. Teams need to shift from “if it works, don’t touch it” to “keep it current.”

Measure where you are. Calculate current libyears. Identify EOL components. Document your current update cadence.

Choose your tool. Dependabot for GitHub-only organisations wanting simplicity. Renovate for multi-platform or complex requirements.

Enable automation on the pilot. Configure schedules that won’t overwhelm. Daily security updates, weekly version updates. Set up dependency grouping.

Define criteria for automated merges versus human review. Patches passing CI automerge. Minors and majors need review.

Establish a regular PR review cadence. Weekly 15-minute standup or asynchronous. Consistency is key.

After 4-6 weeks, expand to additional projects. Monitor metrics: libyears decreasing, time-to-patch decreasing, PR merge rate increasing.

Plan full rollout over 2-3 months. Rushing creates resistance. Gradual expansion builds buy-in.

Success looks like: dependencies stay current (low libyears), patches deploy quickly (days not months), and your team spends less time on dependency management because automation handles the routine work.

Wrapping It Up

Persistent risk is unfixed risk plus corrosive risk. The 95% statistic proves this isn’t a patch availability problem. It’s a process problem. Log4Shell persistence (13% still vulnerable three years post-patch) proves that severity and publicity don’t guarantee patching without proactive process.

Active management reduces risk by 7x compared to passive approaches. Dependabot (GitHub, simple, free) or Renovate (multi-platform, flexible, more complex) enable this shift.

The tooling exists. The patches exist. The evidence is clear.

Start today. Calculate libyears for your projects. Enable Dependabot or Renovate on one pilot project this week. Establish a regular review cadence. Measure and iterate.

Active dependency management isn’t a technical problem requiring new patches. It’s an operational problem requiring better processes. The question isn’t whether to implement it. The question is how quickly you can roll it out.

For a complete overview of supply chain security practices covering frameworks, threat landscape, and additional operational strategies, see our comprehensive guide.

Frequently Asked Questions

What is the difference between direct and transitive dependencies?

Direct dependencies are packages you explicitly declare in your manifest file like package.json or requirements.txt. You chose them. You added them to your project.

Transitive dependencies are packages required by your direct dependencies. Dependencies of dependencies. You didn’t choose them directly, but your project needs them.

A project with 100 direct dependencies typically has 500+ transitive dependencies. That’s a 5x multiplier. Both Dependabot and Renovate monitor both types because vulnerable transitive dependencies compromise your application just as much as vulnerable direct dependencies.

How often should I update dependencies?

Patch versions (1.2.3 to 1.2.4): automerge continuously if tests pass. These should only contain bug fixes and security patches.

Minor versions (1.2.x to 1.3.0): review and merge weekly in batches. These should be backward compatible but deserve human review because semantic versioning promises get broken.

Major versions (1.x.x to 2.0.0): plan deliberately with quarterly review sessions. These contain breaking changes requiring migration effort.

Security updates always bypass the schedule for immediate review. If a patch addresses a CVE, review it now.

What is dependency confusion and how do lockfiles prevent it?

Dependency confusion is a supply chain attack where an attacker publishes a malicious package with the same name as your private package to a public registry. Package managers may install the malicious public version instead of your private version.

Lockfiles prevent this by recording the exact registry source and cryptographic checksum for each dependency. When you run npm install with a lockfile present, npm installs exactly what the lockfile specifies, including which registry it came from. No substitution possible.

Should I automerge all dependency updates?

No. Automerge only patch versions that pass your CI test suite.

Patch versions (1.2.3 to 1.2.4) should contain bug fixes and security patches with no breaking changes according to semantic versioning. Configure Dependabot or Renovate to automerge these if tests pass.

Minor versions (1.3.0) and major versions (2.0.0) require human review because they can introduce breaking changes. Real-world packages break semantic versioning contracts regularly.

What is a dependency lockfile and why do I need to commit it?

A lockfile (package-lock.json, Gemfile.lock, poetry.lock, go.sum) records the exact version of every dependency and transitive dependency installed, including cryptographic checksums.

Committing it ensures every developer and every CI/CD run uses identical versions. Without committed lockfiles, npm install run on Monday installs different versions than npm install run on Tuesday if any package published a new version in between.

This prevents “works on my machine” issues. Always commit lockfiles. Validate them in CI. Treat changes to lockfiles as significant.

How do I handle dependencies that are no longer maintained?

End-of-life dependencies require replacement, not updating. No patches are coming. Vulnerabilities will accumulate indefinitely.

First, check if the maintainer recommends a successor package. Often this is noted in the README or a GitHub banner.

Second, evaluate alternatives. Look for community forks with active maintenance, foundation-backed alternatives, or different approaches to solving the same problem.

Third, budget migration time like you would for a major version upgrade. EOL migrations often require significant refactoring.

Tools like Dependabot flag EOL versions automatically. Don’t ignore these warnings.

What is the libyears metric and should I track it?

Libyears measures your dependency age by summing the years between your current version and the latest version across all dependencies.

If you use React 16.8 (released February 2019) and the latest is 18.2 (June 2022), that’s 3.36 libyears for one dependency. Sum across all dependencies to get your project’s total.

Research shows projects with higher libyears have more vulnerabilities. Track libyears as a process health metric. Goal: less than 10 libyears per project, indicating active dependency management.

Can I use Dependabot and Renovate at the same time?

No. Running both simultaneously creates duplicate PRs and merge conflicts. Choose one based on your needs.

Dependabot for GitHub-only organisations wanting zero setup complexity. Renovate for multi-platform (GitLab, Bitbucket) or needing advanced scheduling.

Both reduce persistent risk equally when configured properly. The choice is about organisational fit, not security outcomes. Pick one, configure it well, and stick with it.

How do I migrate from quarterly dependency updates to active management?

Start with a pilot project with good test coverage. Enable Dependabot or Renovate. Configure weekly scheduling. Observe PR volume for 2-3 weeks.

Establish automerge rules for patch versions that pass CI tests. Hold weekly review sessions for minor versions. After 4-6 weeks, expand to 3-5 more projects.

Track metrics: libyears (should decrease), mean-time-to-patch (should decrease), and update PR merge rate (should increase).

Full rollout takes 2-3 months as teams adapt to continuous small updates instead of large infrequent batches. The cultural shift from “if it works, don’t touch it” to “keep it current” takes time.

What’s the difference between a vulnerability scanner and Dependabot?

Vulnerability scanners (Snyk, GitHub Advanced Security, npm audit) identify CVEs in your current dependencies. They tell you what’s vulnerable. They don’t fix it.

Dependabot and Renovate both identify vulnerabilities AND create automated update PRs to fix them. Then they rerun after merging to verify remediation worked.

Think of scanners as detection tools. Think of Dependabot and Renovate as detection plus remediation automation.

For complete coverage, use both. Scanner provides comprehensive CVE database coverage. Dependabot provides automated updates.

How do I prioritise dependency updates when I have 50+ pending PRs?

Use this priority order:

First, patch immediately for critical CVEs with public exploits. These are emergencies.

Second, plan replacement for EOL components. No patches are coming. Risk accumulates daily.

Third, patch within 7 days for high-severity CVEs.

Fourth, reduce libyears by focusing on dependencies older than 2 years. These carry high corrosive risk.

Fifth, handle medium-severity CVEs and feature updates on normal cadence.

Configure Dependabot or Renovate to group related dependencies like all AWS SDK packages. This reduces PR count by 50-70%. Enable automerge for patches to eliminate manual review.

What happens if a dependency update breaks my tests?

When Dependabot or Renovate creates a PR and your CI tests fail, you have three options.

First, investigate and fix if it’s a real compatibility issue. Update your code to work with the new dependency version.

Second, skip this version if it’s a known regression in the dependency. Wait for the next patch release. Close the PR with a note explaining why.

Third, pin temporarily if you need time to investigate. Add this version to your ignore list. Schedule follow-up work to either fix your code or find an alternative dependency. Don’t let temporary pins become permanent.

Tools won’t automerge failing tests. This is why good test coverage is a prerequisite for active dependency management. Tests catch breaking changes before production.

AUTHOR

James A. Wondrasek James A. Wondrasek

SHARE ARTICLE

Share
Copy Link

Related Articles

Need a reliable team to help achieve your software goals?

Drop us a line! We'd love to discuss your project.

Offices Dots
Offices

BUSINESS HOURS

Monday - Friday
9 AM - 9 PM (Sydney Time)
9 AM - 5 PM (Yogyakarta Time)

Monday - Friday
9 AM - 9 PM (Sydney Time)
9 AM - 5 PM (Yogyakarta Time)

Sydney

SYDNEY

55 Pyrmont Bridge Road
Pyrmont, NSW, 2009
Australia

55 Pyrmont Bridge Road, Pyrmont, NSW, 2009, Australia

+61 2-8123-0997

Yogyakarta

YOGYAKARTA

Unit A & B
Jl. Prof. Herman Yohanes No.1125, Terban, Gondokusuman, Yogyakarta,
Daerah Istimewa Yogyakarta 55223
Indonesia

Unit A & B Jl. Prof. Herman Yohanes No.1125, Yogyakarta, Daerah Istimewa Yogyakarta 55223, Indonesia

+62 274-4539660
Bandung

BANDUNG

JL. Banda No. 30
Bandung 40115
Indonesia

JL. Banda No. 30, Bandung 40115, Indonesia

+62 858-6514-9577

Subscribe to our newsletter