Third-party dependencies have become more than just a tool – they are the backbone of modern software development. Using them, you can push the boundaries of innovation, simplify your workflows, and leverage the genius of others through pre-built solutions. But, as with most good things, there's a catch. As you integrate these external components, you also open the door to potential security vulnerabilities lurking just out of sight.
Supply chain attacks aimed at libraries and other development tools are up by an astounding 633% year-over-year. Among these, the popularity of dependency confusion is rapidly climbing the ranks, posing a formidable challenge to the very efficiencies these dependencies promise. You could say the industry has a bit of a dependency problem.
This article explores how these attacks exploit the trust developers place in package managers and public repositories and, more importantly, provides a comprehensive guide on how to fortify your software supply chain against such threats.
Dependencies are external software libraries, modules, or packages a software application integrates with to achieve specific functionalities. These dependencies are typically sourced from public repositories or package managers, allowing developers to leverage pre-existing solutions and streamline the development process. Unfortunately, leveraging these public repositories could expand your cyber attack surface, as their open and collaborative nature makes them more prone to cyberattacks. One such attack is the “dependency confusion.”
First identified by Alex Birsan, dependency confusion is a relatively new and novel attack within the application security world. Some package managers will look to private and public repositories when resolving a dependency. If they find packages with the same name in both repositories, they might prioritize the one in the public repository, especially if it has a higher version number. Attackers can exploit this behavior.
There are three primary vectors for executing a dependency confusion attack:
Dependency confusion attacks exploit software developers' trust in package managers and public repositories. A typical dependency confusion attack may unfold as follows:
Adopt a standardized naming scheme for all internal packages by using company-specific identifiers, detailed descriptors, and a systematic versioning approach. For instance, a naming structure such as “orgname-functionality-version” can offer clarity and reduce the chances of mistakenly integrating an external package with a similar name.
In the context of package management systems like npm, it's beneficial to proactively reserve specific namespaces that align with your organization's identity. However, you will need a different strategy for platforms like PyPI, which currently lack a namespacing feature. Here, it's advisable to preemptively claim or "namesquat" the titles of your private packages in the public domain.
Embed continuous dependency scanning and monitoring mechanisms like Software Composition Analysis (SCA) tools within your CI/CD workflow. With Jit, you can easily activate robust SCA tools, such as Npm-audit, OSV-scanner, and Nancy, directly within your GitHub environment.
You can automate these tools to run on a recurring schedule and manage them within a single dashboard, getting enriched reports and real-time remediation suggestions for every new vulnerability. Remediation is available in Pull Request or via Jit’s Actions page, as shown below. In this case, you simply click the “Create a Fix PR” button, which will generate a new Pull Request and introduce the fix code so you can apply the fix in your GitHub environment.
Opt for private repositories with stringent data access controls where only specific roles or individuals with the appropriate credentials can access, upload, or modify the packages. These repositories should enforce multi-factor authentication (MFA).
Popular options for private repositories include GitHub Private Repositories, GitLab Private Repositories, Nexus Repository OSS, JFrog Artifactory, Docker Hub Private Repositories, and Bitbucket. These platforms offer enhanced security features tailored to organizations prioritizing code and package security.
Incorporate strict package allowlisting mechanisms within your CI/CD pipeline. Automated tools or scripts can scrutinize every dependency introduced or updated during the build phase. These tools should cross-reference each package's name, version, and possibly its checksum or digital signature against a pre-defined approved list.
If a package doesn't match the criteria or isn't found on the allowlist, the CI/CD pipeline should immediately flag it and halt the build or deployment process. This interruption is a protective measure, so potentially harmful or unvetted packages don't make their way into the production environment without a thorough manual review.
Adopt a robust access control strategy by integrating Role-Based Access Controls (RBAC) within your package management and repository infrastructure. Doing so lets you define specific IAM roles and assign granular permissions associated with each function.
For instance, you can restrict package publishing or modification rights solely to senior developers, DevOps teams, or other trusted personnel. Junior developers or external contributors might only have read access or require multi-factor authentication for specific actions.
This hierarchical access model shields critical packages from malicious changes, which reduces the risk of introducing vulnerabilities or compromised dependencies into the codebase. Detailed access logs can also help you swiftly detect and address any unusual or unauthorized activity.
Embrace cryptographic measures by signing your packages using private keys. This process embeds a unique digital signature within each package, acting as a seal of authenticity. When the package is fetched or integrated into an application, its embedded signature should be cross-verified against a trusted public key corresponding to the private key used for signing.
This practice also deters attackers from introducing rogue packages or tampering with existing ones, as any such attempts would break the signature validation.
Leverage the power of lock files to maintain consistency and security in your dependency management. Files such as `package-lock.json` in the npm ecosystem or `Gemfile.lock` in the Ruby world are snapshots of the exact versions and configurations of dependencies your project relies on.
By locking down specific versions, you prevent the accidental introduction of newer versions of packages that might contain vulnerabilities or untested changes. Version locking is especially crucial when specific packages might introduce breaking changes or when more recent versions haven't undergone rigorous security vetting. Lock files also provide a clear record of which versions of dependencies are in use, making it easier to audit, review, and roll back if necessary.
As we've seen, dependency confusion attacks are a lurking threat. Companies often harbor a false sense of security regarding their third-party dependencies. However, today's risk landscape requires a serious, zero-trust approach. This means that even open-source components must be verified as secure, continuously monitored, and updated regularly.
Jit’s DevSecOps orchestration platform helps you confront these dependency challenges. With its intuitive tools and real-time insights, Jit seamlessly integrates into your CI/CD pipeline, ensuring your software's defenses are always up-to-date. Activate powerful SCA tools like npm audit, OVS Scanner, and Nancy to fortify your software like never before. Explore more here.