Ubin.io All Articles
Infrastructure & DevOps

Your Third-Party Code Is a Security Audit Waiting to Happen

By Ubin.io Infrastructure & DevOps
Your Third-Party Code Is a Security Audit Waiting to Happen

Let's be honest about something: when you run npm install or pip install or go get, you're not just pulling in a library. You're extending trust to a package maintainer you've probably never heard of, their dependencies, their dependencies' dependencies, and a chain of humans making commit decisions that could reach your production environment within hours.

For most teams, this trust is implicit and unexamined. That's how you end up in situations like the [event-stream](https://en.wikipedia.org/wiki/List_of_free_and_open-source_software_packages) incident, where a malicious actor took over a popular npm package and injected code targeting cryptocurrency wallets — code that lived inside the dependency trees of thousands of projects before anyone noticed.

Supply chain security isn't a niche concern anymore. It's a mainstream risk, and auditing your dependencies is no longer optional if you're building anything that handles real user data or sits inside a regulated industry.

Why Transitive Dependencies Are the Real Problem

Your direct dependencies are the ones you consciously chose. You evaluated them, read the docs, maybe checked the GitHub stars. You have at least some awareness of what they do.

Your transitive dependencies — the packages your packages depend on — are a different story. A modern JavaScript project can have dozens of direct dependencies and hundreds, sometimes thousands, of transitive ones. The average React application installs over 1,500 packages. You didn't choose most of them. You don't know most of them. And any one of them could be the weak link.

The math on this is uncomfortable. If each package in your tree has a 0.1% chance of having a critical vulnerability, and you have 500 transitive dependencies, your exposure is not 0.1%. It's a near-certainty that at least one package in your tree has a known issue at any given time.

Step One: Actually Map Your Dependency Tree

Before you can manage risk, you need visibility. Most developers have a vague sense of their direct dependencies but zero visibility into what's underneath.

For JavaScript projects, npm ls --all gives you the full tree, though the output is dense. npx license-checker and npx depcheck are more actionable for getting a summary of what you've got and what you're not actually using. For Python, pip-tree or pipdeptree surfaces the full transitive graph. For Go, go mod graph does the same.

The goal of this exercise isn't to read every line — it's to understand the shape of your exposure. How deep does your tree go? Are there packages with a single maintainer? Any packages that haven't had a commit in two years? These are the signals that warrant closer attention.

Automated Scanning: Your First Line of Defense

Manual audits don't scale. The dependency landscape changes constantly — new vulnerabilities get disclosed daily, and a package that was clean last month might have a CVE today. You need automation.

Several tools make this accessible:

Dependabot (built into GitHub) automatically opens pull requests when it detects a dependency with a known vulnerability. It's not perfect — it can generate noise and doesn't handle all ecosystems equally — but it's free, it's automatic, and it catches a lot of low-hanging fruit.

Snyk goes deeper, scanning not just for known CVEs but also for misconfiguration issues and providing remediation advice. Their free tier is genuinely useful for smaller teams. The paid tiers add container scanning and infrastructure-as-code analysis.

OWASP Dependency-Check is the open-source option if you want to keep things self-hosted. It integrates with most CI systems and generates reports against the National Vulnerability Database.

Socket.dev takes a different angle — instead of just matching against known CVE databases, it analyzes package behavior and flags suspicious patterns like newly added network calls or install scripts that run arbitrary code. It's particularly good at catching supply chain attacks that haven't yet been formally reported as vulnerabilities.

The right answer for most teams is layering these: Dependabot for automatic PR-level alerts, plus a deeper scanner like Snyk or Socket integrated into your CI pipeline so a build fails if you introduce a high-severity vulnerability.

Licensing: The Compliance Risk Nobody Talks About

Security vulnerabilities get the headlines, but licensing issues are what keep legal teams up at night.

Not all open source licenses are created equal. MIT and Apache 2.0 are permissive — use them freely, modify them, ship them in commercial products. GPL and AGPL are copyleft — if you distribute software that includes GPL code, you may be required to open-source your own code under the same terms. LGPL has its own nuances. And some packages on npm have essentially no license, which creates its own legal ambiguity.

If you're building a commercial product, you need to know whether any of your transitive dependencies are pulling in copyleft code. license-checker for Node, pip-licenses for Python, and golicense for Go can generate license reports that your legal team can actually review.

Do this audit before a big customer deal or a fundraising round. Legal due diligence will surface it, and finding a GPL violation in your dependency tree at that moment is a bad time.

The Decision Matrix: Fork, Patch, or Replace

Not every dependency problem requires the same response. Here's a practical framework for triaging:

Replace when: the package is abandoned (no commits in 18+ months, open issues with no response), has a critical vulnerability with no patch available, or has a licensing issue that can't be resolved. Replacement is the cleanest outcome and should be your default for packages that aren't deeply integrated.

Patch when: the vulnerability is known, a fix exists upstream but hasn't been released, and you need to move faster than the maintainer's release cycle. Most package managers support patching via patch-package (npm) or similar mechanisms. Document your patches carefully — they're tech debt that needs to be cleaned up when the upstream fix ships.

Fork when: the package is critical to your system, the maintainer is unresponsive, and you need long-term control. Forking is expensive — you're now responsible for maintaining that code — but sometimes it's the right call. If you fork, commit to a maintenance plan or you've just traded one abandoned dependency for another.

Accept the risk when: the vulnerability isn't exploitable in your specific usage context, the CVSS score is low, and replacement cost is high. This is a legitimate option, but it needs to be a documented, deliberate decision — not a default. Track accepted risks in a software bill of materials (SBOM) so they're reviewed periodically.

Building a Software Bill of Materials

Speaking of SBOMs: if you're working in any federally adjacent space, defense, healthcare, or financial services, you may already be required to produce one. Executive Order 14028, signed in 2021, mandated SBOM requirements for software sold to the federal government. The private sector is following.

An SBOM is essentially a machine-readable inventory of every component in your software — direct and transitive dependencies, their versions, their licenses, their known vulnerabilities. Tools like Syft and CycloneDX can generate SBOMs automatically from your codebase or container images.

Even if you're not under regulatory pressure yet, building SBOM generation into your release pipeline now is a low-cost investment that pays dividends when compliance requirements inevitably arrive.

Making This a Habit, Not a Fire Drill

The teams that handle supply chain risk well don't do it through heroic one-time audits. They build it into the development workflow so it's automatic and continuous.

Block high-severity vulnerabilities in CI. Review license reports as part of new dependency additions. Schedule a quarterly dependency health check — not a full audit, just a review of anything flagged by your automated tools that hasn't been addressed. Keep your dependency count as low as reasonably possible; every package you don't install is a risk you don't carry.

Your supply chain is only as strong as the weakest package in your tree. That's worth taking seriously.