Dependency and Supply Chain Management

By Davy Rogers

Most of your app is code you didn't write. Here's how to manage that risk.

Open a typical JavaScript project and count the dependencies. Hundreds, once you include the transitive ones, and every single one runs with the full trust of your application. You wrote a sliver of the code that ships; the rest you inherited from strangers. That's not an argument against dependencies, it's the reality of modern development, and managing the risk that comes with it is a core part of shipping safely.

This is a real, active threat

These aren't hypotheticals. They're some of the most-used packages in the ecosystem:

  • event-stream (2018) had a new "maintainer" volunteer to help, then slip in code to steal cryptocurrency.
  • ua-parser-js (2021), with millions of weekly downloads, was compromised and shipped malware.
  • xz-utils (2024) was a multi-year social-engineering operation to plant a backdoor in a core Linux library.

The fundamentals

Lock your versions. Commit your lock file and install with npm ci, not npm install, so every build uses the exact versions you tested.

Depend on less. Before adding a package, ask whether you could write it in twenty lines, how many transitive dependencies it drags in, and whether it's maintained by one person who could disappear or be socially engineered.

Audit regularly. Make it part of the routine, not a one-off:

npm audit
pip-audit
govulncheck ./...

Know what you've got: SBOMs

A Software Bill of Materials is an inventory of every component, version, and licence in your build. Its value shows up the day a new CVE drops: with an SBOM you query it and have your answer in seconds; without one you're grepping through projects by hand while the clock runs.

npx @cyclonedx/cyclonedx-npm --output-file sbom.json
syft . -o cyclonedx-json > sbom.json

Stay current without the grind

Automate updates with Dependabot or Renovate so the PRs open themselves, and auto-merge patch updates once your tests pass. The strategy that works: ranges in the manifest, exact versions in the lock file, and a habit of updating the lock file regularly rather than letting it drift for a year.

Defend against confusion attacks

If you publish internal packages, host them in a private registry (npm Enterprise, Artifactory, GitHub Packages) so an attacker can't publish a public package with the same name and watch your build grab theirs. Even if you never publish, register your organisation's scope, because claiming @yourcompany/ first stops anyone else from claiming it.

Vet before you add

A quick check before pulling in a new dependency saves a lot of pain later. When was the last commit? How many maintainers? Any known vulnerabilities? Does it run install scripts? Is the licence compatible? Tools like Socket.dev and Snyk Advisor answer most of that for you.

The whole discipline reduces to this: lock what you have, audit it often, automate the updates, and keep wanting fewer dependencies than you have.