A typical Node.js application contains a few thousand lines you wrote and a few hundred thousand lines you downloaded. OWASP lists "Vulnerable and Outdated Components" (A06) because attackers do not care who wrote the buggy function, and because a single popular package with a flaw gives them thousands of targets at once. The supply chain adds a second risk: the package may have been malicious from the moment you installed it.
In this lesson you will learn how to know what you run, find and fix known vulnerabilities, make installs reproducible with lockfiles, produce an SBOM, and defend against typosquatting, dependency confusion and malicious install scripts.
You cannot patch what you cannot list. Two commands give the inventory:
npm ls --all # full tree including transitive dependencies
npm outdated # installed vs latest, with the semver range you allowFor anything you ship to customers or run in regulated environments, generate a Software Bill of Materials (SBOM): a machine-readable list of every component and version. npm sbom --sbom-format cyclonedx (or spdx) produces one; store it with each release so that when the next major vulnerability is announced you can answer "are we affected?" in seconds instead of days. The same inventory discipline applies to the runtime: a Node.js version past its end-of-life receives no security fixes.
npm audit compares your lockfile with a database of published advisories:
npm audit # report, grouped by severity
npm audit --audit-level=high # non-zero exit only for high or critical (use in CI)
npm audit fix # apply compatible updates automaticallyWhen the fix requires a version your direct dependency has not adopted yet, force the transitive version with overrides in package.json:
{
"overrides": {
"semver": "^7.5.4"
}
}Treat audit results with judgement: a "critical" issue in a build-time tool that never sees user input is not a critical issue for you, but document that decision rather than silencing the alert. Automate updates with Dependabot or Renovate so patches arrive as small, reviewable pull requests instead of a painful yearly upgrade.
package.json says "any 4.x"; package-lock.json says "exactly 4.18.2, with this integrity hash". Without the lockfile, two installs a day apart can produce different code, and a compromised release published overnight lands in production silently.
npm ci, which installs exactly what the lockfile lists and fails if it disagrees with package.json.integrity field in the lockfile is a SHA-512 hash; npm refuses a tarball that does not match, so a swapped package is caught.npm audit signatures verifies registry signatures and, for packages published with provenance, the attestation linking the package to its source repository and build.| Attack | How it works | Defense |
|---|---|---|
| Typosquatting | lodahs instead of lodash, published with malware | Check the name and download count; copy names from the official README |
| Dependency confusion | A public package with the same name as your private one, at a higher version | Use a scope (@company/pkg) and pin the registry for that scope in .npmrc |
| Malicious install scripts | postinstall runs code at install time | npm ci --ignore-scripts in CI; review scripts of new packages |
| Maintainer account takeover | A trusted package's new version contains a backdoor | Lockfiles, delayed adoption of brand-new releases, provenance checks |
Before adding a dependency, ask whether you need it at all. A ten-line utility copied into your code has no supply chain.
Why is `npm ci` preferred over `npm install` in CI pipelines?
npm ls, npm outdated and an SBOM per release.npm audit --audit-level=high in CI, fix with updates or overrides, and automate patches with Dependabot or Renovate.npm ci; integrity hashes and npm audit signatures catch swapped or unsigned packages.--ignore-scripts.Next lesson: Authentication Failures: Brute Force, Credential Stuffing and MFA — protecting the login flow itself.