Safeguard
Supply Chain•

How can an attack hide in a dependency you never chose?

Your software uses packages you never picked, because your packages use others. Learn how attacks hide in that second layer and how to see and shrink it.

Safeguard Security Team
7 min read

An attack can hide in a dependency you never chose because your packages pull in other packages, and those pull in more. You approve a handful but run hundreds. An attacker who compromises something deep in that tree reaches you without ever touching what you picked.

TL;DR

  • You choose a few packages directly. Those bring many more, called transitive dependencies.
  • Nobody reviews the deep layers by hand, which is why attackers like them.
  • You can see the whole tree, shrink it, and watch for changes in it.
  • A searchable list of all packages is the most useful tool for the day an alert arrives.

Definition: A direct dependency is a package you add on purpose. A transitive dependency is a package that comes along because one of your dependencies needs it. Together they form the dependency tree, the full family of code your software relies on.

What does a dependency tree look like?

Imagine you add one package to format dates. That package needs a helper for time zones. The helper needs a tiny tool to read a data file. That tool needs a small function to trim spaces. You approved one thing. You now run four.

Here is a made up tree, shown as a simple drawing. The names are invented.

your-app
  date-pretty            (you chose this)
    zone-helper          (you did not)
      file-reader        (you did not)
        trim-it          (you did not)
  web-kit                (you chose this)
    http-tools           (you did not)
      cookie-parse       (you did not)

Each layer is one more place a problem can start. The npm documentation describes the lockfile as the file that records the exact tree that was generated, which means it lists all of these, not just the ones you picked. Our post direct versus transitive dependencies explains the terms in more detail.

Why do attackers like deep dependencies?

There are three reasons.

  1. Nobody is watching. Teams review the packages they chose. They rarely look at the layers below. An attacker who takes over a tiny helper may go unnoticed for a long time.
  2. One compromise reaches many. A small helper might sit underneath thousands of projects. Poison it once and every project above it is affected.
  3. The path is invisible. When a bad package shows up in a scan, many developers say "we never installed that." They did not choose it. It came with something else.

Think of a restaurant again. The chef picks the sauce brand. The sauce maker picks the tomato supplier. If the tomato supplier ships bad goods, the diner gets sick, though the chef never met that supplier.

What real cases show the problem?

CISA's guidance on Log4j is a good example of how deep use hides. It tells organizations to make sure they account for the library in all assets, including internally developed software and technology that is not facing the internet, and notes that organizations may lack insight into applications such as software as a service and other cloud resources.

The XZ Utils case, described in a CISA alert, involved malicious code in versions 5.6.0 and 5.6.1 of a compression library that may be present in Linux distributions. For many people, that library was not something they chose. It sat several layers down inside an operating system. That is the pattern: the part you never see is the part you never check.

See also Log4Shell and the lessons of transitive Java dependencies.

How can you see and shrink your tree?

Work through these steps.

  1. Count it. Ask your package manager for the full list. Write down the total, and the number you chose directly. The ratio often surprises people.
  2. Make a parts list. A software bill of materials (SBOM) is the full list in a shareable form. Keep one per release. See what an SBOM is.
  3. Find the heaviest branches. Which one direct dependency brings the most others? That is where to look first.
  4. Question each heavy branch. Do you need all of this package for one small feature? Could a few lines of your own code replace it?
  5. Lock and watch. Commit your lockfile, install from it, and review changes to it. See what a lockfile is.
  6. Set a rule for new packages. Anyone adding a dependency checks how many new packages it brings.

What does this look like with real numbers?

All numbers here are invented to illustrate the idea. They are not from a real project.

A team lists its direct dependencies: 38. The full tree has 612 packages. So each direct choice brings about 16 packages on average (612 divided by 38 is roughly 16).

But the average hides the story. When the team looks branch by branch:

Direct packagePackages it bringsShare of the 612
web-framework14023 percent
build-toolkit21034 percent
date-pretty61 percent
35 other packages256 together42 percent

Two direct packages account for more than half of the tree. The team asks whether the build toolkit is needed at runtime or only on developer machines. It turns out most of it is used only during development, so the packages that ship to customers fall from 612 to roughly 400. That is a smaller surface to watch and fewer layers an attacker can hide in.

Smaller does not mean safe. It means easier to watch.

Which deep packages need the most attention?

You cannot give equal attention to all of them. Prioritize:

  • Packages that run code at install time.
  • Packages that handle secrets, network traffic or user input.
  • Packages with a single maintainer and no recent activity.
  • Packages that appear in many of your projects.

Reachability helps with ordinary flaws. It asks whether your code can actually reach the weak part of a package. A flaw in a function that your app never calls is lower priority than one on a busy path. But reachability is about known flaws. It does not replace watching for planted code.

How can Safeguard help?

Safeguard is a software supply chain security platform. SBOM and dependency scanning maps your full tree, including packages you never chose, so you can search it. Reachability analysis helps you see which known flaws in those packages your code can actually reach. Policies and gates can check new dependencies at the pull request, and asset discovery helps find projects with trees nobody has looked at in a while. Zero-day discovery surfaces new problems.

You can try the free scan at gold.safeguard.sh/scan to see your own tree and how deep it goes.

Try it yourself

In the demo, look at how reachability checking cuts a long list of alerts down to the few your code can reach. Click through and compare the lists before and after.

Click through it at your own pace, or open it full screen.

Quick check

Am I responsible for packages I did not choose?

In practice, yes. If a hidden package is compromised, the harm lands on your software and your customers. You may not control it, but you do need to know it is there.

Can I remove a transitive dependency myself?

Sometimes. You can replace or drop the direct package that brings it, or use your package manager's override features if it has them. Test carefully, since the parent package expects the child to be there.

How often should I look at my full tree?

Look at the size whenever you add a dependency, and do a short review each month or quarter. Keep an SBOM per release so you can search later.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.