b8c.io All articles
Engineering Strategy

The Dependency You Didn't Write Is the One That'll Break You

b8c.io
The Dependency You Didn't Write Is the One That'll Break You

Open your package.json. Or your requirements.txt. Or your go.mod. Now think about how many of those entries you've actually read the source code for. Not the README—the actual code.

For most teams, that number is zero. Maybe one or two if something broke in a memorable way.

The modern software supply chain is an act of collective trust in strangers. You're shipping code you didn't write, maintained by people you've never met, pulling in sub-dependencies you've never heard of, all because a blog post said it was the right tool for the job in 2019. That trust isn't always misplaced—open source is genuinely incredible. But the risk profile of dependency sprawl has changed significantly over the last few years, and most teams are still operating like it's 2015.

The Abandoned Package Problem Nobody Talks About Loudly Enough

NPM alone has millions of packages. A substantial portion of them haven't been touched in years. Some of those packages are stable and don't need updates. Many of them are quietly rotting.

Abandonment looks different from malice, but the risk is similar. An unmaintained package means no security patches when vulnerabilities surface. It means no compatibility updates when Node or Python or Go moves forward. It means the person who understood the edge cases is gone, and if something breaks, you're debugging code you don't own in a repository that hasn't had a commit since before your last two engineers were hired.

The particularly insidious version of this is the package that looks maintained. It has recent commits. But those recent commits are from the new maintainer who acquired the project through a GitHub transfer from the original author, and the new maintainer has a very different set of priorities.

Solar Winds was a wake-up call. So was the event-stream incident on NPM, where a malicious actor added cryptomining code to a widely-used package through a seemingly routine dependency update. These aren't hypotheticals anymore. They're documented, they happened at scale, and they'll happen again.

Version Pinning Feels Like Safety. It Isn't.

The standard advice after a supply chain scare is to pin your dependency versions. Lock the exact version, commit the lockfile, and at least you know what you're running. That advice isn't wrong—but it creates its own problem.

Pinned versions stop you from accidentally pulling in a breaking change. They also stop you from automatically pulling in security patches. You've traded one risk for another. The team that hasn't updated their pinned dependencies in eight months because "if it ain't broke" is sitting on a pile of known vulnerabilities they've just chosen not to look at.

Lockfiles also don't fully protect you from transitive dependency risk. You pin react-query at a specific version. That version depends on a specific version of something else, which depends on something else, and somewhere four layers down is a package you've never heard of that just got a new maintainer. Your lockfile locked the top of the tree. The roots are still moving.

Automated Scanning Gives You a Floor, Not a Ceiling

Dependabot, Snyk, GitHub's security advisories—these tools are genuinely useful and you should absolutely be running them. But they have a ceiling that teams consistently mistake for comprehensive coverage.

Automated scanners work from known vulnerability databases. They catch CVEs that have been reported, catalogued, and assigned an identifier. They don't catch the vulnerability that hasn't been discovered yet. They don't catch the malicious package that was published yesterday and hasn't been flagged. They don't catch the package where the behavior changed in a way that's dangerous in your specific context but not dangerous in general.

A clean Dependabot report means you have no known critical vulnerabilities in catalogued packages. That's a meaningful statement. It's not the same as "your supply chain is secure."

A Framework for Thinking About Dependency Risk Differently

Instead of treating dependency management as a compliance checkbox, try thinking about it in terms of three dimensions:

Criticality. How much of your application's security or data integrity depends on this package? A utility library for string formatting has a different risk profile than a package that handles authentication tokens or processes payments. Your scrutiny should scale with criticality.

Maintenance health. When was the last commit? How many open issues are there? Is there a single maintainer or an active community? Does the maintainer respond to security disclosures? These questions take five minutes to answer and should be part of any dependency evaluation.

Transitive surface area. How many sub-dependencies does this package pull in? A package with 40 transitive dependencies isn't just one package—it's 41 trust decisions made for you. Sometimes that's fine. Sometimes it means you've added significant surface area for a relatively minor convenience.

The Practical Stuff

A few things that actually move the needle:

Regular dependency audits, not just automated scans. Once a quarter, manually review your top-level dependencies. Check maintenance status. Check whether you're still using the package. A surprising number of teams have dependencies in their manifest that nothing actually imports anymore.

Vendor critical dependencies. For packages that are deeply embedded in your security or data processing paths, consider vendoring—keeping a local copy under your control rather than pulling from the registry at build time. More overhead, significantly more control.

Treat major version updates as architecture decisions. A major version bump isn't just a dependency update—it's a new contract. Review the changelog. Understand what changed. Don't let your automated tooling merge it without a human reading what the update actually does.

The supply chain risk conversation often gets framed as a security problem, which it is. But it's also an engineering judgment problem. Every dependency is a decision to trust someone else's code in your production environment. Most teams are making hundreds of those decisions implicitly, automatically, without much thought.

The attack surface didn't get bigger because attackers got smarter. It got bigger because we stopped noticing how many doors we were unlocking.

All Articles

Related Articles

Git Log Is a Crystal Ball: Reading Engineering Health Between the Commits

Git Log Is a Crystal Ball: Reading Engineering Health Between the Commits

The Kill Switch You Never Tested: Why Your Rollback Plan Is a Bedtime Story You Tell Yourself

The Kill Switch You Never Tested: Why Your Rollback Plan Is a Bedtime Story You Tell Yourself

Your Logs Are Lying by Omission: The Context Gap That Turns Debugging Into Archaeology

Your Logs Are Lying by Omission: The Context Gap That Turns Debugging Into Archaeology