A client's production application depended on 847 npm packages. The client's engineering team had consciously chosen maybe 30 of them. The other 817 were transitive dependencies — packages that their chosen packages depended on, which depended on other packages, and so on.
One of those transitive dependencies was maintained by a single developer who hadn't committed code in 14 months. If that developer's npm account were compromised — through a phished password, a social engineering attack, or simply by selling the package name — a malicious update would flow automatically into my client's production deployment.
This isn't a theoretical risk. In 2025, over 450,000 malicious packages were published to npm and PyPI. The event-stream attack, the ua-parser-js compromise, the colors.js sabotage — these aren't edge cases. They're the new normal.
Know What You Depend On
The first step is visibility. Generate a software bill of materials (SBOM) — a complete inventory of every package in your dependency tree, including transitive dependencies. Tools like Syft, CycloneDX, or your CI platform's built-in scanner can generate this automatically.
Once you have the SBOM, audit it for red flags: packages with no recent maintenance activity (last commit over 12 months ago), single-maintainer packages in your critical path, packages with known vulnerabilities (your scanner will flag these), packages that request unnecessary permissions (a date-formatting library that accesses the network), and packages with suspiciously recent ownership transfers.
You don't need to audit all 847 packages. Focus on the ones in your critical path — packages that handle authentication, encryption, data processing, or network communication. A vulnerable logging utility is less dangerous than a compromised authentication library.
Pin Everything
Floating version ranges in your package.json (^1.2.3 or ~1.2.3) mean that any npm install might pull in a different version than the one you tested. If that new version is malicious or buggy, it flows into your next deployment automatically.
Use lockfiles (package-lock.json, yarn.lock, poetry.lock) and commit them to your repository. In CI, use npm ci instead of npm install — this installs exactly the versions in the lockfile, ignoring package.json ranges. Review lockfile changes in pull requests — a lockfile diff that updates a package you didn't intentionally upgrade deserves scrutiny.
For maximum control, consider a private registry or caching proxy (Artifactory, Verdaccio, or GitHub Packages) that mirrors only approved packages. This adds operational overhead but provides a chokepoint where you can block compromised packages before they reach your developers.
Automate the Boring Parts
Dependency scanning should be automated and continuous. Configure Dependabot, Snyk, or Renovate to open pull requests when new versions are available, flag PRs that introduce packages with known vulnerabilities, alert on license changes (a package that switches from MIT to GPL affects your legal compliance), and block merges that introduce critical or high-severity vulnerabilities.
The key word is "continuous." A one-time dependency audit is a snapshot. Dependencies change every week — new vulnerabilities are discovered, packages are compromised, maintainers abandon projects. Continuous scanning catches issues as they emerge, not months later during a quarterly review.
The Human Layer
Tools catch known threats. The human layer catches everything else. Build awareness on your team: before adding a new dependency, check its maintenance status, maintainer count, download trends, and whether it's solving a problem you could solve with existing dependencies or a few lines of code.
The best defense against dependency bloat is a simple cultural question: "Do we really need another package for this?" A utility function that adds 3 lines of code to your codebase is almost always better than a package that adds 15 transitive dependencies to your supply chain.
Related: Security and Compliance Without a CISO | Proving Compliance Is Harder Than Being Compliant | DevOps Fundamentals for Growing Teams