Back

Code quality

Dependency Cleanup: Remove Unused and Vulnerable Packages

Dependencies are useful until they become invisible infrastructure. A package added for one experiment can stay for years. A transitive dependency can pull in risk nobody reviews. A build tool can remain long after the framework that needed it is gone.

Dependency cleanup is not about having the smallest possible package.json, go.mod, Cargo.toml, or lockfile. It is about knowing what your software depends on, why it depends on it, and how quickly you can react when one layer becomes risky.

Sort packages by job

Start by giving each direct dependency a job. The fastest audit is a table with four columns.

Package jobKeep whenRemove when
RuntimeThe application imports it in production codeIt is only used by old scripts or dead paths
BuildThe build, bundler, or compiler needs itThe toolchain changed and the package no longer runs
TestIt supports active testsThe test framework or fixtures were removed
DevelopmentIt improves local workflowsIt is unused, undocumented, or replaced by a standard command

This step usually exposes easy wins: duplicate utilities, old plugins, obsolete type packages, stale polyfills, and libraries used by one deleted feature.

Separate unused from vulnerable

Unused and vulnerable packages need different handling.

Unused packages can often be removed immediately after a clean build and test run. Vulnerable packages require more context. A vulnerability in a dev-only package may have a very different risk profile from one in a request parsing library exposed to users.

Use this triage:

  • Is the package direct or transitive?
  • Is it used at runtime, build time, test time, or only in local development?
  • Is the vulnerable code path reachable in your application?
  • Is there a patched version?
  • Does the patch introduce a breaking change?

Avoid blind bulk upgrades for serious production systems. They can hide behavior changes in a huge diff. Prefer small upgrade batches that can be tested and reviewed.

Reduce your reaction surface

The fewer packages you depend on, the easier security maintenance becomes. That does not mean rewriting mature libraries yourself. It means removing packages that no longer carry their weight.

Good cleanup candidates:

  • One-function utility packages replaced by the language or runtime.
  • Framework plugins for features you no longer use.
  • Duplicate date, validation, logging, or HTTP libraries.
  • Deprecated packages with maintained alternatives.
  • Packages only referenced by old scripts that nobody runs.

When you remove a dependency, delete the related config, lockfile entries, examples, and docs references too. Half-removed dependencies are how cleanup turns into confusion.

Make dependency review recurring

Dependency cleanup works best as a monthly habit. Review direct dependencies, run your audit tools, check stale packages, and verify that the lockfile still matches the project you actually ship.

For teams, make ownership explicit. Security should not be the only group responsible for dependency hygiene. The service owner knows whether a package is reachable, whether an upgrade is risky, and when it is safe to delete.

The goal is not dependency minimalism for its own sake. The goal is a codebase where every package has a reason to exist and every risky package has a clear path to removal or upgrade.