Dependency and supply chain security: protecting your app from risky packages

Most of your app's code is other people's packages. Learn how attacks happen through dependencies and the habits that reduce the risk.

A chain of packages with one risky link being checked

A modern application is mostly other people's code. A typical web project pulls in hundreds or thousands of packages through direct and indirect (transitive) dependencies. That is powerful, but it also means your security depends on code you did not write and may never read. Software supply chain attacks target this trust.

This is a defensive guide. It helps you reduce exposure and respond calmly.

How supply chain problems happen

  • Vulnerable dependencies: a library has a known security bug that affects your app. This is the most common issue.
  • Malicious packages: attackers publish packages with harmful code, often with names similar to popular ones (typosquatting), or names that an internal project might accidentally pull (dependency confusion).
  • Compromised maintainers or accounts: a legitimate package is taken over, or a maintainer's token is stolen, and a bad version is published.
  • Abandoned packages: unmaintained code that never gets fixes, or is handed over to a new, untrustworthy owner.
  • Install-time code execution: packages can run scripts when installed (such as postinstall), which can run on developer machines and CI servers.
  • Build and CI compromises: attackers target pipelines, secrets in CI or build tools rather than the code itself.
  • Compromised CDN or script includes: a third-party script loaded in your pages changes or is hijacked.

Habit 1: know what you depend on

  • Keep a small dependency tree. Every package is added risk. Before adding one, ask: do I really need it, is it maintained, and could a few lines of my own code do the job?
  • Prefer well-known, actively maintained packages with many users, recent releases, a clear repository and a responsible security policy.
  • Check the package name carefully before installing, to avoid typosquats. Look at the publisher, download history and repository link.
  • Review what a new dependency itself pulls in.
  • Generate an SBOM (software bill of materials), a list of components and versions in your software, using standard formats such as CycloneDX or SPDX. It helps you answer "are we affected?" quickly when a new vulnerability is announced.

Habit 2: use lockfiles and reproducible installs

  • Commit the lockfile (package-lock.json, pnpm-lock.yaml, yarn.lock, poetry.lock, go.sum, Cargo.lock and so on). It records the exact versions and integrity hashes, so everyone installs the same code.
  • In CI and production builds, use the strict install command that fails if the lockfile and manifest disagree, such as npm ci, rather than a command that updates versions.
  • Be careful with very loose version ranges (*, latest) that can pull in new, unreviewed code automatically.
  • Consider pinning exact versions for critical packages and updating them deliberately.

Habit 3: scan and update regularly

  • Run a vulnerability scanner in CI: npm audit, pip-audit, govulncheck, GitHub Dependabot alerts, or tools such as OSV-Scanner, Trivy or Snyk. Treat results as input to judgement. Not every finding is exploitable in your use, but do not ignore the serious ones.
  • Enable automated update pull requests (Dependabot or Renovate) so security fixes arrive quickly, then review and test them.
  • Update regularly in small steps. Huge, rare upgrades are risky and get postponed.
  • Remove unused dependencies.
  • Track end-of-life runtimes and frameworks, since unsupported versions stop receiving fixes.

Habit 4: reduce what a bad package can do

  • Limit install scripts. Many ecosystems let you disable or restrict lifecycle scripts for packages that do not need them (for example --ignore-scripts in npm, or allow-lists in some package managers). Test that your build still works.
  • Run builds and tests in isolated environments (containers or CI runners) with no long-lived secrets available.
  • Give CI jobs least-privilege tokens, short-lived where possible, and avoid exposing production secrets to pull-request builds from forks.
  • Use private registries or scoped package names for internal packages, and configure your package manager so internal names are always resolved from the internal registry (this defeats dependency confusion).
  • Do not run applications as root and apply the usual hardening, so a compromised dependency has less reach at runtime.

Habit 5: harden your pipeline

  • Pin third-party CI actions and images to a specific version, and for sensitive workflows to an immutable commit hash or digest, instead of a moving tag.
  • Protect the main branch: require pull request review and passing checks.
  • Use two-factor authentication on registry, source-control and cloud accounts, and prefer trusted publishing or short-lived tokens over long-lived publish tokens.
  • Restrict who can publish your own packages, and review changes to build scripts and CI configuration as carefully as code.
  • Consider signed commits and signed releases, and provenance features offered by your platform (such as build provenance attestations) when you publish packages.

Habit 6: front-end and third-party scripts

  • Minimise third-party scripts. Each one can read and change your page.
  • Use Subresource Integrity (SRI) for scripts and styles loaded from a CDN, so the browser refuses a changed file: <script src="..." integrity="sha384-..." crossorigin="anonymous"></script>
  • Use a Content Security Policy to limit where scripts may load from.
  • Prefer self-hosting important assets when practical.

Habit 7: be ready to respond

When a package you use is reported as compromised or vulnerable:

  1. Check exposure: use your lockfile, SBOM or scanner to find where the package and version appear.
  2. Update or pin to a safe version, or remove the package. Follow the maintainer's advisory.
  3. If a malicious version may have run in your environment (developer laptop, CI, server), treat secrets that were available there as exposed: rotate tokens, keys and passwords, and review logs for unusual activity.
  4. Rebuild from a clean state, and redeploy.
  5. Learn from it: tighten permissions, limit scripts, and improve alerts.

Subscribe to security advisories for your main ecosystems and projects.

Quick checklist

  • Small, reviewed dependency list, correct package names.
  • Lockfile committed, npm ci-style strict installs in CI.
  • Automated scanning and update pull requests.
  • Install scripts limited, builds isolated, no long-lived secrets in CI.
  • Internal packages protected against dependency confusion.
  • Pinned CI actions, branch protection, 2FA, least-privilege tokens.
  • SRI and CSP for third-party scripts.
  • An incident plan: find, update, rotate, rebuild.

Takeaway: you cannot audit every line of every package, so reduce what you depend on, lock and scan versions, limit what installs and builds can do, harden your pipeline and be ready to rotate secrets and update fast.


Web security series, Part 3 (accounts and defence): 4 of 5. Previous: Secure sessions and cookies: HttpOnly, Secure, SameSite and session fixation.

Keep reading