Everyone is trying to speed up software development by building their solutions out of "ready-made bricks" like third-party components or libraries. But we tend to forget what problems they may cause. Today, I'll try to explain why it's worth thinking twice before adding a new dependency to a project. This article is a remake of an old piece. I decided to revisit it after realizing that my advice from back then still stands.

Ten years ago, I wrote about it in the compilation of recommendations, The Ultimate Question of Programming, Refactoring, and Everything.
Many people criticized this point, which made me doubt whether I was right. After all, the world doesn't stand still: progress marches on. Developers put apps together like LEGO bricks, and package managers have evolved too. Now it's much easier for everyone, and everyone seems happy. So why am I bringing up my doubts again?
I've not brought this topic up again in later articles, including 60 terrible tips for a C++ developer (2023), but I've been watching things change for several years, and my view has returned to where it was before: whenever possible, it's still better to avoid to add new libraries that spawn more dependencies.
And I think I was right, though honestly, I didn't put it in the best way, and my examples weren't the strongest either. The points I made earlier also missed something important, one that's become central to secure software development and compliance. Actually, it is there, number 4, but it's only mentioned in passing. So let me try again, this time properly.
First, I'll show an older, slightly reduced version of the text, and then I'll reinforce it with new arguments and thoughts. Overall, everything I've said still stands, and I ask that you think about what you've read before we get into another round of arguments in the comments :)
Suppose you need to implement an X functionality in your project. Theorists of software development will say that you have to take the already existing library Y, and use it to implement the things you need. In fact, it is a classic approach in software development, reusing your own or others' previously created libraries (third-party libraries). And most programmers use this way.
However, those theorists in various articles and books, forget to mention what hell it will become to support several dozen third-party libraries in about 10 years.
I strongly recommend avoiding adding a new library to a project. Please don't get me wrong. I am not saying that you shouldn't use libraries at all, and write everything yourself. This would be insufficient, of course. But sometimes a new library is added to the project at the whim of some developer, intending to add a little cool small "feature" to the project. It's not hard to add a new library to the project, but then the whole team will have to carry the load of its support for many years.
Tracking the evolution of several large projects, I've seen quite a lot of problems caused by a large number of third-party libraries. I will probably enumerate only some of the issues, but this list should already provoke some thoughts:
namespace, you'll start having name clashes. This causes compilation errors, or hidden errors. For example, a wrong enum constant can be used instead of the one you've intended to use. Again, I should emphasize; I don't say that we should stop using third-party libraries at all. If we have to work with images in PNG format in the program, we'll take the LibPNG library, and not reinvent the wheel.
But even working with PNG we need to stop and think. Do we really need a library? What do we want to do with the images? If the task is just to save an image in *.png file, you can get by with system functions. For example, if you have a Windows application, you could use WIC. And if you're already using an MFC library, there is no need to make the code more sophisticated, because there's a Cimage class (see the discussion on Stack Overflow). Minus one library—great!
Keep resisting the temptation to add new libraries to your project. Add it only when you're sure that libraries are really indispensable.
What to do? Here are some possible maneuvers:
P.S. Many people won't like what is said here. For example, I recommend using WinAPI rather than a non-portable universal library. It kind of locks the project into a single operating system and complicates the further program migration—and I can't agree with this point. This idea to migrate the program to another OS is just sitting in the developer's head, but in reality, management may never set such a goal at all, or the project will fail due to excessive complexity and versatility, even before it gains popularity and the need for porting arises. Also, don't forget item (8) on the list of issues provided above.
Everything said in the original is still true today, although the emphasis has shifted. Today, we need to focus on controlling an overly gigantic codebase when establishing secure development processes. Now it is coming to the forefront. Point number 4 has become truly urgent.
Adding libraries to your projects triggers the creation of transitive dependencies. This means that the library uses other libraries, which in turn use other libraries, and so on. The 10 libraries your project requires can, in reality, turn into hundreds actually being used. In some languages, such as C++, this is not very noticeable, whereas when developing in JavaScript, Java, or Python, huge chains of dependencies quickly emerge.
If a vulnerability is detected in one of these libraries, it means your application is also potentially vulnerable. It doesn't matter to the user whether your app is vulnerable because of your own code or third-party code.
Of course, the vulnerability in a particular library doesn't automatically mean that your application is vulnerable as well. Dangerous functions may simply never be called, or work with data that won't cause any problems. This means that the vulnerability isn't the primary attack target.
However, that doesn't make it much easier. After all, we need to be sure that the vulnerability can't be exploited. And in reality, it can be really hard work.
So, if you think about your project's reputation, it's worth tracking all new vulnerabilities in all components that are used directly or indirectly, for example, using Software Composition Analysis (SCA) tools for this purpose.
If you have composition analysis tools, what's the problem with having a large number of dependencies? The more there are, the greater the chance that someone will find a new vulnerability and exploit it.
That's not all. Vulnerabilities aren't always accidental, they can be planted deliberately. For example, supply chain attacks are becoming a more well-known problem: attackers use various methods to inject undocumented features into various open-source libraries. For example, they contribute some changes and enhancements for a while, build up a positive reputation, and then sneak a backdoor into the code. Transitive dependencies open the door to exploit a large number of applications at once. So, the more third-party libraries you use, the higher the risk of supply chain attacks.
Another version of this kind of attack has emerged, tied to AI-generated code. GenAI can hallucinate the names of required packages. It's clear that hallucinations follow certain patterns, and malicious actors will not miss the opportunity to use it: they create and publish compromised packages with names that GenAI is most likely to hallucinate. It sounds far-fetched, but it works, and such attacks have already happened. They came to be known as slopsquatting.
The next topic is static analysis. From a security standpoint, you should review your own code plus all third-party code. Few people do this, since most people tend to focus only on reviewing their own code. But anyone who stops to think about it is faced with a huge amount of work.
However, if you want to conduct a compliance assessment on your project, it makes no difference whether you wrote the code yourself or obtained it from an external source. Do you use third-party code? It's yours now. Take responsibility for it. That makes sense: it doesn't matter who wrote the code that makes the app vulnerable.
In practice, it may be impractical for a single company to perform a static analysis of all borrowed code. This has given rise to trusted repository projects and consortia of companies for the conjoint analysis of borrowed components. One approach is to analyze the initial modules that make up the attack surface. Here again, the problem of defining this surface arises. I won't go into detail here.
Of course, it won't have an impact on our habit to eagerly add third-party components into the project. I'm not calling for you to change that; it'd be counterproductive. However, I still want you to be aware of the potential problems you might encounter.
You likely already have a code review process. I'd suggest adding a step to it: reviewing or discussing every library that gets added. Let the developer explain why they really need yet another library. Let the developer explain why they really need yet another library. Knowing they'll have to justify it, they might choose a different approach, or reuse a library that's already in the project. In short, try to resist adding new libraries to the project. It might actually work out for you sometimes :) And you will thank me later.
Ideally, it's worth auditing your project from time to time to check whether all its libraries are actually being used. I've seen cases where, while porting a large C++ application from Win32 to Win64, it turned out that about 10 libraries weren't being used at all. Meanwhile, years of resources had gone into "maintaining" them within the project. Conducting such an audit isn't easy, but you might be able to organize one.
0