Your Dependencies Are Your Attack Surface
By Domenic DiNatale
Your application probably has hundreds of dependencies. You wrote maybe 5% of the code running in production.
That's not an indictment — it's a description of modern software development. Nobody writes their own cryptography library or HTTP framework from scratch, and they shouldn't. The ecosystem of open source software that underpins essentially all modern applications is one of the most productive forces in the history of software engineering. The problem isn't that you depend on code you didn't write. The problem is that most organizations have no coherent model for what it means to trust that code, and treat dependency management as a logistics problem rather than a security problem.
Log4Shell in December 2021 was a demonstration. The Log4j library — a logging utility so ubiquitous that it appeared in systems most organizations didn't even know they had — contained a remote code execution vulnerability that had been sitting quietly in production for years. The response required organizations to inventory every system that might contain Log4j. Many couldn't do that inventory, because they had no map of what they were actually running.
XZ Utils in 2024 was something more unsettling. A sophisticated, patient attacker spent two years cultivating trust within an open source project before inserting a backdoor into a compression library that had deep system-level access on Linux distributions. It was caught almost by accident — a Microsoft engineer noticed unexpected latency in SSH connections. The compromise was nearly complete before anyone knew it was happening.
SolarWinds was earlier and more familiar, but the lesson is the same: the build pipeline is an attack surface, trusted software updates are a delivery mechanism, and the organizations that were compromised weren't negligent by conventional measures. They updated their software. That was the attack.
Trust Without Verification
The architectural failure in supply chain attacks isn't a missing control. It's a missing model. Most organizations extend implicit, unconditional trust to their dependencies. Once a package is approved and pulled into the codebase, it runs with the same privileges as code that was carefully reviewed and tested. The fact that it came from an external source, was written by developers you've never met, and may have changed since you last looked — none of that is typically reflected in how the system treats it.
This is rationalized by pointing to the impracticality of auditing every dependency. A modern Node.js application might have 1,000 transitive dependencies. A Java application might have hundreds. You can't read all that code. But "I can't fully verify it" is not the same as "I should trust it unconditionally." There's a spectrum between those positions, and most organizations don't think carefully about where on that spectrum they want to be.
The same implicit trust extends to the build pipeline. CI/CD systems pull dependencies at build time, often from package registries with minimal integrity guarantees. Build scripts run with elevated permissions. Artifacts are signed — sometimes. The supply chain from source code to running system contains multiple points where compromise is possible, and many of them receive no meaningful scrutiny.
This Is an Architecture Problem
Treating supply chain risk as a patching problem is the wrong frame. Patching addresses known vulnerabilities after they're disclosed. Supply chain attacks exploit trusted dependencies that haven't been flagged as vulnerable — and by the time they are, the damage may already be done.
The architectural questions are different. Where do your dependencies come from, and what guarantees their integrity? What are the runtime permissions of third-party code? If a dependency is compromised, what can it reach? How would you know if a dependency's behavior changed between releases?
Integrity verification is the first layer. Pinning dependency versions and verifying checksums prevents a category of attacks where a package registry is compromised and a malicious version is substituted. It doesn't prevent a legitimate maintainer from inserting malware — but it does prevent the simpler supply chain substitution attacks.
Runtime isolation is the second, and harder, layer. If your application can't separate what first-party code is allowed to do from what third-party code is allowed to do, a compromised dependency has all the same access as trusted code. Sandboxing third-party dependencies is difficult in most application runtimes, but the principle — limit what untrusted code can reach — is achievable in varying degrees depending on how the application is structured.
Visibility is the third layer. You can't respond to a supply chain compromise you don't know you have. Maintaining a software bill of materials (SBOM) isn't exciting, but it's the difference between a two-hour inventory and a two-week one when a vulnerability disclosure arrives.
The Deeper Problem
Supply chain security runs into a fundamental tension that makes it genuinely hard. The open source ecosystem works because of trust — trust that maintainers are acting in good faith, trust that community review catches problems, trust that widely-used libraries are safe because they're widely-used. Most of the time, that trust is warranted. The ecosystem is remarkably reliable given its scale and how it's funded.
But the adversarial actors who run supply chain attacks know that trust exists and target it deliberately. XZ Utils demonstrated a level of patience and sophistication that most security models don't account for — an attacker willing to invest years into compromising a single dependency because that dependency would give them access to millions of systems.
The response isn't to stop trusting the open source ecosystem — it's to trust it in a more sophisticated way. Verify integrity. Constrain runtime permissions. Maintain visibility. Treat changes in well-established dependencies as events worth examining, not just accepting.
Your attack surface is not just the code you wrote. It's everything your code runs on, runs with, and trusts. For most organizations, that's a surface area they've never fully mapped.
This post is part of a series on security as an architectural problem. Read the full series on the Intellitech blog.