DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Dependency Security: 3 Approaches to Vulnerability Management

Introduction: Why is Dependency Security So Critical?

In any software project, the third-party libraries (dependencies) we use are the building blocks of our project. No matter how carefully we write our code, if these blocks are rotten, the entire structure can be jeopardized. Just a few months ago, a critical vulnerability in an npm package we were using could have potentially affected hundreds of thousands of our users. Following this incident, I decided to delve deeper into the matter of managing dependency vulnerabilities.

In this post, we will thoroughly examine the three main approaches I use and have found effective for ensuring dependency security. I will share the advantages, disadvantages, and real-world applications of each approach with concrete examples. My goal is not just to provide theoretical information, but also to offer practical and actionable solutions based on my field experience. Remember, security is a process and requires constant vigilance.

Approach 1: Automated Vulnerability Scanning & Remediation

Automated scanning tools, integrated into every project's CI/CD pipeline, are the first line of defense for dependency security. These tools regularly check all the libraries our project uses against known vulnerability databases. Among the tools I use most frequently in my projects are the npm audit or yarn audit commands. These scan and report security vulnerabilities in the libraries listed in the package-lock.json or yarn.lock files.

For instance, if we are using an outdated version of the lodash library in a project and this version has a known Remote Code Execution (RCE) vulnerability, npm audit might show us output like this:

# npm audit
#
#                             High            Moderate         Low        Critical
#                  ┌─────────┴─────────┬─────────┴─────────┬─────────┴─────────┬─────────┴─────────┐
#                  │        1          │        0          │        0          │        0          │
#
#         Moderate severity vulnerability
#         Denial of Service (DoS)
#         Severity: Moderate
#         Package: lodash
#         Patched in: 4.17.21
#         Dependency of: my-app
#         Path: my-app > lodash
#         More info: https://npmjs.org/advisories/XXXXX
#
#         1 moderate severity vulnerability
#
#         Run `npm audit fix` to install fixed versions.
Enter fullscreen mode Exit fullscreen mode

This output indicates that a version newer than 4.17.21 of the lodash library fixes this vulnerability. The npm audit fix command attempts to resolve this vulnerability by updating dependencies to fixed versions whenever possible. This automation significantly reduces the effort required for manual scanning and updating.

ℹ️ Automated Update Tips

While commands like npm audit fix are great, they are not always 100% reliable. Sometimes, updates can cause compatibility issues with your existing code. Therefore, it is critical to implement a comprehensive testing process (unit tests, integration tests, even manual QA) after any automated updates. Especially for major version jumps, caution is advised before using options like npm audit fix --force.

The biggest advantage of this approach is that it allows us to detect and fix security vulnerabilities at an early stage. However, these tools may not cover all vulnerabilities and can sometimes produce false positives. Additionally, some updates might disrupt the stability of the project. Therefore, we should not ignore the results of these automated scans, but rather review them carefully and intervene manually when necessary.

Approach 2: Dependency Update Strategies & Security Patches

Beyond automated scanning, adopting a proactive dependency update strategy significantly strengthens our security posture. This involves updating our dependencies at regular intervals, not just when critical vulnerabilities are discovered. One method I practice is the "keep it lean" principle. Adding only the libraries that are truly needed to the project and regularly cleaning up unused ones narrows the potential attack surface.

There was a time when a Java project had hundreds of jar files, and we didn't even fully know what most of them did. This situation was a complete nightmare, both for security and maintenance. When a new vulnerability emerged, finding which jar was affected took hours. After this experience, I started asking myself these questions whenever I added a new dependency to any project: "Is this dependency really necessary? Is there a safer, less obscure alternative? When was the last update for this library?"

⚠️ Version Pinning

Using version ranges like ^ (caret) or ~ (tilde) in dependency management can cause the project to unexpectedly move to new, potentially incompatible or insecure versions. Therefore, especially for production environments, pinning dependencies to specific versions (e.g., lodash@4.17.21) is the safest approach. package-lock.json and yarn.lock files automatically provide this pinning, but it's still important to be familiar with these files.

A regular update strategy not only helps avoid new vulnerabilities but also allows us to benefit from performance improvements and new features. However, this strategy has its own challenges. Frequent updates can lead to regressions (breaking existing functionality). Therefore, the update process must be managed carefully. I generally follow these steps:

  1. Small Steps: I update only a few dependencies at a time.
  2. Thorough Testing: After each update, I run automated tests and manually check critical workflows.
  3. Rollback Plan: I ensure we have a mechanism to easily revert to the previous version if an update causes problems.

This approach improves the overall health of our project while making us more resilient against vulnerabilities.

