EOL for Beginners

What is a dependency? —
the code you ship without ever reading it.

Published 2026-07-08 · 10 min read · endoflife.ai Research

Modern software is mostly other people's software. A typical application is a thin layer of original code on top of hundreds — often thousands — of borrowed packages, each maintained (or abandoned) by someone you've never met, on a schedule you don't control. This guide explains what dependencies actually are, how one line in a manifest becomes a thousand packages on disk, what lockfiles and SBOMs do, and why an eleven-line package once broke a measurable fraction of the internet.

Dependencies in plain English

A dependency is a package of someone else's code your application needs to run. Rather than writing date handling, HTTP parsing, or cryptography yourself, you declare a need — one line in a manifest file like package.json (JavaScript), pom.xml (Java), or requirements.txt (Python) — and a package manager (npm, Maven, pip) fetches the code from a public registry and wires it in.

This is the deal that makes modern development fast: you get battle-tested implementations of hard problems for free. The fine print is that every package you pull in has its own release schedule, its own security history, its own maintainers with their own lives — and, critically, its own end of life. When you add a dependency you're not acquiring code; you're entering an unwritten support relationship.

The iceberg: direct vs transitive

The packages you declare are your direct dependencies — the part of the iceberg above water. But each of those has dependencies of its own, and those have theirs. Everything below your declarations is transitive: code you ship, run, and are exploitable through, but never chose. The ratio is brutal — declaring a few dozen direct packages routinely resolves to hundreds or thousands of transitive ones on disk.

This is the single most important fact in dependency security, and it's why Log4Shell was a global event: most affected organizations had never typed "log4j" anywhere. It arrived inside frameworks, inside logging facades, inside vendor products — and in the first days of the crisis, the hardest question wasn't "how do we patch?" but "do we even ship this?" The same pattern holds for SnakeYAML riding in through Spring stacks, dom4j through Hibernate-era apps, and elliptic under half the JavaScript crypto ecosystem — covered in depth in CVEs by stack layer.

Version ranges and semver: what ^ and ~ promise

Most ecosystems use semantic versioningMAJOR.MINOR.PATCH, where majors may break you, minors add features compatibly, and patches only fix. Manifests usually declare ranges, not exact versions: in npm, ^4.17.0 means "any 4.x from 4.17.0 up," and ~4.17.0 means "any 4.17.x patch." The promise is that compatible updates flow in automatically.

Ranges are why two installs of the same project a week apart can produce different code on disk — and why semver is a social contract, not a guarantee. Maintainers mislabel breaking changes as minors; security fixes sometimes only land in a new major that your range will never reach. A range can be simultaneously "up to date" per your manifest and years stale per the registry — Lodash 3.x ranges kept resolving happily for years after that line went end-of-life in 2016.

Lockfiles: the only true inventory you have

Because ranges are ambiguous, package managers write a lockfilepackage-lock.json, yarn.lock, poetry.lock, Gemfile.lock — recording the exact version of every package, direct and transitive, that was actually resolved. Two things follow:

left-pad: the day the iceberg surfaced

In March 2016, a maintainer in a naming dispute unpublished his packages from npm. One of them, left-pad, was eleven lines of code that padded strings with spaces. Within hours, builds broke across the industry — including at companies that had never heard of it — because giants like Babel and React tooling depended on it transitively. npm took the unprecedented step of restoring the package, then changed its unpublish policy entirely.

left-pad wasn't a security incident; nothing was exploited. That's what makes it the perfect teaching case: it demonstrated, harmlessly, that the entire modern build chain stands on transitive dependencies nobody audits, maintained by individuals with no obligation to keep them available — a dress rehearsal for Log4Shell five years later, where the same structure met an actual vulnerability.

How dependencies age, break, and get exploited

Dependency risk arrives on three distinct clocks:

SBOMs and audits: seeing what you ship

A Software Bill of Materials (SBOM) is the formalized answer to "what do we ship?" — a machine-readable inventory of every component in an artifact, in standard formats (CycloneDX, SPDX). Post-Log4Shell, SBOMs moved from nice-to-have to procurement requirement: US federal guidance now expects them from software vendors, and enterprise customers increasingly demand them in contracts.

Day to day, the same visibility comes from built-in audit tooling — npm audit, pip-audit, Maven/Gradle dependency scanners, and bots like Dependabot and Renovate that open update PRs automatically. Every one of them works from the lockfile. What most of them don't track is lifecycle: a package can carry zero known CVEs and still be five years abandoned. That's the gap our catalog, Stack Scanner, and API exist to close — pairing "is it vulnerable?" with the question that predicts it: "is anyone still maintaining this?"

The hygiene checklist

Dependencies are the best trade in software: decades of other people's expertise for one line in a manifest. Just remember what the line actually buys — code plus a lifecycle — and keep both on your books.

The Monthly EOL Digest™

Once a month — critical end-of-life dates, CVE blind spots, and lifecycle changes worth knowing about.

✓ You're on the list.