Part II: A Journey of a Thousand Binaries – The Challenges with Software Dependencies
Everything you need to know about securing your software dependencies, a crucial part of our software development process.
TL;DR
In part one of this series, we looked at what is a dependency, different types of dependencies, and their benefits in our code. In part two, we’ll look at the risks of using dependencies. Whenever we add a dependency we are increasing the risks of any software development cycle.
What can possibly go wrong?
When we add a dependency to our project we are actually trusting the work of other developers. We are essentially outsourcing the work of developing that code – designing, writing, testing, debugging, and maintaining – to someone else. Every time we use a dependency, we are potentially exposed to all failures and flaws of that dependency, and I am not talking about open source exclusively, closed source software has been known to have issues as well.
Open-source software introduced the concept of use “as is”. An open-source developer creates projects that adhere to their core values, intentions, and reputation. What we don’t know is whether all open-source code libraries that we have are in optimal condition. This introduces a variety of security and performance challenges that require our awareness and treatment. Taking preventive measures today will save you time, headaches, and money in the future.
The next reasonable question is what methodology should we use to verify that the open-source software we want to use is safe. What are the considerations that we need to double-check when adding a specific dependency? What are the questions that we should be asking ourselves when using open-source software? How well is the code written? How important is testing? What are the security best practices? What about transitive dependencies? How do we deal with dependencies?
Scandals and horror stories
Before we continue to look at software dependencies, here’s a list of only some of the horror stories that for better or worse still give nightmares to fellow developers. This is not an exhaustive list, because literally, this article has to have an end!
left-pad (npm) – dispute over a package name
In March 2016, a package called “left-pad”, that many popular JavaScript packages depended on, was unpublished as the result of a naming dispute between the author Azer Koçulu and npm. A module named “Kik”, created by Azer Koçulu was already published on the npm registry, leaving the Kik organization unable to publish another module with the same name. npm renamed Koçulu’s package after Kik appealed directly to npm. Deeply disappointed with that decision Azer Koçulu unpublished 250 packages, left-pad among them. Although the package was republished three hours later, it caused widespread disruption, leading npm to change its policies regarding unpublishing.
Lesson learned: Proxy and cache your dependencies.
NPM version 5.7.0 – critical Linux filesystem permissions changed
In February 2018, an issue was discovered in npm version 5.7.0 in which running sudo npm on Linux systems would change the ownership of system files, permanently breaking the operating system.
Lesson learned: Define a roadmap with an extensive testing plan when upgrading dependencies.
eslint-scope version 3.7.2 – malicious code published
In July 2018, the npm credentials of a maintainer eslint-scope package were compromised resulting in a malicious release of eslint-scope, version 3.7.2. The malicious code contained an exploit that sent your .npmrc file to a 3rd party service in a postinstall hook and copied the npm credentials of the machine running eslint-scope and uploaded them.
Lesson learned: Fix dependencies version and define a roadmap with an extensive testing plan when upgrading dependencies
Log4j – zero-day remote code execution exploit
In Dec 2021, the Log4j security vulnerability (CVE-2021–44228) was discovered. Log4j uses the JNDI API to obtain naming and directory services from several available service providers: LDAP (Lightweight Directory Access Protocol), COS (Common Object Services), Java RMI registry (Remote Method Invocation), DNS (Domain Name Service), Common Object Request Broker Architecture (CORBA), etc
The vulnerability took advantage of Log4j not checking LDAP and JNDI requests and allowing attackers to execute arbitrary Java code on a server. This a classic example of missing input validation and blindly trusting the input without sanitisation. (In this case Log4j failed to sanitize URLs passed in these strings). The Apache Software Foundation gave Log4 Shell a CVSS rating of 10 (Critical), the highest available score.
Lesson learned: We need to know what we have, where we have it and why we have it there in the first place! Hint: SBOM
A panoramic view on open-source code
Dependencies are an integral part of any application, every single time we add a dependency we are incorporating a complete new code base to our application. This code base will deeply impact every aspect of the application, from performance functionality, to security and maintainability. We need to be aware at all times what building blocks we decide to add to our application.
Let’s review some numbers. According to the 2022 Open Source Security and Risk Analysis Report by Synopsys,78% of the code found in the 2,400 commercial codebases, is open-source. 85% of the codebases contained open-source that was more than four years out-of-date and 53% of audited codebases had license conflicts.
In the previous article of this series we learned about the different types of dependencies but that is only half of the story. The other key component to be able to make a more informed decision is how much do we depend – Level of dependency – that we have to any Resource, Module, Package, Library, Framework.
Level of dependency
In order to identify what dependencies are crucial, important, cosmetic, easily exchanged or superfluous to our own projects, we should have a solid requirements specification model, service agreement definitions and robust mapping of dependencies by their role in the application execution. For example a functional dependency entails a Resource, Module, Package, Library, Framework required for an application to function.
Functional dependencies are the result of the tasks and activities required to achieve a specified outcome, the priority of the outcome is in turn defined by the requirement specification model. Operational and non-functional dependencies are components, assets, resources, modules, packages, libraries, or frameworks required to operate, deliver, or deploy an application. This can entail any component that is required to perform operational tasks but is not an integral part of the final application.
Choose wisely
The different types of dependencies and our level of dependency on them will help us identify the cadence of update, migration costs or cleanup efforts each dependency requires during its lifetime in our codebase. Each one of these activities requires investment and prioritization of our resources!
What you can do TODAY
To improve the security and quality of your software today, you can scan your binaries and open source dependencies using open source tools like Frogbot. This tool automatically protects your git projects from security vulnerabilities, even before a release is made. Also, to have all this information alongside your development environment there are more free plugins and extensions for several IDEs (VS Code, IntelliJ IDEA, Visual Studio, etc). If you’re building Docker images, you can can your containers to keep vulnerabilities out of your pipeline with the Docker Desktop extension.
Discover more suggested tools, the software supply chain platform, dependencies, and DevOps best practices by joining one of our upcoming workshops.
Happy coding!