Software development heavily relies on third-party dependencies to streamline the development process and enhance functionality. These dependencies offer immense convenience, but they also pose a significant security risk. To ensure the integrity of software, you can employ tools like Endor Labs to detect vulnerabilities lurking in our third-party dependencies, using call graph capabilities, Endor Labs can even annotate whether the vulnerability is reachable. However, discovering a vulnerability is only half of the battle. In this article, we will explore the complexities of fixing these vulnerabilities and the various strategies to address them effectively.
Finding is Half the Battle
Detecting a vulnerability is a crucial step towards securing your software, but it's just the beginning. The true challenge lies in fixing the vulnerability promptly and efficiently. This process can vary significantly based on the culture and choices made by your engineering team and the company.
Addressing Vulnerabilities: Availability of Fixes
When a vulnerability is reported (for instance in NVD) in one of your third-party dependencies, the course of action can vary greatly depending on the circumstances. Here, we'll explore common scenarios and how to navigate them effectively:
No fix available in a new version
Sometimes, a vulnerability is reported, but no immediate fix is available. However, there are varying degrees of concern depending on the status of the impacted dependency:
Impacted dependencies are maintained
If the dependency is actively maintained, there's hope on the horizon. The library maintainers are likely aware of the issue and actively working on a solution. In this case, you should keep an eye on their progress, follow their communication channels, and be prepared to update your dependency as soon as a fix is released.
Impacted dependencies are no longer maintained
A challenging scenario arises when the dependency is no longer actively maintained by its original developers. In this situation, you are essentially on your own. There is no guarantee that a fix will ever be released by the original maintainers.
In such cases, your best options are:
- Patch Strategy: If you have the technical expertise and resources, you can attempt to create a patch to address the vulnerability. This involves diving into the dependency's codebase, identifying the vulnerable code, and crafting a solution. Keep in mind that this can be a time-consuming and complex process, as the codebase may not be well-documented or easy to understand.
- Fork Strategy: Another approach is to fork the dependency and maintain your own version. This allows you to take control of the codebase and implement security fixes as needed. However, forking comes with its own set of responsibilities, including ongoing maintenance, bug fixes, and updates.
It's important to note that both of the above strategies require tooling. It's advisable to have such tooling in place before encountering a vulnerability. Implementing this type of tooling within your build system and codebase can be a complex endeavor.
Fix available in a new version
Maintainers delivered a fix
In an ideal situation, a vulnerability is reported, and the library maintainers have already addressed it by releasing a new, secure version. This is the most straightforward scenario to handle. You can simply update your dependency to the latest version, which includes the necessary security patches. In this case, you benefit from the responsiveness of the library maintainers, who have taken the initiative to rectify the vulnerability. Your role primarily involves integrating the updated library into your project.
However, there's a catch when it comes to updating a library to a newer version. It comes with its own set of risks, and the impact might be more significant than expected. This is because a new version may bring not only the fix for the vulnerability but also other changes. Updating libraries to newer versions can be tricky for several reasons:
- Breaking Changes: Newer library versions may have breaking changes that require significant code adjustments. This can be time-consuming and may necessitate substantial testing to ensure the software remains stable.
- Performance Issues: Upgrading a library may introduce performance problems that need to be addressed. Your application might not function as smoothly as before, leading to user dissatisfaction.
- Regression: Changes in library versions can lead to unexpected regressions in your software. What used to work flawlessly might now exhibit unexpected behavior.
To navigate these challenges effectively, it's essential to have a robust strategy in place for updating dependencies. Regularly reviewing and incorporating library updates as part of your development process (like Dependabot) is a proactive approach to minimize these difficulties.
Maintainers delivered a fix, but you can't update
In some instances, the dependency is actively maintained, and a fix was delivered. However, you may find yourself in a situation where you can not immediately update to the latest version due to compatibility issues, extensive codebase modifications, or other constraints.
In such cases, the patch or fork strategy may once again become your only viable solution. While the library maintainers delivered the fix, you'll need to take matters into your own hands to secure your application. This adds an extra layer of complexity to the process, as you'll have to ensure that your custom fixes remain compatible with your current dependency version. Patch creation and application can be complex for several reasons:
- Version Specificity: Patches may need to be tailored to different versions of the same library. Each version may have unique codebases, requiring custom patches to mitigate vulnerabilities effectively. This can become even more complex if the same library is used by your organization with different versions. Each version could result in a different patch!
- Patch Discovery: Finding the right patch for a vulnerability can be time-consuming. It often involves scouring community forums, GitHub repositories, or other resources to identify a suitable fix.
- Patch Application: Once you have a patch, applying it can be tricky. Managing patches across multiple dependencies can become overwhelming. You may need to maintain forks of these dependencies and continually integrate your patches to keep your software secure. Fortunately, there are tools available, like Bazel, that can help streamline the process of applying patches. These tools simplify dependency management and make it easier to maintain customized versions (no need of fork) of libraries with security fixes.
Finding vulnerabilities in third-party dependencies is just the first step towards ensuring the security and stability of your applications. Fixing these vulnerabilities is a multifaceted challenge that depends on factors such as your team's culture and availability of the fix.
Whether you opt to update libraries to secure versions or apply patches to dependencies, it's essential to have a well-defined strategy in place. Regularly maintaining your dependencies and staying informed about security updates will go a long way in simplifying the process of fixing vulnerabilities and ultimately fortifying your software against potential threats. Remember, securing your software is not a one-time task but an ongoing commitment to maintaining the trust of your users and the integrity of your codebase. Ensuring a fast mitigation of vulnerabilities should be an integral part of your organization's engineering culture.
Alexandre Wilhelm is a Founding Engineer at Endor Labs. As the first engineer in the company, Alexandre built the foundation of the backend platform and set up monorepo at Endor Labs. During his free time, Alexandre loves hiking in the mountains, especially exploring the Sierra Nevada in California.