DEV Community

Pat
Pat

Posted on

Your package.json Is Lying to You

Your package.json has 47 dependencies. When was the last time you checked if any of them were abandoned?

If you're like most developers, the answer is never. You npm install something, it works, and you move on with your life. Maybe you run npm audit once in a while when GitHub sends you that annoying Dependabot email. But that's it.

I used to be the same way. Then I got burned.

The graveyard in your node_modules

Let's talk about some famous examples.

event-stream (2018) — A popular utility package with millions of weekly downloads. The original maintainer got tired of it and handed ownership to a stranger who asked nicely. That stranger injected a targeted attack to steal cryptocurrency from Copay wallet users. The malicious code sat in the package for two months before anyone noticed.

colors and faker (2022) — The maintainer intentionally broke his own packages, pushing an update that printed an infinite loop of garbage text. He was protesting open-source exploitation. Thousands of projects broke overnight.

left-pad (2016) — One developer unpublished an 11-line package and broke the entire npm ecosystem for a few hours. React, Babel, and thousands of other packages couldn't build.

These aren't edge cases. These are warnings. And the common thread is that nobody was watching the health of these dependencies before things went sideways.

What "healthy" actually means

npm audit checks for known CVEs. That's necessary but not sufficient. A dependency can have zero reported vulnerabilities and still be a ticking time bomb.

Here's what actually matters:

Maintenance activity — When was the last commit? The last npm publish? A package that hasn't been touched in 3 years isn't "stable." It's abandoned. There's a difference between "done" (like is-number) and "dead" (like request).

Download trends — Is usage growing, flat, or declining? A declining package means the community is migrating away from it. You should probably know where they're going.

Bundle sizemoment is 4.2 MB unpacked. dayjs does the same thing in 7 KB. If you're shipping a web app, this stuff adds up fast.

Known vulnerabilities — Yes, check npm audit. But also check if the vulnerabilities are actually getting patched.

Community signals — How many open issues? How many PRs sitting with no response? A repo with 200 open issues and no maintainer response in 6 months is a red flag.

How to check manually

You don't need any special tools for this. The npm registry API is public.

Check when a package was last published

npm view moment time --json | jq '.modified'
# "2022-04-07T01:25:37.007Z"  <-- over 3 years ago
Enter fullscreen mode Exit fullscreen mode

Check weekly downloads

curl -s https://api.npmjs.org/downloads/point/last-week/moment | jq '.downloads'
# 12847392
Enter fullscreen mode Exit fullscreen mode

Still getting 12M downloads a week, but that number has been declining steadily. Legacy projects pulling it in, not new adoption.

Compare download trends

# moment vs dayjs over the last month
curl -s "https://api.npmjs.org/downloads/range/last-month/moment" | jq '.downloads[-1].downloads'
curl -s "https://api.npmjs.org/downloads/range/last-month/dayjs" | jq '.downloads[-1].downloads'
Enter fullscreen mode Exit fullscreen mode

Quick vulnerability check

npm audit --json | jq '.vulnerabilities | to_entries[] | {name: .key, severity: .value.severity}'
Enter fullscreen mode Exit fullscreen mode

Check package size

npm view moment dist.unpackedSize
# 4226096  (that's ~4.2 MB)
Enter fullscreen mode Exit fullscreen mode

Or use bundlephobia.com for a nicer view of what actually ends up in your bundle.

The poor man's dependency audit

Here's a quick script you can run right now against any project:

#!/bin/bash
# quick-audit.sh — scan package.json for stale deps
for pkg in $(jq -r '.dependencies | keys[]' package.json); do
  modified=$(npm view "$pkg" time.modified 2>/dev/null)
  downloads=$(curl -s "https://api.npmjs.org/downloads/point/last-week/$pkg" | jq -r '.downloads')
  echo "$pkg | last updated: $modified | weekly downloads: $downloads"
done
Enter fullscreen mode Exit fullscreen mode

This takes a while because it hits the API for every package. But it works. Run it once and you'll probably be surprised by what you find.

The packages you should be worried about

Let me save you some time. If you have any of these in your package.json, it's worth investigating:

Package Status What to use instead
moment In maintenance mode, no new features dayjs or date-fns
request Deprecated since 2020 undici (built into Node 18+) or node-fetch
uuid (v3 or lower) Old, has known issues uuid@9 or crypto.randomUUID()
chalk (if size matters) Heavy for what it does picocolors or built-in ANSI
lodash (full package) 1.4 MB for a few utility functions lodash-es or native methods

None of these will blow up tomorrow. But they're the kind of slow-burn risk that makes upgrades painful later.

Automating the boring stuff

The manual approach works but it's tedious. Checking 40+ dependencies one by one against the registry API is not how I want to spend my Friday afternoon.

I got tired of doing this manually, so I built a CLI called DepScope that runs the whole audit in one command:

npx depscope
Enter fullscreen mode Exit fullscreen mode
  DepScope — Dependency Health Report
  ──────────────────────────────────────────────────────

  Production Dependencies (6)

  A express                       ^4.21.0 → 4.21.0
    ████████████████████░░░░ 85/100  active  28,000,000/wk ↑  209 KB  ✓ secure

  B lodash                        ^4.17.21 → 4.17.21
    ████████████████░░░░░░░░ 68/100  stale   51,000,000/wk →  1.4 MB  ✓ secure

  C moment                        ^2.30.1 → 2.30.1
    ██████████░░░░░░░░░░░░░░ 42/100  abandoned  12,500,000/wk ↓  4.2 MB  ✓ secure
    ↳ Consider: dayjs — 2KB, same API, actively maintained

  F event-stream                  ^4.0.1 → 4.0.1
    ██░░░░░░░░░░░░░░░░░░░░░░ 8/100   abandoned  320,000/wk ↓  45 KB  ✗ 1 vuln (critical)

  ──────────────────────────────────────────────────────

  Project Health: B  ████████████████░░░░░░░░ 64/100
  ⚠ 2 abandoned packages
  ⚠ 1 package with known vulnerabilities
Enter fullscreen mode Exit fullscreen mode

It scores each dependency across maintenance, popularity, size, security, and trend — then gives you a letter grade. Pipe --json to get machine-readable output for CI. Use --fix to get suggested replacements.

But honestly, whether you use DepScope or the bash script above or something else entirely — the point is the same: check your dependencies. Not just when things break. Regularly.

What to do right now

  1. Run npm audit on your biggest project. Read the output instead of ignoring it.
  2. Pick your three least-familiar dependencies and check when they were last published.
  3. If anything hasn't been updated in 2+ years, look for alternatives.
  4. Add a dependency check to your CI pipeline. Even a simple npm audit --audit-level=high is better than nothing.

Your package.json is a list of promises from strangers on the internet. Trust, but verify.


If you found this useful, I write about dependency hygiene, dev tools, and building stuff at @WSDevGuy on Twitter.

Top comments (0)