RE09 All articles
Engineering Culture

Package.json Is Lying to You: The Silent Debt Hiding in Your Dependencies

RE09

You wrote the feature in an afternoon. You shipped it. You moved on. But somewhere in that package.json file, a dependency you added eighteen months ago is quietly running outdated code with three unpatched CVEs — and none of your dashboards are screaming about it.

This is dependency debt. And unlike technical debt, it doesn't just slow you down. It can take the whole project down with it.

The Set-It-and-Forget-It Trap

Most developers treat their dependency list the way they treat their gym membership — full of optimism at the start, mostly ignored after week two. You pull in a package to handle date formatting, another to parse CSV files, maybe a helper library someone on Twitter swore by. Each one feels like a shortcut. And in the moment, it is.

The problem isn't the packages themselves. It's the assumption that they stay safe indefinitely. They don't. Maintainers move on, vulnerabilities get discovered, and the transitive dependencies underneath your dependencies — the ones you never consciously chose — quietly accumulate their own baggage.

Run npm audit on any project older than a year and brace yourself. The output isn't just a list of warnings. It's a history of every shortcut you took and forgot about.

Transitive Risk Is Where It Gets Ugly

Here's the part developers tend to underestimate: you're not just responsible for the packages you directly install. You're on the hook for everything they pull in too.

That utility library with 40 weekly downloads? It depends on a logging package that depends on a string parser that hasn't been touched since 2019. Nobody owns that code anymore. Nobody is watching for vulnerabilities. But it's running in your production environment, probably with access to environment variables or filesystem paths you wouldn't want a stranger near.

The 2021 Log4Shell incident didn't just shake Java developers — it sent a shockwave through every ecosystem. The lesson wasn't "Java is bad." It was that the blast radius of a single vulnerable transitive dependency can be catastrophic, and most teams had no idea the package was even there.

The Early Adopter Response: Audit Aggressively, Cut Ruthlessly

The developers who are getting ahead of this aren't waiting for a breach to force the conversation. They're treating dependency hygiene as a shipping discipline, not a security afterthought.

A few patterns are emerging in the community:

Regular culling sessions. Some teams now schedule quarterly dependency reviews the same way they schedule sprint planning. The question isn't just "is this package vulnerable?" but "do we still actually need this package?" Often the answer is no. A utility you imported to solve a problem in version one might be entirely replaceable with a five-line native function in version three.

Preferring zero-dependency packages. When you're evaluating new libraries, the number of direct dependencies it brings with it is becoming a real selection criterion. A zero-dependency package that does one thing well is a much smaller surface area than a full-featured toolkit with 200 transitive packages underneath it.

Pinning versions intentionally. Floating version ranges like ^1.4.0 feel flexible, but they also mean your build can silently pull in a minor version update that introduces a regression or a newly disclosed vulnerability. More teams are moving toward explicit version pins and treating dependency upgrades as deliberate decisions rather than passive drift.

Using tools that actually surface the full tree. npm audit is a starting point, not a finish line. Tools like Snyk, Socket, and Dependabot give you a more complete picture of what's lurking in your transitive graph — and they can integrate into CI so the conversation happens before code ships, not after.

Velocity Isn't Just About Adding Things

There's a mindset shift buried in all of this that's worth calling out directly. The instinct in most dev cultures is to equate speed with addition — more tools, more packages, more abstractions. But the teams shipping fastest right now are often the ones who've figured out that subtraction is underrated.

Every dependency you remove is one less thing to update, one less attack vector, one less reason your build might break on a Tuesday afternoon when you're trying to push a hotfix. The engineers who've internalized this aren't anti-library zealots. They're just doing the math.

A leaner dependency tree means faster installs, smaller bundles, less cognitive overhead when onboarding new contributors, and a dramatically reduced surface area for supply chain attacks. None of those benefits show up in your feature roadmap, but all of them compound over time.

The Supply Chain Attack Problem Is Real and Growing

If you think supply chain attacks are an enterprise problem, you haven't been paying attention. The 2022 colors and faker incident — where a maintainer deliberately sabotaged their own popular packages — affected thousands of projects overnight. The event-stream backdoor in 2018 targeted a specific Bitcoin wallet and got through because the package had millions of weekly downloads and almost nobody was watching.

Attackers have figured out that the easiest way into a well-defended application isn't through the front door. It's through a dependency that the team stopped thinking about two years ago.

The US government's Cybersecurity and Infrastructure Security Agency (CISA) has explicitly flagged software supply chain security as a national priority. That's not just bureaucratic noise — it's a signal that the threat model has shifted, and teams that aren't thinking about their dependency graph as part of their security posture are behind the curve.

Where to Start if You're Already Behind

If your package.json is already a sprawl, the good news is you don't have to fix it all at once. Start with the basics:

  1. Run a full audit today. Use npm audit --json or the equivalent for your ecosystem and actually read the output. Know what's critical versus informational.
  2. Identify unused dependencies. Tools like depcheck will surface packages you're importing but not actually using. Those are free wins — remove them without touching anything else.
  3. Set up automated alerts. Dependabot, Renovate, or Snyk on your repo means you're not flying blind between manual reviews.
  4. Document why each major dependency exists. A short comment in your package file or a lightweight ADR explaining why you chose a library creates accountability and makes future pruning decisions much easier.

None of this is glamorous work. You won't ship a feature at the end of it. But you'll reduce your blast radius, move faster on future builds, and sleep slightly better knowing your production environment isn't quietly waiting for someone else's abandoned code to become your emergency.

Dependency debt compounds just like financial debt. The longer you ignore it, the more expensive it gets to fix. The difference is that unlike your credit card, this one can take your users down with you.

All Articles

Related Articles

Out of Sight, Out of Mind: The Silent Collapse Happening Inside Your Job Queues

Out of Sight, Out of Mind: The Silent Collapse Happening Inside Your Job Queues

47 Repos, Zero Users: The Side Project Graveyard Most Developers Won't Talk About

47 Repos, Zero Users: The Side Project Graveyard Most Developers Won't Talk About

The Stand-Up That Ate the Fix: How Debugging Became a Spectator Sport

The Stand-Up That Ate the Fix: How Debugging Became a Spectator Sport