Safeguard
Security Guides

OWASP A06: Vulnerable and Outdated Components — A Deep-Dive Guide

Vulnerable and Outdated Components rank #6 in the OWASP Top 10 (2021). A deep dive into transitive risk, real CVEs like Log4Shell, and how to fix it in 2026.

Daniel Osei
Security Researcher
6 min read

Vulnerable and Outdated Components is the risk of running software — libraries, frameworks, runtimes, and their dependencies — with known, published vulnerabilities, and it ranks number six on the OWASP Top 10 (2021). It is the category most directly about supply chain risk: modern applications are mostly other people's code, and a single vulnerable package deep in your dependency tree can hand an attacker remote code execution even if your own code is flawless. This guide explains what the category covers, why it earned a rare community-survey top ranking, the CVEs that made it infamous, and how to manage dependency risk without drowning in alerts.

What OWASP A06 actually covers

A06:2021 maps to just three CWEs — CWE-1104 (use of unmaintained third-party components), CWE-937, and CWE-1035 — but its narrow taxonomy belies its enormous reach. The category covers any situation where you run a component with a known vulnerability, where you do not know the versions of all the components you use (including transitive dependencies), where the software is out of date or unsupported, or where you do not patch in a timely, risk-based fashion. The defining challenge is depth. A typical application declares a few dozen direct dependencies but pulls in hundreds or thousands transitively, and a vulnerability three levels down is just as exploitable as one you chose directly — you simply never see it without a software bill of materials.

Why it ranks number six

This category is unusual in the OWASP Top 10 because it ranked number two in the community survey — practitioners rate it far more dangerous than the incidence data alone would place it. That gap reflects lived experience: security teams know that dependency vulnerabilities are the ones they cannot fully control, cannot always fix quickly, and cannot even inventory without tooling. OWASP could not assign the category a full set of exploit and impact statistics because CVEs mapped to specific components are hard to aggregate the way application-level CWEs are. The number-six placement is therefore a compromise between limited data and a strong practitioner signal — and the wave of supply chain incidents since 2021 has vindicated the practitioners.

Real-world examples

Log4Shell (CVE-2021-44228) is the defining incident: a remote code execution flaw in Log4j, a logging library embedded transitively in a staggering share of Java applications, meant that organizations which had never heard of Log4j were suddenly exploitable through it. The Equifax breach of 2017 traced back to CVE-2017-5638, a remote code execution vulnerability in Apache Struts that had a patch available months before the breach; the failure was not the flaw but the unpatched, unknown presence of the component, leading to the exposure of 147 million people's data. Both cases share the A06 signature — the vulnerable code was not written by the victim, was buried in the dependency graph, and remained exploitable because no one knew it was there or how urgently it needed patching.

Vulnerable versus fixed code

For this category the "code" is your dependency manifest, and the fix is a version bump informed by the advisory.

// VULNERABLE: pins Log4j to a version with CVE-2021-44228 (RCE via JNDI)
{
  "dependencies": {
    "org.apache.logging.log4j:log4j-core": "2.14.1"
  }
}
// FIXED: upgrade past the patched release; verify transitive pulls too
{
  "dependencies": {
    "org.apache.logging.log4j:log4j-core": "2.24.3"
  }
}

The trap is that Log4j is usually a transitive dependency, not one you declared, so bumping your direct dependencies is not enough — you must resolve the full graph and confirm no path still pulls a vulnerable version. That is exactly why a software bill of materials and dependency resolution, rather than a manual manifest edit, are the real controls here.

Prevention checklist

  • Generate a software bill of materials (SBOM) for every build so you know every component and version, direct and transitive.
  • Continuously scan dependencies against vulnerability databases rather than checking once at release.
  • Remove unused dependencies and features to shrink the attack surface you have to track.
  • Prioritize by reachability and exploitability, not raw CVE count, so critical patchable issues rise to the top.
  • Pin versions and use lockfiles so builds are reproducible and upgrades are deliberate.
  • Prefer maintained components; treat an unmaintained dependency as a latent vulnerability.
  • Automate the upgrade path so patching is a routine pull request, not a fire drill.
  • Monitor for newly disclosed CVEs against components you already ship, since today's safe version can be tomorrow's advisory.

How Safeguard helps

This is the category Safeguard was built for. Our SCA engine generates a complete SBOM from your build artifacts and maps every direct and transitive component against current advisories, so nothing hides three levels down the way Log4j did. The differentiator is reachability: instead of dumping a thousand raw CVEs, Safeguard traces whether each vulnerable function is actually invoked from a reachable entry point, so a critical, exploitable dependency is not buried under dormant findings. Griffin AI then prioritizes what to fix and Auto-Fix opens the upgrade pull request — resolving the whole graph, not just your direct manifest — so patching becomes a routine, reviewable change. Run it in your pipeline with the Safeguard CLI, and see how reachability-first triage differs from alert-count tools on our comparison against Snyk.

Frequently Asked Questions

Why do practitioners rate this category more dangerous than the data does?

Because dependency vulnerabilities are the ones teams feel least able to control. You did not write the code, you may not know it is even present, and you cannot always patch it on your schedule. That combination of low visibility and low control is why practitioners ranked it second in OWASP's community survey even though aggregate exploit data placed it sixth.

What is a transitive dependency and why does it matter?

A transitive dependency is a package your dependencies pull in, rather than one you declared yourself. It matters because most of your code is transitive — often the large majority — and a vulnerability there is exactly as exploitable as one in a direct dependency. Log4Shell hit so hard precisely because Log4j was usually transitive and therefore invisible without an SBOM.

How does reachability analysis reduce dependency noise?

Reachability analysis checks whether your application actually calls the vulnerable function in a component, not just whether the component is present. Many CVEs live in code paths you never execute, so they are not practically exploitable in your context. Filtering by reachability turns an overwhelming list of raw findings into a short list of issues that genuinely matter.

Is keeping everything on the latest version the safest policy?

Updating regularly is far safer than lagging, but "always latest" carries its own risks — breaking changes and the occasional malicious release published to a package registry. The better policy is timely, risk-based patching: prioritize known-exploitable vulnerabilities, use lockfiles for reproducibility, and verify upgrades in CI so security and stability move together.

Ready to see every vulnerable component in your stack? Start scanning at app.safeguard.sh/register, or read the setup guide at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.