According to Sonatype's 2024 report, over 512,000 malicious packages were detected across registries in 2024 - a 156% increase year over year. Every npm install is a loan against your future. The interest comes due when maintainers walk away, vulnerabilities surface at 2 AM, or your build breaks because someone deleted 11 lines.
Audit every dependency: last update, maintainer count, security issues, transitive deps. Each dependency is debt. Budget for maintenance or remove it.
I remember when adding a dependency was a big decision. You evaluated the library, read the source, considered whether you could maintain it yourself if the author disappeared. I've watched this discipline erode over decades, and the consequences are predictable. Today, developers run package installers like they're free. They're not.
The Left-Pad Lesson We Didn't Learn
In March 2016, a developer named Azer Koculu removed 273 packages from npm after a dispute with Kik Interactive over the "kik" package name. Among those packages was left-pad. Just 11 lines of code.
According to documentation of the incident, the fallout was immediate. Babel, React, and thousands of other projects broke. Facebook, Netflix, PayPal, and Spotify all depended on left-pad through their dependency trees. An 11-line function had become a critical point of failure for the JavaScript ecosystem.
npm responded by preventing package removal after 24 hours. But that treated the symptom, not the disease. We've normalized depending on external code for trivial functionality.
When Dependencies Attack
Left-pad was accidental. What happened with colors.js and faker.js in January 2022 was intentional.
As BleepingComputer reported, Marak Squires, the maintainer of these two popular packages - colors.js with over 3.3 billion downloads and faker.js with 272 million - deliberately sabotaged his own code. He pushed versions that printed infinite loops of gibberish to the console.
Squires had warned this was coming. In November 2020, he posted that he would no longer support large companies with his "free work." Corporations should either fork his projects or pay him a six-figure salary. Nobody listened.
GitHub suspended his account. npm reverted the malicious versions. But the message was clear: your application stack depends on maintainers who may be burned out, bitter, or broke. As I've written about before, open source isn't free.
Log4j: The Debt Comes Due
If left-pad and colors.js were warnings, Log4j was the catastrophe.
In December 2021, a critical vulnerability was discovered in Log4j, a logging library used by millions of Java applications. CVE-2021-44228 received the maximum CVSS score of 10. It was trivially exploitable.
The numbers were staggering. Palo Alto Networks observed over 125 million exploitation attempts. Check Point blocked over 4.3 million attempts, with 46% coming from known malicious groups.
But here's what made Log4j different: many organizations didn't even know they were running it. Log4j wasn't in their direct dependencies - it was buried deep in their dependency trees. Companies spent weeks inventorying where the vulnerability existed.
The US Cyber Safety Review Board declared Log4j an "endemic vulnerability" that would remain in systems for years. As of late 2022, researchers still saw one to two million exploitation attempts daily. This is the layer tax in its most dangerous form.
The Math Nobody Does
According to recent supply chain security research, the average software application has 150 dependencies. 90% of those are indirect dependencies you never explicitly chose. You picked 15 packages. Those packages brought in 135 more.
In 2024 alone, over 512,000 malicious packages were detected across package registries - a 156% increase year over year. The Verizon report found that 30% of breaches now involve a third party.
Here's the uncomfortable reality: 35% of supply chain attacks target compromised software dependencies. Every package you add expands your attack surface. Every transitive dependency is a door you didn't know you opened.
We Used to Write Code
I've been programming since the late 1970s. For most of that time, if you needed a function, you wrote it. Padding a string? Write it. Parsing a date? Write it.
This wasn't heroism or masochism - it was the default. You understood your codebase because you wrote your codebase. When something broke, you knew where to look.
The shift to "npm install everything" didn't come from necessity. It came from a culture that celebrates shipping fast over understanding deeply. It came from the lie that reinventing the wheel is always wrong.
Sometimes reinventing the wheel is exactly right. A wheel you built is a wheel you understand. A wheel from npm might have 50 dependencies of its own.
Evaluating the Trade-Off
I'm not suggesting we return to writing everything from scratch. Some dependencies earn their place. But each one should clear a bar:
- Is the functionality non-trivial? If you could write it in an hour, consider writing it. Left-pad was 11 lines. You don't need a package for 11 lines.
- Is the package actively maintained? Check the commit history. Check the issue backlog. If the last commit was two years ago and there are 200 open issues, you're adopting abandoned software.
- What's the dependency tree? Run npm ls or pip show. If your "simple" package pulls in 50 transitive dependencies, reconsider whether it's worth it.
- What's the bus factor? Is this a one-person project? What happens if that person gets a job that prohibits open source work? What happens if they get burned out and go hostile?
- Could you maintain a fork? If the project dies tomorrow, could your team take over? If not, you're betting your application on someone else's continued goodwill.
Most packages fail these tests. Most packages should never have been installed.
Dependency Risk Assessment
Score a package before adding it to your project:
The Lock File Illusion
Lock files don't solve the dependency problem - they just defer it. Yes, your builds are reproducible. But you're still running code you don't understand, written by people you don't know.
I've watched teams treat lock files as security blankets. "We have a lock file, so we're safe." Then a CVE drops and they discover they're running a vulnerable version of a package they didn't know they had. Nobody reviews the transitive dependency changes.
The lock file reproduced your bugs perfectly. Congratulations.
What Actually Works
Organizations that handle dependencies well share common practices:
- Minimize dependencies by default. The question isn't "why not add this package?" It's "why is this package necessary?"
- Write simple utilities yourself. String manipulation, date formatting, shallow cloning - these aren't worth external dependencies. Sometimes the best code is code that was deleted - or never added in the first place.
- Audit the dependency tree. Know what you're actually running. Tools like npm audit, pip-audit, and cargo audit help, but they're not comprehensive.
- Vendor critical dependencies. For packages that are truly essential, consider pulling the source into your repository. You own it now - and that ownership is the point.
- Budget for maintenance. If you have 500 dependencies, you need engineering time to keep them updated. Budget it explicitly or watch your technical debt rot into a security incident.
None of this is easy. All of it is necessary.
When Dependencies Make Sense
I'm not advocating for writing everything from scratch. Some dependencies genuinely pay off:
- Cryptography libraries. Never roll your own crypto. The cost of getting it wrong is catastrophic. Use OpenSSL, libsodium, or your language's standard crypto library.
- Mature, battle-tested packages. Libraries like lodash, requests, or Joda-Time have millions of users, years of hardening, and active maintenance. The debt-to-value ratio is favorable.
- Complex protocols. HTTP clients, database drivers, OAuth implementations - these are spec-heavy and bug-prone. Use established libraries.
- Rapid prototyping. When validating an idea, speed matters more than long-term maintenance. Depend freely, then pay down the debt if the idea survives.
The question isn't whether to use dependencies at all. It's whether each specific dependency earns its place on your balance sheet.
Dependency Risk Scorecard
Before adding any dependency, score it. A failing score means write it yourself or find an alternative.
| Dimension | ๐ข Low Risk (0) | ๐ก Medium Risk (1) | ๐ด High Risk (2) |
|---|---|---|---|
| Complexity | Can't write in 1 hour | Could write in 1-8 hours | Under 100 lines / trivial |
| Maintenance | Active commits, multiple maintainers | Occasional updates, 1-2 maintainers | No commits in 12+ months |
| Dependency Tree | 0-5 transitive deps | 5-20 transitive deps | 20+ transitive deps |
| Bus Factor | Organization-backed | Active community, 3+ contributors | Single maintainer |
| Fork Viability | Could maintain fork easily | Could maintain with effort | Too complex to maintain |
The Bottom Line
Every dependency is a liability on your balance sheet. You're borrowing functionality from maintainers who don't work for you and might disappear tomorrow. The interest rate is measured in CVEs, breaking changes, and 3 AM incident calls.
Before you run that package install, ask yourself: do I need this? Could I write it myself? What happens when this breaks? The answers usually point toward fewer dependencies.
The left-pad incident was 2016. Colors.js was 2022. Log4j was 2021. The next one is coming. Will it find you with 50 dependencies or 500?
"Every dependency is a liability on your balance sheet. You're borrowing functionality from maintainers who don't work for you and might disappear tomorrow."
Sources
- Wikipedia: npm left-pad incident โ Comprehensive overview of the 2016 incident that broke thousands of JavaScript projects
- BleepingComputer: Colors.js and Faker.js Sabotage โ Detailed reporting on the 2022 intentional corruption of popular npm packages
- Sonatype: 2024 State of the Software Supply Chain โ Industry report documenting 512,000+ malicious packages and supply chain attack trends
Architecture Review
Worried about your dependency sprawl? Get perspective on which packages are worth the risk and which are ticking time bombs.
Get Assessment