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."
package.json / pom.xml / requirements.txt. Usually a few dozen.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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
npm config set ignore-scripts true).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.