All checks

Vulnerable dependencies

Severity: Low – CriticalQuick + Deep· 5 min read

What it is

Your app is mostly code you didn't write: frameworks, libraries and the libraries they depend on. A vulnerable dependency is a package version with a publicly known security flaw. The fix usually exists already, in a newer version, but your app keeps running the old one.

Think of a building with a recalled door lock. The manufacturer published the defect and a replacement, but until someone swaps the lock, the known trick still opens the door.

Why it matters

Once a vulnerability is published, the details are public too: which versions are affected, what the flaw allows, and often how it's triggered. That shifts the race toward attackers, who can look for apps still running the affected version.

The impact depends on the flaw. It might be a crash, data leaking out, a bypassed login or code running on your server. Transitive dependencies (the packages your packages depend on) make it easy to run a vulnerable version without ever having chosen it.

The best-known example is the 2017 Equifax breach. Attackers exploited a known vulnerability in Apache Struts, CVE-2017-5638, months after a fix had been released; the U.S. Government Accountability Office report describes how the unpatched system was used to reach personal data on about 145 million people.

Using components with known vulnerabilities is its own category in the OWASP Top 10.

How it happens

Nobody chooses a vulnerable version on purpose. It happens because:

  • Versions are pinned and never revisited, which is good for stability and bad if nothing prompts an update.
  • Lockfiles hold old transitive versions long after the direct dependency would allow a fixed one.
  • There's no lockfile at all, so you don't know exactly what's installed.
  • The runtime itself is end-of-life, such as an old Node.js or Python release that no longer gets security fixes.
{  "dependencies": {    "express": "4.17.1",    "lodash": "4.17.20",    "jsonwebtoken": "8.5.1"  }}

Each of these files is ordinary. Whether it's dangerous depends on which advisories apply to those exact versions, which is why checking against a vulnerability database beats reading version numbers by eye. For example, lodash 4.17.20 is affected by a command-injection advisory in its template function, CVE-2021-23337, fixed in 4.17.21.

How it's abused

At a conceptual level:

  1. An advisory is published describing a flaw in a package version.
  2. Attackers identify which sites run that package, from framework fingerprints, public source code or exposed files.
  3. They test the published technique against those sites.
  4. Where the flaw is reachable, they use it, often automatically and at scale.

Not every vulnerable package is exploitable in your app, since the vulnerable function may never be called. But proving that takes work. Upgrading is usually cheaper than proving you're safe.

How to fix it

Upgrade to a fixed version, keep a lockfile, and make updates routine.

{  "dependencies": {    "express": "^4.21.2",    "lodash": "^4.17.21",    "jsonwebtoken": "^9.0.2"  }}

Why this works: fixed versions contain the patch for the advisory. A committed lockfile records exactly what's installed, so what you checked is what you ship.

Before you upgrade:

  • Read the changelog for major versions. jsonwebtoken 9, for instance, changed defaults that some apps depend on. Run your tests.
  • Fix transitive versions too. npm audit fix, npm update <pkg>, or an overrides entry (npm) or resolutions (Yarn) can pull a fixed version of a nested package.
  • Commit your lockfile (package-lock.json, poetry.lock, go.sum, Gemfile.lock and so on).
  • Automate updates with Dependabot or Renovate, so fixes arrive as small pull requests.
  • Upgrade end-of-life runtimes. An unsupported runtime gets no security fixes at all.
  • Remove what you don't use. Every dependency is attack surface.

Common mistakes

  • Upgrading the direct dependency only. If the vulnerable package is nested, check that the lockfile actually changed for it.
  • Deleting the lockfile to "fix" audits. That swaps a known set of versions for an unknown one.
  • Ignoring dev dependencies entirely. Most don't ship, but build tools run with your credentials in CI. Review advisories for them too.
  • Auto-merging major upgrades. Patch and minor updates can usually be merged quickly; major versions deserve a test run and a changelog read.

Testing your fix

After upgrading, reinstall from the lockfile in a clean checkout (npm ci, pip install -r with a compiled lockfile, go mod download) and run your tests. Then confirm the installed version: npm ls lodash, pip show django or go list -m all. The vulnerable version should no longer appear anywhere in the tree.

How SetRepo detects it

Dependency checks run on every Quick and Deep scan.

  • Known vulnerabilities: SetRepo reads your manifests and lockfiles and checks every package version against the OSV vulnerability database. It covers npm, PyPI, Go, Maven and Gradle, Composer, RubyGems, crates.io and NuGet. Direct and transitive dependencies are both checked.
  • Manifests without a lockfile: version ranges are resolved to what a fresh install would pick. Only package names and versions are sent for that lookup, through an allowlisted proxy; your source code never leaves the scan.
  • End-of-life runtimes: runtimes that no longer receive security updates.

Each finding names the package, the installed version, the advisory and the first fixed version, so the fix is usually a one-line upgrade. These are facts from the advisory database, so they're reported with high confidence.

Checklist

  • A lockfile is committed for every package manager you use.
  • No dependency has an open advisory with a fixed version available.
  • Transitive dependencies are updated or overridden when needed.
  • Automated dependency updates are enabled.
  • Major upgrades go through your test suite.
  • The runtime (Node.js, Python, Go and so on) is a supported version.
  • Unused dependencies are removed.

FAQ

The vulnerable function isn't used in my app. Do I still need to upgrade?

Possibly not urgently, but it's hard to be sure, and code changes over time. Upgrading is usually faster than proving the flaw can't be reached.

Why does a package I never installed show up?

It's a transitive dependency: something you installed depends on it. Update the parent, or override the nested version.

Is npm audit enough?

It's a good start for npm. Projects often mix ecosystems, though (a Python API, a Go worker, a Dockerfile), so checking all of them in one place catches what single-ecosystem tools miss.

How often should I update dependencies?

Small and often is easiest: let a bot open update pull requests weekly, merge patch and minor updates after tests pass, and schedule major upgrades. Security advisories should be handled as they arrive.

References

Check your app for this, free.

Start free scan