Have you ever found humor in the little details of your profession? It's amusing to think that a developer can progress through years of crafting software, stitching together libraries and packages, all the while remaining blissfully unaware of a fundamental truth: with each dependency they happily import or install, they often usher in a parade of other unseen dependencies. It's akin to inviting a friend over for dinner and having them bring their entire extended family.
Transitive dependencies, in the simplest terms, are the dependencies of your dependencies. If you've ever relied on a library or framework for your project, there's a high probability that said library depends on another set of libraries to function correctly. And those libraries? Well, they might have their own sets of dependencies as well. This intricate chain can go on, forming a web of interrelated modules and packages, most of which remain concealed from the primary user.
Well-known risks with transitive dependencies
Supply chain vulnerabilities have gained significant attention in the realm of software development, and for good reason. Just as in the physical world where a car manufacturer relies on various suppliers for parts, and a disruption or defect in one part can affect the entire production, the software supply chain operates similarly.
When we talk about software supply chains, we're referring to the process through which software is developed, packaged, distributed, and consumed. Consider a popular library that many apps depend on (i.e., import requests if you’re a python developer); if this library has a vulnerability, all apps using it are potentially at risk. In this chain, transitive dependencies, which are the libraries that our libraries depend on, become especially crucial due to their pervasive and often obscured nature.
Lack of Scrutiny: Direct dependencies are usually chosen with care, often after reviewing their documentation, popularity, maintenance frequency, and sometimes even their code. Transitive dependencies, on the other hand, might not undergo the same level of scrutiny since they come "for free" with primary packages. This difference in evaluation depth means that issues in transitive dependencies can persist unnoticed for extended periods.
Entry Points for Malicious Code: Given that transitive dependencies are often overlooked, they present a prime target for attackers. A malicious actor doesn't necessarily have to compromise a popular package directly. They can target a less-known package that the popular one depends on. If this transitive dependency is compromised, anyone using the main package can unwittingly introduce malicious code into their projects.
Complexity in Version Management: Transitive dependencies may require specific versions to operate correctly. However, multiple direct dependencies could rely on different versions of the same transitive dependency. Resolving these version conflicts can be tricky and can lead to unforeseen issues.
License Compliance Issues: Not all dependencies may have the same licensing terms. While a primary dependency might have a license compatible with a project, its transitive dependencies might not. Overlooking these can lead to potential legal and compliance challenges.
How Real Are These Risks?
Transitive dependency risks, while real and potent, might not always manifest in every project or software. This fact raises the question: Why don't some security engineers prioritize these risks as highly as one might expect? Or if they do, can their time be better spent elsewhere?
Some risks, like compliance issues, are unavoidable and demand immediate attention. However, others, such as reachability and exploitability of a vulnerability, can be more accurately assessed. By discerning the genuine threats from the theoretical ones, security engineers can make informed decisions on where to invest their time and resources.
In comes Reachability!
Software Composition Analysis (SCA) tools often show that developers do not utilize the entirety of a dependency's codebase. More often than not, they leverage only a fraction of its functionality. It means that even if a vulnerability exists within a transitive dependency, there's a good chance it never gets executed or exploited because the developer's application doesn't call the vulnerable part of the code.
Both static and dynamic analysis tools offer insights into the utilization of a codebase, but they do so in different ways:
Static Analysis Tools: These analyze the source code, bytecode, or binary code of an application without executing it. They can identify which parts of a dependency are referenced in the code. However, just because a part of a library is referenced doesn't mean it's actively used or that it's part of a viable execution path. On the plus side, static analysis can be comprehensive, covering all potential code paths and offering a complete view of the codebase.
Dynamic Analysis Tools: These analyze the application during its runtime. They can show exactly which parts of a dependency are executed during a specific run or set of runs. This real-world data can be invaluable in determining which portions of a library are genuinely used by the application. However, it's worth noting that dynamic analysis is context-dependent: it only covers the code paths that were taken during the analyzed runs, meaning some rarely used functionalities might go unnoticed.
For a holistic understanding of the codebase utilization and potential security implications, it's often recommended to employ both static and dynamic analysis tools. Together, they provide a fuller picture, allowing engineers to make informed decisions about their dependencies and potential vulnerabilities therein.
In comes Exploitability!
Understanding the reachability of a vulnerability within an application or its dependencies is just one piece of the puzzle. Even if vulnerable code is reachable, what truly matters from a security standpoint is its exploitability. This looks into the feasibility of a potential attacker successfully leveraging the vulnerability to compromise your application or system.
Several factors contribute to determining exploitability:
Public Exposure: A critical consideration is whether the vulnerability is public-facing. If it is buried deep within internal systems and not directly accessible, its exploitability diminishes.
Web Application Firewalls (WAFs): These are designed to filter, monitor, and block malicious web traffic. A well-configured WAF can often detect and thwart exploitation attempts against web application vulnerabilities.
User-provided Input: Vulnerabilities are often exploited through user input. If the vulnerable code processes user-provided data without adequate validation or sanitation, it becomes a prime target for attackers.
Sanitizers and Filters: Even if user input reaches a vulnerable code section, sanitizers or filters applied beforehand can neutralize attack attempts. The presence of these security layers can significantly reduce exploitability.
Environment Configuration: Secure configurations, the principle of least privilege, and other best practices can limit what an attacker can do even after exploiting a vulnerability. For instance, a compromised process running with restricted permissions is less of a threat than one with full system rights.
For a comprehensive understanding of vulnerability exploitability, it's essential not just to identify reachable vulnerabilities but to assess them in the full context of your application's environment, architecture, and usage. This multidimensional assessment empowers organizations to prioritize fixes, allocate resources efficiently, and defend their systems effectively.
What can you do about it?
For security engineers, transitive vulnerabilities are akin to hidden landmines within a software project. These vulnerabilities, nestled deep within the layers of dependency chains, present a unique and often frustrating challenge. Why? Because addressing them isn't always within the engineer's direct control.
Imagine encountering a problem buried so deep that reaching it requires navigating through a complex maze of interconnected components. Each component in this maze — an intermediary dependency — has its own maintainers, release cycles, and compatibility issues. Even with the best intentions and resources, resolving a vulnerability at its root might mean waiting on multiple third parties to release patches, which is neither timely nor guaranteed.
Complicating matters, simply discarding a primary dependency because of a transitive vulnerability isn't always feasible. Such dependencies could be foundational to the software's functionality. So, security engineers find themselves boxed in, knowing there's a potential threat but unable to directly neutralize it.
In the vast landscape of software security, it's undeniable that transitive vulnerabilities carry inherent risks. They're like silent alarms, embedded deep within the layers of a project, often escaping immediate attention. However, the practical world of software security is less about eliminating all risks and more about managing and prioritizing them effectively.
Given the sheer volume of vulnerabilities that emerge—spanning both direct and transitive dependencies—security engineers find themselves in the challenging role of a gatekeeper. They must sift through this avalanche of potential threats, determining which pose immediate risks and which can be relegated to a watchlist.
Direct dependencies, being front and center in a project, naturally demand immediate attention. Vulnerabilities here can have direct, palpable impacts on the application and its users. Thus, these often rise to the top of the priority list. Transitive vulnerabilities, while important, might sometimes take a backseat, not out of negligence but due to the need to allocate resources efficiently.
It's a classic case of triage. Just as medical professionals must decide who to treat first in an emergency, security engineers have to discern which vulnerabilities demand immediate action. And while it might seem ideal to address every single issue as it emerges, the on-the-ground reality is that with limited time and resources, prioritization isn't just a strategy; it's a necessity.
Dive deep into Semgrep Supply Chain and learn how we do the heavy lifting in identifying what vulnerabilities you care about.
Semgrep is a fast, open-source, code scanning tool for finding bugs, detecting dependency vulnerabilities, and enforcing code standards.