Approach 3: Software Bill of Materials (SBOM) and Vulnerability Database Integration

The highest level of dependency security involves creating a complete inventory of all components used in our project and continuously comparing this inventory against known vulnerabilities. This is where a Software Bill of Materials (SBOM) comes into play. An SBOM is a digital document that lists all the components of a software product, their versions, and the relationships between them.

While developing the backend service for an enterprise ERP system, we were using hundreds of modules and libraries. In this complex structure, knowing which library was running with which version in which service was almost impossible. During a security audit, we found a critical vulnerability in an old, unused library that was still loaded on the system. It took us a full week of analyzing logs and scanning code to understand exactly where and how this vulnerability could be triggered. This experience showed me how vital SBOMs are.

Various standards and tools are available in the industry for creating SBOMs. Formats like CycloneDX and SPDX are commonly used. In my projects, I have started using tools that automatically generate SBOMs within the CI/CD pipeline. For example, tools like syft can scan Docker images or file systems to create SBOMs by identifying components.

Integrating the generated SBOM with various vulnerability databases (like NVD, CVE, OSV) is the next step in proactive vulnerability management. With this integration, when a new vulnerability is discovered, we can scan our project's SBOM and determine in seconds whether we are affected.

💡 SBOM and Enterprise ERP Experience

In a manufacturing ERP system, a critical vulnerability was detected in a Java library used for supply chain integration. This vulnerability allowed remote code execution during XML processing. Normally, this would have been a major problem for us. However, thanks to the syft tool running in our CI/CD pipeline, we had automatically generated an SBOM of our system and identified that a specific version of this library was in use. With this information, we quickly pinpointed the affected service and applied the patch. This event once again demonstrated to me how critical SBOMs are as a security layer. Without an SBOM, finding this vulnerability could have taken weeks.

The biggest benefit of this approach is the complete visibility it provides. You clearly know what your project contains. This allows for better risk management. The disadvantage is that setting up and maintaining these systems can be a bit complex initially. However, the security and efficiency gained in the long run are well worth the effort.

Real-World Scenarios and Trade-offs

One of the most common trade-offs I encounter in dependency security is balancing the effort to stay updated with maintaining stability. New dependency versions often contain security patches, making it logical to update them. However, sometimes these new versions can cause compatibility issues with our existing code or lead to unexpected behavior.

For example, when I tried to update a native library in a mobile application (which I developed with Flutter), this update caused serious problems in the build processes on both Android and iOS. I spent about 2 days just trying to get this single library to the correct version. Eventually, I had to revert to the older, more stable version and wait for a new release to address the vulnerability. In this case, our goal of "staying updated" could not override the goal of "stability."

Another important trade-off is the balance between the overhead introduced by security tools and risk reduction. Automated scanning tools can slow down the CI/CD pipeline. Deeper analysis and SBOM generation processes also require time and resources. However, this overhead is often negligible compared to the cost of a potential data breach or system outage. The loss of reputation and financial damage following a cyberattack is far greater than the few extra minutes spent on scanning tools.

🔥 Dependency Chain Attack

One of the most dangerous types of dependency vulnerabilities is an attack carried out through transitive dependencies. If a dependency used by your primary dependency has a vulnerability, you might be affected even if you don't directly use that package. For instance, in your my-app project, you use package-a@1.0.0. package-a takes package-b@2.0.0 as a dependency. If package-b@2.0.0 has a critical vulnerability, you might be exposed to an attack even if you haven't directly added package-b to your project. This is why it's crucial for SBOM and audit tools to scan the entire chain.

When managing these trade-offs in the real world, we need to consider the project's size, sensitivity, and available resources. The strict security measures we might apply to a small side project could be insufficient for a large enterprise system. The important thing is to accurately assess the risks for each project and define an appropriate security strategy.

Conclusion: Staying Constantly Vigilant

Dependency security is not a topic that can be addressed once and forgotten; it is a process that requires constant attention and effort. The libraries we use are constantly updated, new vulnerabilities are discovered, and attack methods evolve. Therefore, adopting a proactive approach is essential.

The three main approaches I've discussed – automated scanning and remediation, conscious update strategies, and integration with SBOMs – have provided me with a strong foundation for managing dependency vulnerabilities. By using these approaches together, I've been able to make my projects more secure and minimize potential risks.

We must remember that even the best code can be weakened by insecure dependencies. Therefore, I believe that every developer and every team should consider dependency security as one of their top priorities. The information and experiences I've shared in this post will help you move forward with more confidence on this path.

I have also previously written about [related: CI/CD pipeline security] and [related: Container security best practices]. These topics are closely related to dependency security.

Top comments (0)