OWASP Top 10 Deep Dive · 7 of 8

Vulnerable Dependencies — The 90% You Didn't Write

Modern apps are mostly someone else's code. Each npm install, pip install, or Maven build pulls in hundreds of libraries with thousands of transitive dependencies. When one of them ships a CVE — or worse, gets compromised by an attacker — your app inherits the problem. Managing this surface is now a core engineering responsibility, not an "ops thing."

CVESBOMDependabotSupply ChainLog4Shell
← Back to Security
Quick Facts

The Shape of the Problem

Basic Concepts

  • Direct dependencies: the libraries you wrote into package.json / pom.xml / requirements.txt. Usually a few dozen.
  • Transitive dependencies: the libraries those depend on. Usually hundreds to thousands.
  • CVE: Common Vulnerabilities and Exposures. A unique ID for a publicly disclosed flaw.
  • SBOM (Software Bill of Materials): a machine-readable list of every component and version in your build. Increasingly mandatory in regulated industries.
  • Reachability: a CVE only matters if your code actually calls the vulnerable code path. Most modern scanners try to filter on this.
  • Supply-chain attacks: the dependency itself is malicious — typo-squat packages, compromised maintainers, hijacked accounts. Different threat from "old library has a CVE."
The Family

The Threats in This Category

Known CVEs in Old Versions

The classic. Log4Shell (CVE-2021-44228, Log4j 2.x) demonstrated this at scale — an unauthenticated RCE in a logging library used by half the Java world. Companies spent weeks locating and patching every instance. Most teams discovered they didn't know what versions they were running.

The pattern: a vulnerability is disclosed; attackers start scanning the internet within hours; if your dependency's old, you're a target.

Transitive Surprises

You don't depend on the vulnerable library directly — but a library you do depend on does. npm projects routinely pull 1,000+ packages from a 10-line direct list. Auditing only the top level misses most of the surface.

Typo-Squatting and Confusion Attacks

Attacker publishes requets next to requests, colors next to colors, hoping a developer fat-fingers the install. Or — dependency confusion — registers your internal package name on the public registry, and your build pulls the malicious public version because it's "newer." Real attacks; real money lost.

Compromised Maintainers

event-stream in 2018, node-ipc in 2022, xz-utils in 2024 — popular packages briefly published malicious versions because an attacker took over a maintainer account or social-engineered their way in. Detection lag was days to years.

Build-Time Code Execution

Many ecosystems run install-time scripts. npm install can execute arbitrary code from any package via postinstall; Python wheels can run setup hooks; Maven plugins are code. Compromise a build dependency and you compromise the developer's laptop and the CI runner.

Defenses

The Working Posture

1. Lockfiles, Always

package-lock.json, yarn.lock, poetry.lock, Pipfile.lock, Cargo.lock, go.sum. Pin exact versions and the integrity hashes. Without a lockfile, "the same build" yesterday and today can install different code.

2. Continuous Vulnerability Scanning

GitHub Dependabot, Snyk, Trivy, OWASP Dependency-Check, Mend, Sonatype, JFrog Xray. Wire into CI; fail builds on critical CVEs; auto-PR for safe minor/patch upgrades. The tool is the easy part; the hard part is treating PRs from it as work, not noise.

3. Generate and Store SBOMs

Generate a CycloneDX or SPDX SBOM for every release. Store it with the artifact. When a new CVE drops at 2 a.m., grepping SBOMs answers "are we exposed?" in minutes instead of days. Increasingly required by compliance (US Executive Order 14028, EU CRA).

4. Update Routinely, Not Reactively

The teams that survive CVE drops are the ones already on or near the latest minor versions. Teams pinned to "the version we shipped 18 months ago" face weeks of upgrade work under fire. Renovate-bot or Dependabot's auto-merge for minor/patch is usually safe.

5. Slim the Tree

Every dependency is a future CVE waiting to be found. Question whether you really need the new package. Prefer well-maintained ones over flashy new ones. Use the standard library when it's adequate. Audit and remove unused dependencies — they're still on the classpath, still attackable.

6. Defend Against Supply-Chain Attacks
  • Use a private registry / proxy. Cache the public registry and review what flows in.
  • Pin or scope-prefix internal packages so dependency confusion can't occur.
  • Disable install-time scripts where possible (npm config set ignore-scripts true).
  • Run builds in ephemeral, locked-down environments — not on developer laptops.
  • Verify package signatures where the ecosystem supports it (Sigstore, npm provenance, Maven signed jars).
7. Reachability-Aware Triage

Most CVEs in your tree don't actually affect you — the vulnerable code path isn't called by your app. Modern scanners (Snyk, Endor, Backslash) compute reachability and prioritize the CVEs you actually need to fix today versus the ones that can wait for the next sprint.

When a CVE Drops

The Five-Minute Playbook

  1. Find every instance. Grep SBOMs and lockfiles across all repos and deployed images.
  2. Assess reachability. Is your code path affected? Is there a workaround (config flag, network rule)?
  3. Apply the fix. Upgrade, override the transitive version, or apply the vendor's mitigation.
  4. Rebuild and roll out. Don't just patch repos — patch container images, Lambda layers, anything immutable.
  5. Audit logs for prior exploitation. If the CVE was disclosed weeks ago, attackers were probably already trying.
Continue

Other OWASP Top 10