Back to blog

Keeping Your Dependency Supply Chain Healthy

Your Code Is Only as Secure as Your Dependencies

A typical JavaScript project has hundreds of dependencies. A mid-sized application can easily pull in over a thousand packages when you include transitive dependencies. Each one of those packages is code that your team did not write, does not review, and often does not even know exists.

This is not inherently bad. Open-source packages save enormous amounts of development time. But every dependency is also a surface area for risk: security vulnerabilities, licence violations, abandoned maintenance, and supply chain attacks. Managing dependencies well is not optional. It is a core part of codebase health.

Dependency Sprawl

Dependency sprawl happens gradually. A developer installs a package to solve a small problem. Another developer installs a different package to solve a similar problem. Over time, the project accumulates packages that overlap in functionality, packages that are no longer used, and packages that were added for a single utility function.

Why It Matters

Every dependency you add increases your attack surface, your build time, and your bundle size. It also increases the maintenance burden. When a vulnerability is disclosed in a package you depend on, you need to assess whether you are affected, update the package, and verify that nothing breaks.

With 50 dependencies, this is manageable. With 500, it becomes a significant operational overhead.

How to Tackle It

Start by auditing your dependencies. Tools like depcheck can identify packages that are installed but never imported. Remove them. Then look for overlapping packages. Do you have both axios and node-fetch? Both moment and dayjs? Pick one and standardise.

Before adding a new dependency, ask whether it is genuinely needed. For small utility functions, writing your own implementation may be preferable to adding a package with its own dependency tree. The npm ecosystem has a well-documented history of packages that do very little but pull in surprising amounts of transitive code.

Lockfile Hygiene

The lockfile (package-lock.json, pnpm-lock.yaml, or yarn.lock) is one of the most important files in your repository. It pins the exact version of every dependency, including transitive ones, ensuring that every developer and every build environment uses identical packages.

Common Problems

  • Missing lockfile. Some teams add the lockfile to .gitignore, which means every npm install can resolve to different versions. This creates "works on my machine" problems and makes builds non-reproducible.
  • Stale lockfile. When developers run npm install instead of npm ci, the lockfile can be updated without anyone noticing. Over time, the lockfile drifts from what was tested and reviewed.
  • Multiple lockfiles. A project with both package-lock.json and yarn.lock is using neither correctly. Pick a package manager and remove the other lockfile.

Best Practices

Always commit your lockfile. Use npm ci (or pnpm install --frozen-lockfile) in CI to ensure builds use the exact versions specified. Add a CI check that fails if the lockfile is out of sync with package.json. Review lockfile changes in pull requests, paying attention to unexpected version bumps.

Transitive Vulnerability Scanning

When you install a package, you also install everything that package depends on. A vulnerability in a transitive dependency can be just as dangerous as one in a direct dependency, but it is far harder to spot.

The Problem With npm audit

npm audit is a useful starting point, but it has significant limitations. It reports vulnerabilities based on version ranges without considering whether your code actually reaches the vulnerable code path. It also produces false positives, which leads teams to ignore its output entirely.

More importantly, npm audit only checks direct and transitive npm packages. It does not assess the broader risk profile of your dependency tree, such as whether a critical package is maintained by a single developer with no succession plan.

A Better Approach

Layer your vulnerability scanning:

  1. Run npm audit or pnpm audit in CI. Treat critical and high-severity findings as build failures.
  2. Use OSV (Open Source Vulnerabilities) scanning for more comprehensive coverage. The OSV database aggregates vulnerabilities from multiple sources and supports scanning lockfiles directly.
  3. Monitor for new disclosures. Vulnerabilities are disclosed after you install a package. Services like GitHub Dependabot or Snyk can alert you when a new vulnerability affects your dependencies.
  4. Assess transitive risk. If a critical transitive dependency has a known vulnerability, you may need to update or replace the direct dependency that pulls it in.

Licence Compliance

Every open-source package comes with a licence. Most are permissive (MIT, Apache-2.0, BSD), but some carry requirements that may be incompatible with your project, particularly copyleft licences like GPL or AGPL.

Why It Matters

Using a GPL-licensed package in a proprietary application can create legal obligations, including the requirement to release your own source code. This is not a theoretical risk. Companies have faced legal action over licence violations.

How to Check

Audit the licences of your direct dependencies. Tools like license-checker or license-report can generate a report of all licences in your dependency tree. Establish a list of approved licences for your project and flag any package that falls outside it.

Licence Type Typical concern
MIT Permissive None
Apache-2.0 Permissive Patent clause (rarely an issue)
BSD-2-Clause / BSD-3-Clause Permissive None
ISC Permissive None
GPL-2.0 / GPL-3.0 Copyleft Must release derivative source code
AGPL-3.0 Strong copyleft Applies to network use
UNLICENSED No licence Cannot legally use

Pay special attention to transitive dependencies. A permissive direct dependency can pull in a copyleft transitive dependency. Your licence audit must cover the entire tree, not just the packages listed in package.json.

Dependency Health as a Quality Signal

The health of your dependency tree is a meaningful indicator of overall codebase quality. Teams that manage dependencies well tend to manage other aspects of their codebase well too.

Key signals of a healthy dependency tree include:

  • Few unused packages. Every dependency serves a clear purpose.
  • No duplicate functionality. One HTTP client, one date library, one utility library.
  • Current versions. Dependencies are kept reasonably up to date, not pinned to versions from years ago.
  • Clean audit results. Known vulnerabilities are addressed promptly.
  • Consistent lockfile. One package manager, one lockfile, committed and enforced in CI.
  • Licence compliance. All licences are reviewed and approved.

These are not just hygiene factors. They directly affect security, performance, and maintainability.

Frequently Asked Questions

How often should I update dependencies?

There is no single right answer, but a regular cadence matters more than the frequency. Many teams update dependencies weekly or fortnightly. Critical security patches should be applied as soon as they are disclosed. Avoid letting dependencies fall so far behind that updating becomes a large, risky project.

Should I use automated dependency update tools?

Tools like Dependabot and Renovate are valuable for keeping dependencies current. However, do not blindly merge their pull requests. Automated updates should pass your full test suite and be reviewed for breaking changes. Use auto-merge only for patch updates in non-critical packages.

How do I handle a vulnerability in a transitive dependency?

First, check whether your code reaches the vulnerable code path. If it does, look for an updated version of the direct dependency that pulls in a patched transitive version. If no patched version exists, consider whether you can replace the direct dependency entirely or apply an override to force a specific version of the transitive package.

What is the right number of dependencies?

There is no magic number. The right question is whether each dependency earns its place. A project with 200 well-chosen, actively maintained dependencies is healthier than one with 50 packages that include abandoned libraries and duplicated functionality.

Practical Steps

Managing your dependency supply chain does not require a dedicated sprint. Build these habits into your regular workflow:

  • Audit unused packages quarterly. Run depcheck and remove anything that is no longer imported.
  • Add licence checking to CI. Fail the build if an unapproved licence is detected.
  • Enforce lockfile integrity. Use --frozen-lockfile in CI and review lockfile changes in PRs.
  • Set up vulnerability alerts. Enable Dependabot or a similar service and triage alerts within a week.
  • Review before adding. Before running npm install, check the package's maintenance status, download count, and dependency tree.

Your dependencies are part of your codebase. Treat them with the same care you give to your own code.