Your Dependencies Are Someone Else's Keys to Your Server

The Axios supply chain attack reminded me of a dependency audit I ran at a client last year. What I found was worse than any vulnerability scanner could flag.


A week ago, Axios — the HTTP client installed in roughly half the Node.js projects on the planet — shipped two versions containing a fully functional remote access trojan. A compromised maintainer account, a staged malicious dependency, and 39 minutes later both the 0.x and 1.x release branches were shipping malware to CI pipelines, developer laptops, and production servers worldwide.

This wasn't a theoretical risk. It was a state-sponsored attack attributed to a North Korean threat group. And it hit one of the most downloaded packages on npm, with over 83 million weekly downloads.

I'd like to say I was shocked. I wasn't. Because six months ago I ran a dependency audit at a client that made me realize most teams have no idea what's actually running in their node_modules.

The Audit Nobody Asked For

The client was a mid-size fintech company, about 25 engineers, running a fairly standard stack: React frontend, NestJS backend, PostgreSQL. They'd hired me to help with performance issues in their API layer. While profiling request latency, I noticed something odd — a transitive dependency was making DNS lookups that didn't match any of their infrastructure.

Turned out to be benign. An analytics library phoning home for update checks. But it made me curious, so I asked if I could spend a day auditing their dependency tree. The tech lead shrugged and said sure.

Here's what I found in their main backend service alone:

  • 847 transitive dependencies from 43 direct ones
  • 12 packages with no commits in over two years
  • 3 packages maintained by a single person with no organizational backing
  • 1 package whose npm publish token was stored in a public GitHub Actions workflow (I reported this)
  • Their lockfile hadn't been regenerated in 14 months. npm audit reported 67 vulnerabilities, 4 critical

None of this was unusual. That's the scary part. This is what a typical Node.js project looks like if nobody's actively watching.

The 278-Day Gap

A recent report found that the median dependency now trails its latest major version by 278 days — up from 215 days the year before. We're falling further behind, not catching up.

I get why. Updating dependencies is thankless work. Nothing visibly improves when you bump a patch version. Nobody gets promoted for keeping the lockfile fresh. And every major version bump carries the risk of breaking something, which means writing migration code, running the full test suite, and praying that one flaky integration test doesn't block the release.

So teams don't do it. They pin versions, ignore Dependabot PRs, and move on to feature work. The dependency tree becomes a slowly rotting foundation that everyone walks on but nobody inspects.

Warning

If you're pinning dependency versions to avoid breaking changes, you're also pinning yourself to every unpatched vulnerability in those versions. Pick your risk.

What Actually Helps

After the audit, I worked with the fintech team to set up a few things that didn't require heroic effort. No dedicated security team, no enterprise tooling budget. Just some reasonable defaults.

Lock everything to exact versions and review diffs on update. This sounds basic, but their package.json was full of ^ ranges. A supply chain attack like the Axios one works precisely because ^1.14.0 silently resolves to 1.14.1 on the next install. Exact versions don't prevent attacks, but they give you a window where your existing deployments are safe and you can evaluate before adopting a new version.

Run npm audit in CI and fail the build on critical severity. They'd never done this. It took one line in their pipeline config. Yes, you'll get false positives. Yes, you'll occasionally need to add exceptions. That's still better than finding out about a critical vulnerability from a security researcher's tweet.

Actually read Dependabot PRs. Not all of them. But the security ones, at minimum. The team had 140+ open Dependabot PRs. We triaged them in an afternoon, merged the security patches, and closed the noise. Then we set up a weekly rotation where one engineer spends 30 minutes reviewing new ones.

Audit your dependency tree quarterly. Not with a tool — with human eyes. Run npm ls --all, look at what you're pulling in, and ask: do we actually need this? The fintech team discovered they had three different date-formatting libraries because different engineers had different preferences. We consolidated to one and removed 23 transitive dependencies.

The Deeper Problem

The Axios incident will fade from the news cycle in another week. Teams will patch, security vendors will update their blog posts, and we'll go back to npm install-ing packages from strangers on the internet without a second thought.

I don't think the answer is to stop using open source. That ship sailed decades ago, and open source has given us more than it's taken. But I think we've gotten too comfortable with the idea that dependencies are free. They're not. Every package you add is a trust relationship with its maintainers, their security practices, and the entire tree of their dependencies.

The fintech client's CTO told me something that stuck: "We do quarterly security reviews of our own code, but we've never once reviewed the code we import." They were spending time and money auditing the 20% of their codebase they wrote themselves, while completely ignoring the 80% they downloaded.

That ratio feels off. Maybe the question isn't whether your code is secure, but whether you even know what code you're running.