Your dependencies are time bombs
Seven security advisories in a single project. flatted, minimatch, tar, next, simple-git, kysely, each one a transitive dependency most developers never chose directly. Your node_modules folder is a trust graph, and you have no idea who's in it.
That realization hit me while running npm audit on a portfolio project. Not a massive enterprise app, just a mid-sized Node.js project with a handful of direct dependencies. Yet somehow I'd inherited hundreds of packages I'd never reviewed, written by people I'd never heard of, maintained on schedules I couldn't predict. Seven advisories stacked up, and most of them traced back to packages buried three or four levels deep in the dependency tree.
This isn't a story about one bad project. It's about a structural problem baked into how the JavaScript ecosystem works.
Your dependency tree is bigger than you think
When you run npm install, you're not just pulling in the packages listed in your package.json. You're pulling in everything those packages depend on, and everything those depend on, recursively. A typical Node.js project can easily contain 300 to over 1,000 packages in its dependency tree. Many developers have seen node_modules folders balloon to hundreds of megabytes.
The result is that for every line of code you wrote, you've inherited thousands of lines you've never read. You might review your own code religiously, run linters, write tests, follow best practices. But the vast majority of the code running in your project was written by strangers, on their own time, with their own priorities.
Each of those packages is a point of trust. And trust, in software, is an attack surface.
Supply chain attacks are the new frontier
This isn't theoretical. The JavaScript ecosystem has been hit by a series of high-profile supply chain attacks that demonstrate exactly how fragile this trust graph is.
event-stream (2018) was the wake-up call. A maintainer handed over control of a popular npm package to a contributor who then injected malicious code targeting a specific Bitcoin wallet application. The attack was surgically targeted and went undetected for weeks because the compromised package was a transitive dependency most users never looked at directly.
ua-parser-js (2021) was even scarier. The package, with tens of millions of weekly downloads, was hijacked when an attacker compromised the maintainer's npm account. Malicious versions were published that installed cryptominers and password stealers. Because ua-parser-js sat deep in the dependency trees of countless projects, the blast radius was enormous.
colors.js and faker.js (2022) showed a different kind of risk entirely. The maintainer of these hugely popular packages, colors.js alone had over 3.3 billion lifetime downloads, deliberately sabotaged them in protest. One morning, thousands of applications that depended on these packages started printing garbage to the console or entering infinite loops. No external attacker needed. The threat came from inside the house.
And then came 2025. The Shai-Hulud worm, named after the sandworms from Dune, was a self-replicating npm supply chain attack that compromised real developer accounts and used stolen npm tokens to infect more packages in a chain reaction. Over 500 packages were hit before the attack was contained. Shortly after, a separate attack compromised widely-used packages like chalk and debug, injecting code that targeted cryptocurrency wallets. These packages collectively had billions of weekly downloads. CISA issued a formal advisory urging organizations to review their entire npm dependency trees.
The pattern is clear: attackers have figured out that compromising one popular package, especially a transitive one, gives them access to thousands of downstream projects for free.
Alert fatigue is a real problem
Tools like Dependabot, npm audit, and GitHub's security advisories are genuinely helpful. But they create their own problem: alert fatigue.
When you run npm audit and see seven advisories, your first instinct might be to fix them. But then you realize most are in transitive dependencies you don't control directly. Some require waiting for upstream maintainers to patch. Others involve vulnerabilities that are technically present but not exploitable in your specific usage. A few might require major version bumps that break other things.
So you start triaging. Some advisories get ignored. Some get suppressed. Over time, the red warnings become background noise. And that's exactly the environment where a real, exploitable vulnerability slips through unnoticed.
The cultural problem: micro-packages and misaligned incentives
The JavaScript ecosystem has a cultural bias toward small, single-purpose packages. This philosophy, "do one thing well," sounds elegant in theory. In practice, it means your project depends on dozens of packages that each do something trivially small. The left-pad incident in 2016 was supposed to be the cautionary tale. A developer unpublished an 11-line package from npm, and it broke builds across the internet, including at Facebook and Airbnb. The ecosystem patched the immediate problem (npm changed its unpublish policies), but the underlying incentive structure never changed. Micro-packages persist because they're convenient. Need to check if a number is odd? There's a package for that. Need to pad a string? Package. These tiny utilities accumulate, and each one adds a node to your trust graph, a maintainer you're implicitly relying on, a potential point of failure. The incentive problem runs deeper than convenience, though. Package authors are rewarded with download counts and visibility. Breaking functionality into smaller packages inflates these metrics. Meanwhile, the costs of additional dependencies, increased attack surface, maintenance burden, audit noise, are externalized to every downstream consumer.
AI coding assistants are making this worse
There's a newer dimension to the dependency problem that deserves attention. AI coding assistants, tools like GitHub Copilot, Cursor, and others, have a tendency to reach for packages you'd never choose yourself. Ask an AI to help you parse dates, and it might pull in a library when a few lines of native code would do. Ask it to handle a string operation, and it suggests a package instead of using built-in methods. The AI optimizes for getting something working quickly, not for minimizing your dependency footprint. Research from Endor Labs found that only about one in five dependency versions recommended by AI coding assistants were safe to use, free from both hallucinated packages and known vulnerabilities. Even more concerning is the phenomenon of "slopsquatting," where AI models hallucinate package names that don't exist, and attackers register those exact names with malicious code, waiting for the next developer (or AI) to install them. The result is that AI-assisted development can silently expand your attack surface. Every unnecessary dependency the AI adds is another entry in your trust graph, another package to audit, another potential vector for compromise.
What you can actually do about it
None of this means you should write everything yourself. Dependencies exist because they solve real problems, and reinventing well-tested libraries is its own source of bugs and security issues. The goal isn't zero dependencies. It's intentional dependencies.
Here's what practical mitigation looks like:
Lock your dependencies. Always commit your package-lock.json or yarn.lock. Lock files ensure that every install reproduces the exact same dependency tree, preventing surprise updates from sneaking in. This is your first line of defense.
Audit regularly, and take it seriously. Run npm audit as part of your CI pipeline, not just locally. Set thresholds for what severity levels block a build. Don't let advisory counts quietly climb.
Adopt a minimal dependency policy. Before adding a package, ask: does this really need to be a dependency? Could I write this in 20 lines? Is this package actively maintained? How deep is its dependency tree? A package with zero dependencies is fundamentally less risky than one with fifty.
Prune what you don't need. Dependencies accumulate over time. Features get removed, but their packages stay in package.json. Periodically review your direct dependencies and remove anything that's no longer used. Tools like depcheck can help identify unused packages.
Review what your AI writes. If you're using AI coding assistants, treat their dependency suggestions with skepticism. Check whether a suggested package actually exists, whether it's maintained, and whether you actually need it. The convenience isn't worth the risk if it's pulling in packages you've never vetted.
Use npm overrides for transitive fixes. When a vulnerability exists in a transitive dependency, you can use npm's overrides field in package.json to force a patched version without waiting for every intermediate package to update.
Monitor the ecosystem. Follow security advisories from sources like CISA, Snyk, and Socket. The npm supply chain attack landscape evolves fast, and awareness is half the battle.
The trust problem isn't going away
Every dependency is an act of trust. Trust that the maintainer won't go rogue. Trust that their npm account won't get compromised. Trust that their dependencies are equally trustworthy. Scale that across hundreds or thousands of packages, and you're not managing code anymore, you're managing risk.
The seven advisories in my project weren't catastrophic. Most were low to moderate severity, and none were actively exploited. But they were a reminder that the code I write is only a fraction of the code I ship. The rest comes from a sprawling, largely invisible network of strangers, each one a potential point of failure.
Your node_modules folder is a trust graph. It's worth knowing what's in it.
References
- Sonatype, "Maintainer Sabotages npm Libraries 'colors' and 'faker'" (2022), https://www.sonatype.com/blog/npm-libraries-colors-and-faker-sabotaged-in-protest-by-their-maintainer-what-to-do-now
- CISA, "Widespread Supply Chain Compromise Impacting npm Ecosystem" (2025), https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem
- Wiz, "Shai-Hulud: Ongoing Package Supply Chain Worm Delivering Data-Stealing Malware" (2025), https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack
- Palo Alto Networks, "Breakdown: Widespread npm Supply Chain Attack Puts Billions of Weekly Downloads at Risk" (2025), https://www.paloaltonetworks.com/blog/cloud-security/npm-supply-chain-attack/
- Endor Labs, "80% of AI-Suggested Dependencies Contain Risks: Study" (2025), https://www.sdcexec.com/safety-security/risk-compliance/news/22954651/endor-labs-80-of-aisuggested-dependencies-contain-risks-study
- Contrast Security, "Slopsquatting: How Attackers Exploit AI-Generated Package Names" (2025), https://www.contrastsecurity.com/security-influencers/slopsquatting-attacks-how-ai-phantom-dependencies-create-security-risks
- Chainguard, "The npm registry can't protect you: The new JavaScript supply chain attacks" (2025), https://www.chainguard.dev/supply-chain-security-101/the-npm-registry-cant-protect-you-the-new-javascript-supply-chain-attacks
- OWASP, "CICD-SEC-3: Dependency Chain Abuse," https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-03-Dependency-Chain-Abuse
- Immersive Labs, "The Hidden Attack Surface in Your Dependencies," https://www.immersivelabs.com/resources/c7-blog/the-hidden-attack-surface-in-your-dependencies
You might also enjoy