Vulnerable and Outdated Components in SaaS Dependency Chains
Dependency chains hide most real risk where teams can't see it.

Vulnerable and outdated components stay in SaaS dependency chains because of how modern software gets built, not because engineers are careless. Most code running in a production system today was written by someone outside the team that shipped it. A developer writes only a fraction of the application directly; the rest arrives through package managers, often pulled in several layers removed from anyone who actually reviewed it. That gap between what a team wrote and what a team runs is where the risk lives.
OWASP's Top 10 for 2025 ranks Software Supply Chain Failures, the category that absorbed the old "Vulnerable and Outdated Components" entry, at #3. So dependency chains have become a primary attack surface in modern software, not a side issue you handle with a quarterly patch cycle.
Few teams choose a bad component on purpose. A framework gets pulled in for speed, a plugin solves a problem fast, a transitive package rides in on something else entirely, and all of it gets absorbed as trusted infrastructure. Nobody revisits that trust later. Ownership of these components gets fuzzy, update schedules slow down, and the picture of what's actually running in production fades, all without a single meeting where anyone agreed to accept the risk.
The three layers where the vulnerability management system breaks down
Vulnerability management breaks down across three layers that feed into each other: data, consumption, and ecosystem. Fixing one without touching the others leaves most of the exposure in place.
The data layer is the foundation everything else depends on, and it's cracking. The global vulnerability intelligence system, built on CVE and NVD records and the advisory feeds downstream tools rely on, has gotten less complete, less consistent, and slower, even as the volume flowing through it grows. Coverage gaps let serious vulnerabilities into the ecosystem with no real signal attached to them. When Sonatype went back and scored CVEs that had never been scored, nearly half turned out to be High or Critical severity. Scoring also disagrees across sources: the same CVE can land in a different severity bucket depending on whether a tool trusts NVD or an independent analysis, and teams working from different feeds end up pulling in different directions on the same vulnerability. The raw CVE count has doubled in five years, and the backlog of unscored entries has grown even faster than that, straining a system that was built for manual review at a much slower pace of software release.
The consumption layer is where teams keep installing vulnerable components even when accurate data and working patches already exist. Pinned versions, sprawling transitive dependency trees, stale CI build images, and AI-generated manifests all push in the same direction: toward the continued use of insecure versions. The next two sections unpack how that happens.
The ecosystem layer is the long tail: a growing share of software runs on components that are unsupported or past end of life, with no patches coming no matter how well a team manages everything upstream. Old frameworks, abandoned libraries, and orphaned package versions pile up as technical debt that nobody budgeted time to retire. For these components, the standard "scan, ticket, patch" workflow stops functioning as a workflow. It turns into a backlog generator, because there is no patch to apply.
How transitive dependencies hide the majority of real exposure
Most of the real risk in a dependency chain doesn't sit in the packages a team picked out directly. It sits in the packages those packages brought along with them, several layers down from anything an engineer actually chose or reviewed.
Transitive dependencies don't get the same attention direct dependencies do. They appear in a lock file, but they rarely appear in any security review a developer actually performs day to day. Each package carries its own dependency tree, and those trees multiply against each other, so the attack surface grows combinatorially. No manual review process scales to cover that kind of growth.
The Polyfill.io incident shows what that blast radius looks like in practice. A dependency that had been safe for years was compromised and used to serve malicious JavaScript across hundreds of thousands of sites. Teams that had done real diligence on their direct dependencies still got hit, because the exposure came through a component they had no reason to be watching. That's the core problem with transitive risk: careful review of what a team chose directly still leaves most of the actual exposure untouched.
Why dependency lag keeps widening as pipelines move faster
The median dependency now trails its latest major version by 278 days, up from the year before, because build pipelines keep getting faster while the version a team runs falls further behind the version currently available.
Pinning is a big part of why. Teams pin dependency versions to keep builds reproducible and avoid breaking changes showing up unannounced, which is reasonable engineering practice on its own terms. But pinning only works safely if something actively goes back and unpins, tests, and updates on a schedule. Without that active process, the installed version stays frozen in place, but new vulnerabilities keep getting discovered against it. The version doesn't move. The threat landscape does.
CI/CD build environments add another layer to the same problem. Outdated base images carry old versions of runtimes, package managers, and build tooling, so a build can run cleanly from start to finish and still produce an artifact with known vulnerabilities baked into the image layer itself. The pipeline is also a target in its own right: it stores secrets, tokens, and signing keys, and a compromised build step can inject malicious code during the build process even when the application repository itself is clean. Dependency lag isn't only about what ships in the final product; it's also about what the build process touches on the way there.
AI code generation and new vulnerable components in dependency chains
AI coding assistants have become a new source of dependency risk, and the mechanism is simple: they recommend the package version that showed up most often in their training data, not the version that's currently secure. That training data lags behind the threat landscape by design, since it was collected and frozen at some point in the past. Supply chain compromise is already showing up as a major driver of incidents involving AI applications: 30% of breaches involving AI applications originated from supply chain compromises.
An AI assistant can only recommend what it learned. If a patch ships for a known flaw, an AI coding tool trained before that patch existed will keep suggesting the old, vulnerable version, simply because that version was dominant in whatever corpus it learned from. The suggestion looks current, but it reflects a version that was dominant in training data collected before the patch existed, not the present state of the package.
AI code generators sometimes recommend package names that have never been registered. A developer who installs one of these hallucinated package names can end up pulling down a malicious package that an attacker registered specifically to catch that traffic, a kind of typosquatting that scales directly with how much teams lean on AI for dependency suggestions. Package poisoning already exploits automation and human error, and AI-generated manifests widen that same opening by producing dependency choices nobody with real knowledge of the package ever reviewed.
The vulnerability classes most commonly introduced through outdated dependencies
The vulnerability classes that outdated components tend to introduce aren't exotic or obscure: they're well understood, reliably exploitable, and in most cases preventable, and that combination is what makes it so costly that they keep showing up through dependency chains anyway.
Remote code execution through an outdated framework is the clearest case, and Log4Shell remains the reference point. Log4j is a logging library, not application logic, yet a critical flaw in it gave attackers arbitrary code execution on any host running the vulnerable version.
SQL injection through an unpatched database driver or ORM layer follows a similar pattern. A single outdated, poorly maintained, or tampered component anywhere in the chain can expose the entire application behind it, and an outdated database driver that trusts raw input while building a query is the textbook version of this failure.
SSRF can enter through a dependency that handles user-supplied URLs, the kind of library a SaaS product uses for webhooks, image uploads, or third-party integrations. A flaw in that library gives an attacker a route into the internal network, including cloud metadata endpoints that hand over temporary credentials with broad permissions attached.
Hardcoded secrets are the fourth recurring class. Secrets baked into a dependency or into build tooling make it into production whenever secret scanning is missing from the pipeline, and that risk grows when AI-generated code introduces credentials a developer never wrote and may never notice sitting there.
The limits of point-in-time scans and annual pen tests
Point-in-time scans and annual penetration tests fail because the dependency chain keeps changing while the test stays fixed to a single date on the calendar.
An annual test proves one specific thing: a defined, scoped set of systems met a certain security bar on that date. It says nothing about the months before or after it, even though compliance paperwork often treats that single snapshot as if it represented continuous assurance. Static SCA tools run into a related problem. They flag outdated components, but they also flood teams with alerts, and when the vulnerability data underneath those alerts is inaccurate or scored inconsistently, teams end up chasing false positives while real risks go unflagged in the noise.
Compliance programs add a quiet distortion of their own. Once satisfying an audit becomes the main goal, testing scope and depth tend to settle at the minimum the requirement demands. That produces a coverage floor, not a security target.
There's also a gap between finding and proving. A vulnerability scan produces a list of possible issues. A penetration test attempts to actually exploit them. Without dynamic testing to confirm what's truly exploitable in the live environment, remediation time gets spent on theoretical risk while real attack paths stay open. Severity itself shifts once runtime context gets applied: only a fraction of dependency vulnerabilities that look critical in isolation stay critical once you check whether an attack path actually reaches that component in the live, deployed configuration. Severity isn't fixed on a spec sheet. It depends on reachability in the real system.
Effective dependency security in a modern SaaS build pipeline
Closing this gap takes continuous automated discovery combined with human-verified proof of exploitability. Automated scanning alone misses too much, and periodic manual review alone moves too slowly, given how fast and how large modern dependency chains have grown.
Start with a software bill of materials that covers the full dependency graph, transitive packages included. Inventorying every component in use is the precondition for managing any of it: a team can't prioritize risk in software it can't see.
Pair software composition analysis, which shows what's sitting in the manifest, with dynamic testing, which confirms what's actually exploitable in the running application. A dynamic-testing-first approach keeps the focus on vulnerabilities that are genuinely reachable, which helps teams prioritize real risk instead of working through alert volume for its own sake.
Move security checks into the CI/CD pipeline at the pull-request stage. Catching a newly introduced vulnerable dependency before it merges costs far less than fixing it after it's live in production, and it runs at the pace development actually moves at.
Pin GitHub Actions to commit hashes. A floating tag can be silently modified by a third party whose repository gets compromised, and pinning to a hash closes that door. This protects the pipeline layer of the attack surface, not just the application code sitting on top of it.
Validate AI-suggested dependencies against current vulnerability databases before anything they recommend enters a manifest file. AI-generated component choices reflect training data that lags the real threat landscape, so running that output through automated SCA checks is a necessary step for any pipeline that uses AI coding assistants.
Whitebox access, meaning real access to source code, dependency manifests, and cloud configuration, is what lets a penetration test map the full attack surface. A blackbox test can't walk the transitive dependency graph or tell you which vulnerable components an external attacker can actually reach. Every finding that comes out of a pen test should be backed by a working exploit before it lands in a remediation backlog, since unverified, theoretical vulnerabilities compete for the same engineering attention that confirmed, real attack paths need.
Retest capability matters just as much as the initial test. Dependency chains change with every merge, so a one-time pen test result goes stale almost immediately. The ability to retest specific components after a fix ships, and to scan continuously on every pull request, is the structural response this problem has been asking for all along.


