Back to Feed

Understanding Phantom Packages: A Hidden Risk in Your Codebase

Yossi Pik

-

July 1, 2024

In the world of software development, the use of third-party packages is ubiquitous. They save time, add functionality, and allow developers to build complex applications efficiently. However, this convenience comes with its own set of risks, one of which is the issue of phantom packages, also known as "ghost" packages.

What Are Phantom Packages?

Phantom packages are dependencies that are not explicitly defined in your application's manifest (such as package.json in Node.js) but are still used directly in your application code. This happens because these packages are dependencies of other packages you’ve included in your project – also known as transitive dependencies.

A package becomes a phantom package when it's imported and used directly in your application code without being explicitly declared in your application manifest. For instance, if you import and use a function from a package that is only a transitive dependency, that package becomes a phantom.

How Do Phantom Packages Work?

In languages and environments where compilation is not required, like Node.js, it's possible to use packages if they are already installed in your environment. When you install a package that has its own set of dependencies, these sub-dependencies get installed as well. This means that the package you need for your project is being implicitly installed by one of the other packages your project depends on.

Example of a Phantom Package

div.code-block { display: block; background: black; color: #fff; font-weight: 500; padding: 20px; max-width: calc(100vw - 1.6rem); } code:not(.block) { color: #188038; } span.pink { color: #df307a; } span.blue { color: #4a86e8; } span.green { color: #188038; } span.orange { color: #ff9900; } span.red { color: #ff0000; }

Let's say you have a Node.js project, and you install a package called awesome-library. In your package.json, you might have something like this:

{
"dependencies": {
"awesome-library": "^1.0.0"
}
}

Now, awesome-library has its own dependencies, one of which is helper-library:


{
"name": "awesome-library",
"version": "1.0.0",
"dependencies": {
"helper-library": "^2.0.0"
}
}

You don't explicitly list helper-library in your package.json, but it gets installed because awesome-library depends on it. Now, if you directly use helper-library in your application code like this:

const helper = require('helper-library');

helper.someFunction();

helper-library becomes a phantom package because it is used directly in your code without being declared in your package.json. This creates a situation where you rely on helper-library but have no control over its version or visibility in your dependency management tools.

How Does This Happen?

Phantom packages often come into existence due to a few common scenarios:

  1. Manifest Cleanup: Someone might clean up the manifest and remove the declaration of dependencies they believe are not being used. However, if the package is still used indirectly through another package, everything keeps working because the package is still installed. Voilà, a phantom package is born.
  2. Code Snippet Inclusion: While looking for a small piece of code, you might search Google, Stack Overflow, or even use ChatGPT and come across an effective code snippet that uses a new external package. If you "forget" to add the package as a dependency to your manifest because it just works (as it was installed as a transitive dependency), another phantom package is born.

The Risks of Phantom Packages

The main risk associated with phantom packages is the lack of control and visibility over what exactly is being included in your codebase. Here are some specific concerns:

  1. Unknown Versions: Since the phantom package is not declared in your manifest, you have no direct control over its version. This can lead to situations where different versions of the same package are included in different parts of your project, potentially causing conflicts and bugs.
  2. Security Vulnerabilities: Without explicit declarations, it becomes challenging to track and manage security vulnerabilities. If a phantom package has a known vulnerability, you might remain unaware and leave your application exposed.
  3. Consistency: If the same package is being declared by multiple transitive packages with different versions, it can create inconsistency in your codebase. Since the phantom package is not explicitly managed, changes in transitive dependencies might lead to different versions of the package being used. This inconsistency can cause unexpected behavior, bugs, and make troubleshooting difficult, resulting in dependency hell where resolving these conflicts becomes a major challenge.

Why Phantom Packages Are Being Neglected

Phantom packages often go unnoticed and unmanaged for a couple of reasons:

  1. It Works: When everything functions correctly, developers might not even be aware of the problem. Since the application works as intended, there's no immediate indication that phantom packages are present.
  2. Focus on Declared but Unused Packages: Many linters and development tools are designed to detect the opposite issue – packages that are declared in the manifest but not used in the code. This focus can cause developers to overlook the case of undeclared yet used packages, missing the phantom package problem altogether.

How Common Is This?

Surprisingly, phantom packages are very common. Our experience shows that in many real-life production projects we have scanned, there were phantom packages present. These undeclared dependencies often go unnoticed by developers because the application continues to function correctly, masking the underlying issue. This widespread occurrence highlights the importance of specifically looking for phantom package cases to ensure the stability and security of your codebase.

Mitigating the Risks

The primary strategy to mitigate the risks associated with phantom packages is to use scanning tools that have the ability to detect phantom packages. These tools can help you identify and manage any undeclared dependencies that are directly used in your application code. By leveraging such tools, you gain better control and visibility over your entire dependency tree, ensuring that you are aware of all packages being utilized in your project.

Conclusion

Phantom packages are an often overlooked aspect of dependency management that can introduce significant risks to your application. By understanding what they are and implementing strategies to manage them, you can maintain better control over your codebase, enhance security, and ensure the overall health of your project. From a security perspective, phantom packages can bypass regular checks, leaving your application vulnerable. Proactively identifying and managing phantom packages reduces your attack surface and strengthens your defenses. Backslash analyzes the manifest files and the direct and transitive packages and detects cases of phantom packages

Stay vigilant and proactive in your dependency management to mitigate the hidden risks posed by phantom packages. Regularly use scanning tools designed to detect these dependencies and keep your package manifests up to date. This will help safeguard your project and maintain the integrity of your codebase. Start a free trial with full access to the Backslash platform via a pre-configured demo environment that includes phantom packages and more, now available at https://www.backslash.security/trial.