Dependency security updates: audit, triage, and fix with care
A zero-advisory lockfile is a good sign, but it is not a safety guarantee. Understanding what each tool actually checks keeps the process honest.
In the early 2010s, learning about a vulnerable dependency meant reading a mailing-list post, checking a CVE database manually, and hoping your manifest did not contain the affected version. The ecosystem has changed dramatically since then. npm v6 shipped npm audit in 2018. Dependabot and similar services made automated update proposals common in the late 2010s, while the Open Source Vulnerability (OSV) format and OpenSSF projects added more shared infrastructure for describing and assessing risk. Today, automated alerts can arrive shortly after a disclosure. The hard part is no longer finding out about a vulnerability. The hard part is triaging it correctly and applying the fix without introducing a worse problem.
1. Triage by severity and reachability
Every advisory carries a severity label: critical, high, moderate, or low. That label reflects the CVSS score assigned by the reporter or a distributor like the npm Security Advisory database or the GitHub Advisory Database. Start there, but do not stop there. Severity alone does not tell you whether your project actually calls the vulnerable code path. Reachability analysis, available in some specialized security tooling, tries to answer that question by tracing whether the exported API containing the flaw is ever imported or invoked in your dependency tree. A critical flaw in an unused export of a deep transitive dependency is a different threat from a critical flaw in a function your application calls on every request.
2. What npm audit and audit fix actually do
Running npm audit submits your dependency tree to the registry and returns a list of known advisories that match installed versions. The output shows severity, path, and the version range that contains a fix. The audit fix subcommand tries to install the patched version automatically, but it only does so when the update fits within the semver range declared in your manifest. If the fix requires a major bump, npm prints a SEMVER WARNING and leaves the change for you to make manually. This is a deliberate safety constraint: an automatic upgrade that breaks your public API is worse than a temporary advisory.
audit fix --force overrides the semver guard and installs the major bump. That is sometimes the right call for a critical vulnerability with no other mitigation, but it should be a deliberate decision, not a default habit.npm audit fix also does not fix vulnerabilities in packages that have no patched release. For those, the report directs you to check for mitigating factors, open an issue, or submit a pull request upstream.
3. Direct versus transitive fixes
A direct dependency is one you list in your package.json. A transitive dependency is pulled in by one of your direct dependencies. The distinction matters for fix strategy. When a transitive dependency has a vulnerability, you have three options: update the direct parent so it uses a newer version of the transitive package, add an override (npm) or resolution (Yarn) to force the patched version into the tree, or fork the parent and patch the dependency yourself. Overrides are the most common approach, but they introduce a risk: you are using a combination of versions that the upstream maintainer never tested together. Run your full test suite after any override change.
4. Major-version security fixes
Sometimes the only fix for a vulnerability is a new major version of a package. That can feel disproportionate, especially when the vulnerable function is small and the breaking changes are large. Before dismissing the upgrade, check whether the maintainer backported the fix to a supported older branch. Some projects maintain LTS lines specifically for this purpose. If no backport exists and the vulnerability is high or critical, the responsible move is almost always to upgrade and absorb the breaking changes. Document the migration effort in your changelog and note any temporary workarounds so the next person on the project understands why the jump was necessary.
5. Review the diff and run the tests
A security PR from Dependabot or Renovate is not self-approving. Open the diff and look for changes beyond the version bump. Has the package added new dependencies? Did the maintainer change the repository URL or the author email? These can be signs of maintainer compromise. Run the affected package's test suite if you have it available, and run your own integration and end-to-end tests. The lockfile should be regenerated or validated as part of this step, not treated as an opaque blob.
GitHub's dependency review feature surfaces a rich diff of added, removed, and updated dependencies inside a pull request, along with known vulnerabilities for each. Using the dependency-review-action in CI blocks merges that introduce a vulnerable dependency before it reaches your default branch.
6. Lockfile integrity
The lockfile (package-lock.json, yarn.lock, or pnpm-lock.yaml) pins every resolved version and its integrity hash. It is the ground truth for what actually runs in production. After any security fix, verify that the lockfile changed only for the intended packages. A lockfile diff that shows unexpected additions or hash changes deserves investigation, but is not by itself evidence of an attack. Use npm audit signatures to verify provenance attestations and registry signatures for packages that publish them. Sigstore-backed provenance lets you confirm a package was built on a known CI/CD platform from a known repository.
7. Maintainer compromise versus vulnerability
A known vulnerability is a bug with a CVE, an advisory entry, and usually a fix. A maintainer compromise is a social engineering or account takeover event where an attacker publishes a malicious version of a legitimate package under the maintainer's name. The event-stream incident of 2018 is the best-known example: an attacker gained maintainer access, added a dependency that injected cryptocurrency-stealing code, and published it as a routine update. No audit tool would have flagged the code as a known vulnerability because it was not one. It was novel malware inside a trusted package.
Defending against compromise requires a different set of practices: enable 2FA on every account that can publish packages, use npm publish --provenance to generate verifiable build attestations, review new transitive additions in dependency PRs, and monitor OpenSSF Scorecard and Package Analysis signals for suspicious behaviour. Sigstore and SLSA are raising the bar, but no technical control replaces human review of an unexpected dependency addition.
8. Document exceptions
Not every advisory demands an immediate fix. A low-severity issue in a dev-only tool that runs in a locked-down CI environment may be acceptable to defer. When you decide not to fix, write down the rationale: which CVE, why the exploit path does not apply to your deployment, what conditions would trigger a re-evaluation, and when you plan to revisit. Some teams use a ADVISORIES.md file checked into the repository, others use a project issue tagged security-exception. The goal is to make the decision visible and reviewable rather than invisible and permanent.
A clean audit does not mean zero risk
A clean npm audit report means the registry's advisory database does not contain a record that matches any version in your tree. It does not mean your dependencies are free from unknown vulnerabilities, novel supply-chain attacks, or maintainer compromise. The advisory database is only as complete as the disclosures that have been filed, curated, and assigned to the correct version ranges. Treat a clean audit as a useful signal, not a certificate of safety. Keep your dependencies reasonably current, review unexpected additions, and invest in provenance verification and dependency review tooling. The goal is not zero risk. It is informed, documentable risk management.
Sources and further reading
- npm Docs: Auditing package dependencies for security vulnerabilities
- npm Docs: About audit reports
- npm Docs: Generating provenance statements (Sigstore)
- GitHub Docs: Dependabot version and security updates
- GitHub Docs: Dependency review
- GitHub: dependency-review-action
- OSV: Open Source Vulnerability format and API
- OpenSSF: Projects (Scorecard, Sigstore, SLSA, OSV Schema)
- npm Blog: Details about the event-stream incident (November 2018)
- SLSA: Supply-chain Levels for Software Artifacts
- CVE: Common Vulnerabilities and Exposures